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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user