diff --git a/.gitignore b/.gitignore
index c82ce0d..35b4a7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,4 @@ bin/
### .kotlin ###
.kotlin
kotlin-js-store
+gradle.properties
diff --git a/.idea/artifacts/midi_arrays_js_1_0_0_SNAPSHOT.xml b/.idea/artifacts/midi_arrays_js_1_0_0_SNAPSHOT.xml
new file mode 100644
index 0000000..5fec0c2
--- /dev/null
+++ b/.idea/artifacts/midi_arrays_js_1_0_0_SNAPSHOT.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/artifacts/midi_arrays_jvm_1_0_0_SNAPSHOT.xml b/.idea/artifacts/midi_arrays_jvm_1_0_0_SNAPSHOT.xml
new file mode 100644
index 0000000..bdf9596
--- /dev/null
+++ b/.idea/artifacts/midi_arrays_jvm_1_0_0_SNAPSHOT.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
new file mode 100644
index 0000000..cc50acf
--- /dev/null
+++ b/.idea/jsLibraryMappings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index af3b15e..a13e170 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,64 +1,108 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
plugins {
- kotlin("multiplatform") version "2.1.0"
+ kotlin("multiplatform") version "2.0.21"
+ `maven-publish`
+ signing
}
group = "nl.astraeus"
-version = "1.0.0-SNAPSHOT"
+version = "0.1.0"
repositories {
mavenCentral()
- maven {
- url = uri("https://gitea.astraeus.nl/api/packages/rnentjes/maven")
- }
maven {
url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
}
}
kotlin {
- jvmToolchain(17)
jvm()
js {
- binaries.executable()
- browser {
- distribution {
- outputDirectory.set(File("$projectDir/web/"))
- }
- }
+ browser()
}
sourceSets {
val commonMain by getting {
dependencies {
- api("nl.astraeus:kotlin-simple-logging:1.1.1")
- api("nl.astraeus:kotlin-css-generator:1.0.10")
-
- implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
+ api("nl.astraeus:typed-byte-arrays:0.2.6")
}
}
val commonTest by getting
- val jvmMain by getting {
- dependencies {
- implementation("io.undertow:undertow-core:2.3.14.Final")
- implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0")
-
- implementation("org.xerial:sqlite-jdbc:3.32.3.2")
- implementation("com.zaxxer:HikariCP:4.0.3")
- implementation("nl.astraeus:simple-jdbc-stats:1.6.1") {
- exclude(group = "org.slf4j", module = "slf4j-api")
- }
- }
- }
- val jvmTest by getting {
- dependencies {
- }
- }
- val jsMain by getting {
- dependencies {
- implementation("nl.astraeus:kotlin-komponent:1.2.4")
- }
- }
+ val jvmMain by getting
+ val jvmTest by getting
+ val jsMain by getting
val jsTest by getting
}
-}
\ No newline at end of file
+}
+
+extra["PUBLISH_GROUP_ID"] = group
+extra["PUBLISH_VERSION"] = version
+extra["PUBLISH_ARTIFACT_ID"] = name
+
+// Stub secrets to let the project sync and build without the publication values set up
+val signingKeyId: String? by project
+val signingPassword: String? by project
+val signingSecretKeyRingFile: String? by project
+
+extra["signing.keyId"] = signingKeyId
+extra["signing.password"] = signingPassword
+extra["signing.secretKeyRingFile"] = signingSecretKeyRingFile
+
+val javadocJar by tasks.registering(Jar::class) {
+ archiveClassifier.set("javadoc")
+}
+
+publishing {
+ repositories {
+ maven {
+ name = "gitea"
+ setUrl("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
+
+ credentials {
+ val giteaUsername: String? by project
+ val giteaPassword: String? by project
+
+ username = giteaUsername
+ password = giteaPassword
+ }
+ }
+ }
+
+ // Configure all publications
+ publications.withType {
+ // Stub javadoc.jar artifact
+ artifact(javadocJar.get())
+
+ // Provide artifacts information requited by Maven Central
+ pom {
+ name.set("typed-byte-arrays")
+ description.set("Typed byte arrays")
+ url.set("https://gitea.astraeus.nl/rnentjes/typed-byte-arrays")
+
+ licenses {
+ license {
+ name.set("MIT")
+ url.set("https://opensource.org/licenses/MIT")
+ }
+ }
+ developers {
+ developer {
+ id.set("rnentjes")
+ name.set("Rien Nentjes")
+ email.set("info@nentjes.com")
+ }
+ }
+ scm {
+ url.set("https://gitea.astraeus.nl/rnentjes/typed-byte-arrays")
+ }
+ }
+ }
+}
+
+signing {
+ sign(publishing.publications)
+}
+
+tasks.withType {
+ dependsOn(tasks.withType())
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index bd76f11..6cf07d9 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,5 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
}
-val REPO_NAME = "template"
+
rootProject.name = "midi-arrays"
diff --git a/src/commonMain/kotlin/nl/astraeus/midi/message/MidiMessage.kt b/src/commonMain/kotlin/nl/astraeus/midi/message/MidiMessage.kt
new file mode 100644
index 0000000..03ddef4
--- /dev/null
+++ b/src/commonMain/kotlin/nl/astraeus/midi/message/MidiMessage.kt
@@ -0,0 +1,42 @@
+package nl.astraeus.midi.message
+
+import nl.astraeus.tba.DataType
+import nl.astraeus.tba.MutableByteArrayHandler
+import nl.astraeus.tba.SlicedByteArray
+import nl.astraeus.tba.Type
+import nl.astraeus.tba.TypedByteArray
+import nl.astraeus.tba.blob
+import nl.astraeus.tba.double
+import nl.astraeus.tba.long
+
+class MidiMessage() : TypedByteArray(
+ Type("type", DataType.LONG),
+ Type("generated", DataType.DOUBLE),
+ Type("playTime", DataType.DOUBLE),
+ Type("midi", DataType.BLOB, 240),
+) {
+ var type by long("type")
+ var generated by double("generated")
+ var playTime by double("playTime")
+ var midi by blob("data")
+
+ init {
+ this.type = MidiMessageTypes.MIDI_DATA.typeId
+ }
+
+
+ constructor(data: ByteArray): this() {
+ check(data.size == definition.size) {
+ "Invalid data size: ${data.size} != ${definition.size}"
+ }
+
+ this.data = MutableByteArrayHandler(data)
+ }
+
+ constructor(playTime: Double, midi: ByteArray): this() {
+ this.generated = getCurrentTime()
+ this.playTime = playTime
+ this.midi = SlicedByteArray(midi)
+ }
+
+}
diff --git a/src/commonMain/kotlin/nl/astraeus/midi/message/MidiMessageTypes.kt b/src/commonMain/kotlin/nl/astraeus/midi/message/MidiMessageTypes.kt
new file mode 100644
index 0000000..b224181
--- /dev/null
+++ b/src/commonMain/kotlin/nl/astraeus/midi/message/MidiMessageTypes.kt
@@ -0,0 +1,41 @@
+package nl.astraeus.midi.message
+
+
+inline fun String.encodeToNumber(): T {
+ val cls = T::class
+
+ val length = when(cls) {
+ Byte::class -> 1
+ Short::class -> 2
+ Int::class -> 4
+ Long::class -> 8
+ else -> throw IllegalArgumentException("Unsupported type")
+ }
+
+ var count = 0
+ var result = 0L
+ for (ch in this) {
+ result = result shl 8
+ result += ch.code and 0xff
+
+ if (count++ == length) {
+ break
+ }
+ }
+
+ return when(cls) {
+ Byte::class -> result.toByte() as T
+ Short::class -> result.toShort() as T
+ Int::class -> result.toInt() as T
+ Long::class -> result as T
+ else -> throw IllegalArgumentException("Unsupported type")
+ }
+}
+
+enum class MidiMessageTypes(
+ val type: String,
+ val typeId: Long,
+) {
+ NONE("Unknown", 0L),
+ MIDI_DATA("Timestamped midi data", "TimeMidi".encodeToNumber()),
+}
diff --git a/src/commonMain/kotlin/nl/astraeus/midi/message/Time.kt b/src/commonMain/kotlin/nl/astraeus/midi/message/Time.kt
new file mode 100644
index 0000000..a1b148c
--- /dev/null
+++ b/src/commonMain/kotlin/nl/astraeus/midi/message/Time.kt
@@ -0,0 +1,3 @@
+package nl.astraeus.midi.message
+
+expect fun getCurrentTime(): Double
diff --git a/src/jsMain/kotlin/nl/astraeus/midi/message/Time.js.kt b/src/jsMain/kotlin/nl/astraeus/midi/message/Time.js.kt
new file mode 100644
index 0000000..f255784
--- /dev/null
+++ b/src/jsMain/kotlin/nl/astraeus/midi/message/Time.js.kt
@@ -0,0 +1,3 @@
+package nl.astraeus.midi.message
+
+actual fun getCurrentTime(): Double = js("performance.now()").unsafeCast()
diff --git a/src/jvmMain/kotlin/nl/astraeus/midi/message/Time.jvm.kt b/src/jvmMain/kotlin/nl/astraeus/midi/message/Time.jvm.kt
new file mode 100644
index 0000000..a70ff06
--- /dev/null
+++ b/src/jvmMain/kotlin/nl/astraeus/midi/message/Time.jvm.kt
@@ -0,0 +1,5 @@
+package nl.astraeus.midi.message
+
+actual fun getCurrentTime(): Double {
+ TODO("Not yet implemented")
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/tmpl/Main.kt b/src/jvmMain/kotlin/tmpl/Main.kt
deleted file mode 100644
index db76ced..0000000
--- a/src/jvmMain/kotlin/tmpl/Main.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package tmpl
-
-import com.zaxxer.hikari.HikariConfig
-import nl.astraeus.logger.Logger
-import tmpl.db.Database
-
-val log = Logger()
-
-fun main() {
- Thread.currentThread().uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e ->
- log.warn(e) {
- e.message
- }
- }
-
- Runtime.getRuntime().addShutdownHook(
- object : Thread() {
- override fun run() {
- Database.vacuumDatabase()
- Database.closeDatabase()
- }
- }
- )
-
- Class.forName("nl.astraeus.jdbc.Driver")
- Database.initialize(HikariConfig().apply {
- driverClassName = "nl.astraeus.jdbc.Driver"
- jdbcUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/midi-arrays.db"
- username = "sa"
- password = ""
- maximumPoolSize = 25
- isAutoCommit = false
-
- validate()
- })
-
-}
diff --git a/src/jvmMain/kotlin/tmpl/db/Database.kt b/src/jvmMain/kotlin/tmpl/db/Database.kt
deleted file mode 100644
index eec7b94..0000000
--- a/src/jvmMain/kotlin/tmpl/db/Database.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package tmpl.db
-
-import com.zaxxer.hikari.HikariConfig
-import com.zaxxer.hikari.HikariDataSource
-import java.sql.Connection
-import java.util.*
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlin.collections.set
-import kotlin.use
-
-private val currentConnection = ThreadLocal()
-
-fun transaction(
- block: (Connection) -> T
-): T {
- val hasConnection = currentConnection.get() != null
- var oldConnection: Connection? = null
-
- if (!hasConnection) {
- currentConnection.set(Database.getConnection())
- }
-
- val connection = currentConnection.get()
-
- try {
- val result = block(connection)
-
- connection.commit()
-
- return result
- } finally {
- if (!hasConnection) {
- currentConnection.set(oldConnection)
- connection.close()
- }
- }
-}
-
-object Database {
-
- var ds: HikariDataSource? = null
-
- fun initialize(config: HikariConfig) {
- val properties = Properties()
- properties["journal_mode"] = "WAL"
-
- config.dataSourceProperties = properties
- config.addDataSourceProperty("cachePrepStmts", "true")
- config.addDataSourceProperty("prepStmtCacheSize", "250")
- config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
-
- ds = HikariDataSource(config)
- Migrations.databaseVersionTableCreated = AtomicBoolean(false)
- Migrations.updateDatabaseIfNeeded()
- }
-
- fun getConnection() = ds?.connection ?: error("Database has not been initialized!")
-
- fun vacuumDatabase() {
- getConnection().use {
- it.autoCommit = true
-
- it.prepareStatement("VACUUM").use { ps ->
- ps.executeUpdate()
- }
- }
- }
-
- fun closeDatabase() {
- ds?.close()
- }
-
-}
diff --git a/src/jvmMain/kotlin/tmpl/db/Migrations.kt b/src/jvmMain/kotlin/tmpl/db/Migrations.kt
deleted file mode 100644
index cfc8f3a..0000000
--- a/src/jvmMain/kotlin/tmpl/db/Migrations.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-package tmpl.db
-
-import tmpl.log
-import java.sql.Connection
-import java.sql.SQLException
-import java.sql.Timestamp
-import java.util.concurrent.atomic.AtomicBoolean
-
-sealed class Migration {
- class Query(
- val query: String
- ) : Migration() {
- override fun toString(): String {
- return query
- }
- }
-
- class Code(
- val code: (Connection) -> Unit
- ) : Migration() {
- override fun toString(): String {
- return code.toString()
- }
- }
-}
-
-val DATABASE_MIGRATIONS = arrayOf(
- Migration.Query(
- """
- CREATE TABLE DATABASE_VERSION (
- ID INTEGER PRIMARY KEY,
- QUERY TEXT,
- EXECUTED TIMESTAMP
- )
- """.trimIndent()
- ),
- Migration.Query("SELECT sqlite_version()"),
-)
-
-object Migrations {
- var databaseVersionTableCreated = AtomicBoolean(false)
-
- fun updateDatabaseIfNeeded() {
- try {
- transaction { con ->
- con.prepareStatement(
- """
- SELECT MAX(ID) FROM DATABASE_VERSION
- """.trimIndent()
- ).use { ps ->
- ps.executeQuery().use { rs ->
- databaseVersionTableCreated.compareAndSet(false, true)
-
- if(rs.next()) {
- val maxId = rs.getInt(1)
-
- for (index in maxId + 1..
- log.debug {
- "Executing migration index - [DATABASE_MIGRATIONS[index]]"
- }
- val description = when(
- val migration = DATABASE_MIGRATIONS[index]
- ) {
- is Migration.Query -> {
- con.prepareStatement(migration.query).use { ps ->
- ps.execute()
- }
-
- migration.query
- }
- is Migration.Code -> {
- migration.code(con)
-
- migration.code.toString()
- }
- }
- con.prepareStatement("INSERT INTO DATABASE_VERSION VALUES (?, ?, ?)").use { ps ->
- ps.setInt(1, index)
- ps.setString(2, description)
- ps.setTimestamp(3, Timestamp(System.currentTimeMillis()))
-
- ps.execute()
- }
- }
- }
-
-}