Refactor MIDI handling and update dependencies.

Streamlined MIDI message handling by introducing `MidiMessageHandler` and removed redundant code. Added better handler support for specific message types and parameters. Also upgraded Kotlin to version 2.1.0 and adjusted build configurations.
This commit is contained in:
2024-12-21 20:42:19 +01:00
parent fbba6d1422
commit d58fb9c7b5
7 changed files with 117 additions and 167 deletions

View File

@@ -35,7 +35,6 @@ kotlin {
}
}
}
jvm()
sourceSets {
val commonMain by getting {

View File

@@ -8,6 +8,7 @@ import nl.astraeus.tba.SlicedByteArray
import nl.astraeus.vst.ADSR
import nl.astraeus.vst.AudioWorkletProcessor
import nl.astraeus.vst.currentTime
import nl.astraeus.vst.midi.MidiMessageHandler
import nl.astraeus.vst.registerProcessor
import nl.astraeus.vst.sampleRate
import org.khronos.webgl.Float32Array
@@ -65,8 +66,8 @@ enum class RecordingState {
@ExperimentalJsExport
@JsExport
class VstChipProcessor : AudioWorkletProcessor() {
var midiChannel = 0
val midiMessageBuffer = SortedTimedMidiMessageList()
val midiMessageHandler = MidiMessageHandler()
val notes = Array<PlayingNote?>(POLYPHONICS) { null }
var waveform = Waveform.SINE.ordinal
@@ -100,6 +101,82 @@ class VstChipProcessor : AudioWorkletProcessor() {
init {
this.port.onmessage = ::handleMessage
Note.updateSampleRate(sampleRate)
with(midiMessageHandler) {
addHandler(0x90) { b1, b2, b3 ->
val note = b2.toInt() and 0xff
val velocity = b3.toInt() and 0xff
if (velocity > 0) {
console.log("Note on", note, velocity)
noteOn(note, velocity)
} else {
console.log("Note off", note)
noteOff(note)
}
}
addHandler(0x80) { b1, b2, b3 ->
val note = b2.toInt() and 0xff
console.log("Note off", note)
noteOff(note)
}
addHandler(0xc9) { b1, b2, b3 ->
waveform = b2.toInt() and 0xff
}
addHandler(0xb0, 7) { b1, b2, b3 ->
volume = b3 / 127f
}
addHandler(0xb0, 0x47) { b1, b2, b3 ->
dutyCycle = b3 / 127.0
}
addHandler(0xb0, 0x40) { b1, b2, b3 ->
fmFreq = b3 / 127.0
}
addHandler(0xb0, 0x41) { b1, b2, b3 ->
fmAmp = b3 / 127.0
}
addHandler(0xb0, 0x42) { b1, b2, b3 ->
amFreq = b3 / 127.0
}
addHandler(0xb0, 0x43) { b1, b2, b3 ->
amAmp = b3 / 127.0
}
addHandler(0xb0, 0x49) { b1, b2, b3 ->
attack = b3 / 127.0
}
addHandler(0xb0, 0x4b) { b1, b2, b3 ->
decay = b3 / 127.0
}
addHandler(0xb0, 0x46) { b1, b2, b3 ->
sustain = b3 / 127.0
}
addHandler(0xb0, 0x48) { b1, b2, b3 ->
release = b3 / 127.0
}
addHandler(0xb0, 0x4e) { b1, b2, b3 ->
delay = b3 / 127.0
}
addHandler(0xb0, 0x4f) { b1, b2, b3 ->
delayDepth = b3 / 127.0
}
addHandler(0xb0, 0x50) { b1, b2, b3 ->
feedback = b3 / 127.0
}
addHandler(0xb0, 123) { b1, b2, b3 ->
for (note in notes) {
note?.noteRelease = currentTime
}
}
addHandler(0xe0) { b1, b2, b3 ->
if (b2.toInt() and 0xff > 0) {
val lsb = b2.toInt() and 0xff
val msb = b3.toInt() and 0xff
amFreq = (((msb - 0x40) + (lsb / 127.0)) / 0x40) * 10.0
}
}
}
}
private fun handleMessage(message: MessageEvent) {
@@ -122,8 +199,9 @@ class VstChipProcessor : AudioWorkletProcessor() {
data.startsWith("set_channel") -> {
val parts = data.split('\n')
if (parts.size == 2) {
midiChannel = parts[1].toInt()
println("Setting channel: $midiChannel")
midiMessageHandler.channel = parts[1].toByte()
println("Setting channel: ${midiMessageHandler.channel}")
}
}
@@ -166,8 +244,9 @@ class VstChipProcessor : AudioWorkletProcessor() {
}
private fun playBuffer() {
while (midiMessageBuffer.isNotEmpty() && (midiMessageBuffer.nextTimestamp()
?: 0.0) < currentTime
while (
midiMessageBuffer.isNotEmpty() &&
(midiMessageBuffer.nextTimestamp() ?: 0.0) < currentTime
) {
val midi = midiMessageBuffer.read()
console.log("Message", currentTime, midi)
@@ -178,163 +257,16 @@ class VstChipProcessor : AudioWorkletProcessor() {
private fun playMidi(bytes: SlicedByteArray) {
var index = 0
console.log(
"--playMidi",
bytes.size,
index,
bytes[index + 0],
bytes[index + 1],
bytes[index + 2]
)
while (index < bytes.size && bytes[index].toUByte() > 0u) {
console.log("playMidi", bytes, index, bytes[index + 0], bytes[index + 1], bytes[index + 2])
index += playMidiFromBuffer(bytes, index)
val buffer = bytes.getBlob(index, 3)
playMidiFromBuffer(buffer)
index += 3
}
}
private fun playMidiFromBuffer(bytes: SlicedByteArray, index: Int): Int {
if (bytes[index] == 0.toByte()) {
return 0
}
if (bytes.length > 0) {
var cmdByte = bytes[0].toInt() and 0xff
val channelCmd = ((cmdByte shr 4) and 0xf) != 0xf0
val channel = cmdByte and 0xf
//println("Channel cmd: $channelCmd")
val byteLength = when (cmdByte) {
0x90, 0xb0, 0xe0 -> 3
0x80, 0xc9 -> 2
else -> throw IllegalArgumentException("Unknown command: $cmdByte")
}
if (channelCmd && channel != midiChannel) {
console.log("Wrong channel", midiChannel, bytes)
return byteLength
}
cmdByte = cmdByte and 0xf0
//console.log("Received", bytes)
when (cmdByte) {
0x90 -> {
if (bytes.length >= 3) {
val note = bytes[1].toInt() and 0xff
val velocity = bytes[2].toInt() and 0xff
if (velocity > 0) {
console.log("Note on", note, velocity)
noteOn(note, velocity)
} else {
console.log("Note off", note)
noteOff(note)
}
}
}
0x80 -> {
if (bytes.length >= 2) {
val note = bytes[1].toInt() and 0xff
console.log("Note off", note)
noteOff(note)
}
}
0xc9 -> {
if (bytes.length >= 1) {
val waveform = bytes[1].toInt() and 0xff
if (waveform < 4) {
this.waveform = waveform
}
}
}
0xb0 -> {
if (bytes.length >= 3) {
val knob = bytes[1].toInt() and 0xff
val value = bytes[2].toInt() and 0xff
when (knob) {
7 -> {
volume = value / 127f
}
0x47 -> {
dutyCycle = value / 127.0
}
0x40 -> {
fmFreq = value / 127.0
}
0x41 -> {
fmAmp = value / 127.0
}
0x42 -> {
amFreq = value / 127.0
}
0x43 -> {
amAmp = value / 127.0
}
0x49 -> {
attack = value / 127.0
}
0x4b -> {
decay = value / 127.0
}
0x46 -> {
sustain = value / 127.0
}
0x48 -> {
release = value / 127.0
}
0x4e -> {
delay = value / 127.0
println("Setting delay $delay")
}
0x4f -> {
delayDepth = value / 127.0
println("Setting delayDepth $delayDepth")
}
0x50 -> {
feedback = value / 127.0
println("Setting feedback $delayDepth")
}
123 -> {
for (note in notes) {
note?.noteRelease = currentTime
}
}
}
}
}
0xe0 -> {
if (bytes.length >= 3) {
val lsb = bytes[1]
val msb = bytes[2]
amFreq = (((msb - 0x40) + (lsb / 127.0)) / 0x40) * 10.0
}
}
}
return byteLength
}
throw IllegalArgumentException("Unable to handle empty byte array")
private fun playMidiFromBuffer(bytes: SlicedByteArray) {
midiMessageHandler.handle(bytes[0], bytes[1], bytes[2])
}
private fun noteOn(note: Int, velocity: Int) {

View File

@@ -12,8 +12,6 @@ import kotlin.math.round
* Time: 11:50
*/
@ExperimentalJsExport
@JsExport
enum class Note(
val sharp: String,
val flat: String

View File

@@ -3,13 +3,12 @@ version = "0.1.0"
allprojects {
repositories {
mavenLocal()
mavenCentral()
maven {
url = uri("https://gitea.astraeus.nl/api/packages/rnentjes/maven")
}
maven {
url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
}
mavenCentral()
}
}

View File

@@ -1,6 +1,6 @@
pluginManagement {
plugins {
kotlin("multiplatform") version "2.0.21"
kotlin("multiplatform") version "2.1.0"
}
repositories {
gradlePluginPortal()

View File

@@ -2,6 +2,7 @@
package nl.astraeus.vst.chip.audio
import nl.astraeus.midi.message.TimedMidiMessage
import nl.astraeus.vst.chip.PatchDTO
import nl.astraeus.vst.chip.view.MainView
import nl.astraeus.vst.chip.view.WaveformView
@@ -134,7 +135,20 @@ object VstChipWorklet : AudioNode(
super.postMessage(msg)
}
override fun postMessage(msg: Any) {
if (msg is ByteArray) {
val tmm = TimedMidiMessage(msg)
val byte1 = tmm.midi[0]
if (byte1.toInt() and 0xf0 == 0xb0) {
handleIncomingMidi(tmm.midi[1], tmm.midi[2])
}
}
super.postMessage(msg)
}
override fun postMessage(vararg msg: Int) {
println("postMessage ${msg.size} bytes")
if (
msg.size == 3
&& (msg[0] and 0xf == midiChannel)
@@ -144,12 +158,13 @@ object VstChipWorklet : AudioNode(
val value = msg[2]
handleIncomingMidi(knob.toByte(), value.toByte())
} else {
super.postMessage(msg)
}
super.postMessage(msg)
}
private fun handleIncomingMidi(knob: Byte, value: Byte) {
println("Incoming knob: $knob, value: $value")
when (knob) {
0x46.toByte() -> {
volume = value / 127.0

View File

@@ -35,6 +35,8 @@ import nl.astraeus.css.style.cls
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
import nl.astraeus.komp.currentElement
import nl.astraeus.midi.message.TimedMidiMessage
import nl.astraeus.midi.message.getCurrentTime
import nl.astraeus.vst.chip.audio.VstChipWorklet
import nl.astraeus.vst.chip.audio.VstChipWorklet.midiChannel
import nl.astraeus.vst.chip.midi.Midi
@@ -46,7 +48,6 @@ import nl.astraeus.vst.ui.css.Css.defineCss
import nl.astraeus.vst.ui.css.Css.noTextSelect
import nl.astraeus.vst.ui.css.CssName
import nl.astraeus.vst.ui.css.hover
import nl.astraeus.vst.ui.util.uInt8ArrayOf
import org.khronos.webgl.get
import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.HTMLCanvasElement
@@ -114,6 +115,11 @@ object MainView : Komponent(), CssName {
requestUpdate()
}
override fun renderUpdate() {
println("Rendering MainView")
super.renderUpdate()
}
override fun HtmlBuilder.render() {
div(MainDivCss.name) {
if (!started) {
@@ -190,7 +196,8 @@ object MainView : Komponent(), CssName {
+"STOP"
onClickFunction = {
VstChipWorklet.postDirectlyToWorklet(
uInt8ArrayOf(0xb0 + midiChannel, 123, 0)
TimedMidiMessage(getCurrentTime(), (0xb0 + midiChannel).toByte(), 123, 0)
.data.buffer.data
)
}
}