Refactor MIDI handling and improve audio processing

Replaced `uInt8ArrayOf` with simplified integer arrays for MIDI messages. Introduced `TimedMidiMessage` and buffer handling for better synchronization in audio processing. Updated Gradle dependencies and added timing-aware MIDI utilities.
This commit is contained in:
2024-12-17 20:51:32 +01:00
parent 4c00356dff
commit fbba6d1422
8 changed files with 153 additions and 80 deletions

View File

@@ -41,6 +41,7 @@ kotlin {
val commonMain by getting {
dependencies {
implementation("nl.astraeus:vst-worklet-base:1.0.1")
implementation("nl.astraeus:midi-arrays:0.3.2")
}
}
val jsMain by getting

View File

@@ -2,14 +2,15 @@
package nl.astraeus.vst.chip
import nl.astraeus.midi.message.SortedTimedMidiMessageList
import nl.astraeus.midi.message.TimedMidiMessage
import nl.astraeus.tba.SlicedByteArray
import nl.astraeus.vst.ADSR
import nl.astraeus.vst.AudioWorkletProcessor
import nl.astraeus.vst.currentTime
import nl.astraeus.vst.registerProcessor
import nl.astraeus.vst.sampleRate
import org.khronos.webgl.Float32Array
import org.khronos.webgl.Int32Array
import org.khronos.webgl.Uint8Array
import org.khronos.webgl.get
import org.khronos.webgl.set
import org.w3c.dom.MessageEvent
@@ -65,6 +66,7 @@ enum class RecordingState {
@JsExport
class VstChipProcessor : AudioWorkletProcessor() {
var midiChannel = 0
val midiMessageBuffer = SortedTimedMidiMessageList()
val notes = Array<PlayingNote?>(POLYPHONICS) { null }
var waveform = Waveform.SINE.ordinal
@@ -101,15 +103,15 @@ class VstChipProcessor : AudioWorkletProcessor() {
}
private fun handleMessage(message: MessageEvent) {
//console.log("VstChipProcessor: Received message:", message.data)
//console.log("VstChipProcessor: Received message:", currentTime)
val data = message.data
try {
when (data) {
is String -> {
when (data) {
"start_recording" -> {
when {
data == "start_recording" -> {
port.postMessage(recordingBuffer)
if (recordingState == RecordingState.STOPPED) {
recordingState = RecordingState.WAITING_TO_START
@@ -117,34 +119,43 @@ class VstChipProcessor : AudioWorkletProcessor() {
}
}
else ->
if (data.startsWith("set_channel")) {
val parts = data.split('\n')
if (parts.size == 2) {
midiChannel = parts[1].toInt()
println("Setting channel: $midiChannel")
}
} else if (data.startsWith("waveform")) {
val parts = data.split('\n')
if (parts.size == 2) {
waveform = parts[1].toInt()
println("Setting waveform: $waveform")
}
data.startsWith("set_channel") -> {
val parts = data.split('\n')
if (parts.size == 2) {
midiChannel = parts[1].toInt()
println("Setting channel: $midiChannel")
}
}
data.startsWith("waveform") -> {
val parts = data.split('\n')
if (parts.size == 2) {
waveform = parts[1].toInt()
println("Setting waveform: $waveform")
}
}
}
}
is Uint8Array -> {
val data32 = Int32Array(data.length)
for (i in 0 until data.length) {
data32[i] = (data[i].toInt() and 0xff)
}
playMidi(data32)
is ByteArray -> {
val message1 = TimedMidiMessage(data)
console.log("Message as bytearray: ", message1.timeToPlay, data)
midiMessageBuffer.add(message1)
playBuffer()
}
/*
is Uint8Array -> {
val data32 = Int32Array(data.length)
for (i in 0 until data.length) {
data32[i] = (data[i].toInt() and 0xff)
}
playMidi(data32)
}
is Int32Array -> {
playMidi(data)
}
is Int32Array -> {
playMidi(data)
}
*/
else ->
console.error("Don't kow how to handle message", message)
@@ -154,16 +165,52 @@ class VstChipProcessor : AudioWorkletProcessor() {
}
}
private fun playMidi(bytes: Int32Array) {
//console.log("playMidi", bytes)
private fun playBuffer() {
while (midiMessageBuffer.isNotEmpty() && (midiMessageBuffer.nextTimestamp()
?: 0.0) < currentTime
) {
val midi = midiMessageBuffer.read()
console.log("Message", currentTime, midi)
playMidi(midi.midi)
}
}
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)
}
}
private fun playMidiFromBuffer(bytes: SlicedByteArray, index: Int): Int {
if (bytes[index] == 0.toByte()) {
return 0
}
if (bytes.length > 0) {
var cmdByte = bytes[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
return byteLength
}
cmdByte = cmdByte and 0xf0
@@ -171,13 +218,15 @@ class VstChipProcessor : AudioWorkletProcessor() {
//console.log("Received", bytes)
when (cmdByte) {
0x90 -> {
if (bytes.length == 3) {
val note = bytes[1]
val velocity = bytes[2]
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)
}
}
@@ -185,15 +234,16 @@ class VstChipProcessor : AudioWorkletProcessor() {
0x80 -> {
if (bytes.length >= 2) {
val note = bytes[1]
val note = bytes[1].toInt() and 0xff
console.log("Note off", note)
noteOff(note)
}
}
0xc9 -> {
if (bytes.length >= 1) {
val waveform = bytes[1]
val waveform = bytes[1].toInt() and 0xff
if (waveform < 4) {
this.waveform = waveform
@@ -202,9 +252,9 @@ class VstChipProcessor : AudioWorkletProcessor() {
}
0xb0 -> {
if (bytes.length == 3) {
val knob = bytes[1]
val value = bytes[2]
if (bytes.length >= 3) {
val knob = bytes[1].toInt() and 0xff
val value = bytes[2].toInt() and 0xff
when (knob) {
7 -> {
@@ -272,7 +322,7 @@ class VstChipProcessor : AudioWorkletProcessor() {
}
0xe0 -> {
if (bytes.length == 3) {
if (bytes.length >= 3) {
val lsb = bytes[1]
val msb = bytes[2]
@@ -280,7 +330,11 @@ class VstChipProcessor : AudioWorkletProcessor() {
}
}
}
return byteLength
}
throw IllegalArgumentException("Unable to handle empty byte array")
}
private fun noteOn(note: Int, velocity: Int) {
@@ -332,6 +386,8 @@ class VstChipProcessor : AudioWorkletProcessor() {
recordingStart = 0
}
playBuffer()
for ((index, note) in notes.withIndex()) {
if (note != null) {
val midiNote = Note.fromMidi(note.note)
@@ -468,5 +524,5 @@ class VstChipProcessor : AudioWorkletProcessor() {
fun main() {
registerProcessor("vst-chip-processor", VstChipProcessor::class.js)
println("'vst-chip-processor' registered!")
console.log("'vst-chip-processor' registered!", currentTime)
}