-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
49b41db
commit 0c6d915
Showing
9 changed files
with
317 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1657,6 +1657,11 @@ jest-worker@^27.4.5: | |
merge-stream "^2.0.0" | ||
supports-color "^8.0.0" | ||
|
||
[email protected]: | ||
version "1.10.0" | ||
resolved "https://registry.yarnpkg.com/js-synthesizer/-/js-synthesizer-1.10.0.tgz#b13fd5589697cd4f8a3bea221586e9c5919e3bf7" | ||
integrity sha512-OR8j8QcWgAPvVcJ8LOtCx3Ypdm7lfyumYGQmxLUWIpZmnpzoUlb/HDYagHMv79je1D/rS6mEkq27F48MS87LMw== | ||
|
||
[email protected]: | ||
version "4.1.0" | ||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import org.jetbrains.compose.compose | ||
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl | ||
|
||
plugins { | ||
kotlin("multiplatform") | ||
id("maven-publish") | ||
id("signing") | ||
id("org.jetbrains.dokka") | ||
} | ||
|
||
kotlin { | ||
@OptIn(ExperimentalWasmDsl::class) | ||
wasmJs { | ||
browser {} | ||
} | ||
sourceSets { | ||
val wasmJsMain by getting { | ||
dependencies { | ||
implementation(libs.compose.components.resources) | ||
implementation(project(":ktmidi")) | ||
implementation(npm("js-synthesizer", libs.versions.js.synthesizer.get())) | ||
} | ||
} | ||
} | ||
} | ||
|
||
tasks.withType<AbstractPublishToMaven>().configureEach { | ||
val signingTasks = tasks.withType<Sign>() | ||
mustRunAfter(signingTasks) | ||
} | ||
|
||
tasks { | ||
val dokkaOutputDir = "${layout.buildDirectory}/dokka" | ||
|
||
dokkaHtml { | ||
outputDirectory.set(file(dokkaOutputDir)) | ||
} | ||
|
||
val deleteDokkaOutputDir by registering(Delete::class) { | ||
delete(dokkaOutputDir) | ||
} | ||
|
||
register<Jar>("javadocJar") { | ||
dependsOn(deleteDokkaOutputDir, dokkaHtml) | ||
archiveClassifier.set("javadoc") | ||
from(dokkaOutputDir) | ||
} | ||
} | ||
|
||
val repositoryId: String? = System.getenv("OSSRH_STAGING_REPOSITORY_ID") | ||
val moduleDescription = "Kotlin Multiplatform library for MIDI 1.0 and MIDI 2.0 - Native specific" | ||
// copypasting | ||
afterEvaluate { | ||
publishing { | ||
publications { | ||
publications.withType<MavenPublication>{ | ||
// https://github.com/gradle/gradle/issues/26091#issuecomment-1681343496 | ||
val dokkaJar = project.tasks.register("${name}DokkaJar", Jar::class) { | ||
group = JavaBasePlugin.DOCUMENTATION_GROUP | ||
description = "Assembles Kotlin docs with Dokka into a Javadoc jar" | ||
archiveClassifier.set("javadoc") | ||
from(tasks.named("dokkaHtml")) | ||
|
||
// Each archive name should be distinct, to avoid implicit dependency issues. | ||
// We use the same format as the sources Jar tasks. | ||
// https://youtrack.jetbrains.com/issue/KT-46466 | ||
archiveBaseName.set("${archiveBaseName.get()}-${name}") | ||
} | ||
artifact(dokkaJar) | ||
|
||
pom { | ||
name.set("$name") | ||
description.set(moduleDescription) | ||
url.set("https://github.com/atsushieno/ktmidi") | ||
scm { | ||
url.set("https://github.com/atsushieno/ktmidi") | ||
} | ||
licenses { | ||
license { | ||
name.set("the MIT License") | ||
url.set("https://github.com/atsushieno/ktmidi/blob/main/LICENSE") | ||
} | ||
} | ||
developers { | ||
developer { | ||
id.set("atsushieno") | ||
name.set("Atsushi Eno") | ||
email.set("[email protected]") | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
repositories { | ||
maven { | ||
name = "OSSRH" | ||
//url = uri("https://s01.oss.sonatype.org/service/local/staging/deployByRepositoryId/$repositoryId/") | ||
url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") | ||
credentials { | ||
username = System.getenv("OSSRH_USERNAME") | ||
password = System.getenv("OSSRH_PASSWORD") | ||
} | ||
} | ||
} | ||
} | ||
|
||
// keep it as is. It is replaced by CI release builds | ||
signing {} | ||
} | ||
|
||
|
66 changes: 66 additions & 0 deletions
66
ktmidi-wasm-ext/src/wasmJsMain/kotlin/dev/atsushieno/ktmidi/ISynthesizer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
@file:JsModule("js-synthesizer") | ||
|
||
package dev.atsushieno.ktmidi | ||
|
||
import org.khronos.webgl.ArrayBuffer | ||
import org.khronos.webgl.Uint8Array | ||
import kotlin.js.Promise | ||
|
||
external interface SynthesizerSettings : JsAny | ||
external interface InterpolationValues : JsAny | ||
external interface PlayerSetTempoType : JsAny | ||
|
||
external interface ISynthesizer : JsAny { | ||
fun isInitialized(): Boolean | ||
fun init(sampleRate: Double, settings: SynthesizerSettings? = definedExternally) | ||
fun close() | ||
fun isPlaying(): Boolean | ||
fun setInterpolation(value: InterpolationValues, channel: Double?) | ||
fun getGain(): Double | ||
fun setGain(gain: Double) | ||
fun setChannelType(channel: Double, isDrum: Boolean) | ||
fun waitForVoicesStopped(): Promise<JsAny?> // void | ||
|
||
fun loadSFont(bin: ArrayBuffer): Promise<JsAny?> // number | ||
fun unloadSFont(id: Double) | ||
fun unloadSFontAsync(id: Double): Promise<JsAny?> // void | ||
fun getSFontBankOffset(id: Double): Promise<JsAny?> // number | ||
fun setSFontBankOffset(id: Double, offset: Double) | ||
|
||
fun render(outBuffer: JsAny?) // AudioBuffer | Float32Array[] | ||
|
||
fun midiNoteOn(chan: Byte, key: Byte, vel: Byte) | ||
fun midiNoteOff(chan: Byte, key: Byte) | ||
fun midiKeyPressure(chan: Byte, key: Byte, value: Byte) | ||
fun midiControl(chan: Byte, ctrl: Byte, value: Byte) | ||
fun midiProgramChange(chan: Byte, prognum: Byte) | ||
fun midiChannelPressure(chan: Byte, value: Byte) | ||
fun midiPitchBend(chan: Byte, value: Short) | ||
fun midiSysEx(data: Uint8Array) | ||
|
||
fun midiPitchWheelSensitivity(chan: Byte, value: Byte) | ||
fun midiBankSelect(chan: Byte, bank: Byte) | ||
fun midiSFontSelect(chan: Byte, sfontId: Int) | ||
fun midiProgramSelect(chan: Byte, sfontId: Int, bank: Byte, presetNum: Byte) | ||
fun midiUnsetProgram(chan: Byte) | ||
fun midiProgramReset() | ||
fun midiSystemReset() | ||
fun midiAllNotesOff(chan: Byte?) | ||
fun midiAllSoundsOff(chan: Byte?) | ||
fun midiSetChannelType(chan: Byte, isDrum: Boolean) | ||
|
||
fun resetPlayer(): Promise<JsAny?> // void | ||
fun closePlayer() | ||
fun isPlayerPlaying(): Boolean | ||
fun addSMFDataToPlayer(bin: ArrayBuffer): Promise<JsAny?> // void | ||
fun playPlayer(): Promise<JsAny?> // void | ||
fun stopPlayer() | ||
fun retrievePlayerCurrentTick(): Promise<JsAny?> // number | ||
fun retrievePlayerTotalTicks(): Promise<JsAny?> // number | ||
fun retrievePlayerBpm(): Promise<JsAny?> // number | ||
fun retrievePlayerMIDITempo(): Promise<JsAny?> // number | ||
fun seekPlayer(ticks: Double) | ||
fun setPlayerLoop(loopTimes: Double) | ||
fun setPlayerTempo(tempoType: PlayerSetTempoType, tempo: Double) | ||
fun waitForPlayerStopped(): Promise<JsAny?> // void | ||
} |
104 changes: 104 additions & 0 deletions
104
ktmidi-wasm-ext/src/wasmJsMain/kotlin/dev/atsushieno/ktmidi/JsSynthesizerMidiAccess.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package dev.atsushieno.ktmidi | ||
|
||
//import org.jetbrains.compose.resources.InternalResourceApi | ||
//import org.jetbrains.compose.resources.readResourceBytes | ||
import org.khronos.webgl.ArrayBuffer | ||
import org.khronos.webgl.Uint8Array | ||
|
||
private fun createSynthesizer(): ISynthesizer = js("new JSSynth.Synthesizer()") | ||
private fun waitForReady(runnable: ()->Unit): Nothing = js("JSSynth.waitForReady().then(runnable)") | ||
|
||
class JsSynthesizerMidiAccess(private val sfBinary: ArrayBuffer, sfName: String) : MidiAccess() { | ||
companion object { | ||
|
||
/* | ||
@Suppress("CAST_NEVER_SUCCEEDS") | ||
@OptIn(InternalResourceApi::class) | ||
suspend fun createDefault() = | ||
create(readResourceBytes("FluidR3_GM.sf2") as ArrayBuffer, "FluidR3_GM.sf2") | ||
*/ | ||
|
||
fun create(sfBinary: ArrayBuffer, sfName: String): JsSynthesizerMidiAccess { | ||
var synth: JsSynthesizerMidiAccess? = null | ||
println("!!! JsSynthesizerMidiAccess.create() start.") | ||
try { | ||
waitForReady { | ||
println("!!! in waitForReady().") | ||
synth = JsSynthesizerMidiAccess(sfBinary, sfName) | ||
println("!!! waitForReady() done.") | ||
} | ||
println("!!! JsSynthesizerMidiAccess.create() done.") | ||
} catch (ex: Exception) { | ||
println(ex) | ||
throw ex | ||
} | ||
return synth!! | ||
} | ||
} | ||
|
||
private val details = JsSynthesizerMidiOutputDetails(sfName) | ||
|
||
override val name: String | ||
get() = "JsSynthesizer" | ||
override val inputs: Iterable<MidiPortDetails> | ||
get() = listOf() | ||
override val outputs: Iterable<MidiPortDetails> | ||
get() = listOf(details) | ||
|
||
override suspend fun openInput(portId: String): MidiInput { | ||
throw UnsupportedOperationException() | ||
} | ||
|
||
override suspend fun openOutput(portId: String): MidiOutput { | ||
val synth = createSynthesizer() | ||
synth.init(44100.0) | ||
if (sfBinary.byteLength > 0) | ||
synth.loadSFont(sfBinary) | ||
return JsSynthesizerMidiOutput(details, synth) | ||
} | ||
|
||
internal class JsSynthesizerMidiOutputDetails( | ||
private val soundFontName: String, | ||
override val version: String = "1.0" | ||
) : MidiPortDetails { | ||
override val id: String = soundFontName | ||
override val manufacturer: String | ||
get() = "JsSynthesizerMidiAccess" | ||
override val name: String | ||
get() = "js-synthesizer: $soundFontName" | ||
override val midiTransportProtocol: Int | ||
get() = MidiTransportProtocol.MIDI1 | ||
} | ||
|
||
internal class JsSynthesizerMidiOutput( | ||
override val details: MidiPortDetails, | ||
private val synth: ISynthesizer | ||
) : MidiOutput { | ||
private var state = MidiPortConnectionState.OPEN | ||
|
||
override fun send(mevent: ByteArray, offset: Int, length: Int, timestampInNanoseconds: Long) { | ||
Midi1Message.convert(mevent, offset, length, Midi1SysExChunkProcessor()).onEach { | ||
when(it.statusCode.toInt()) { | ||
MidiChannelStatus.NOTE_OFF -> synth.midiNoteOff(it.channel, it.msb) | ||
MidiChannelStatus.NOTE_ON -> synth.midiNoteOn(it.channel, it.msb, it.lsb) | ||
MidiChannelStatus.PAF -> synth.midiKeyPressure(it.channel, it.msb, it.lsb) | ||
MidiChannelStatus.CC -> synth.midiControl(it.channel, it.msb, it.lsb) | ||
MidiChannelStatus.PROGRAM -> synth.midiProgramChange(it.channel, it.msb) | ||
MidiChannelStatus.CAF -> synth.midiChannelPressure(it.channel, it.msb) | ||
MidiChannelStatus.PITCH_BEND -> synth.midiPitchBend(it.channel, (it.msb * 128 + it.lsb).toShort()) | ||
else -> | ||
if (it.statusByte.toInt() == Midi1Status.SYSEX) | ||
synth.midiSysEx((it as Midi1CompoundMessage).extraData!! as Uint8Array) | ||
} | ||
} | ||
} | ||
|
||
override val connectionState: MidiPortConnectionState | ||
get() = state | ||
|
||
override fun close() { | ||
synth.close() | ||
state = MidiPortConnectionState.CLOSED | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters