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:
@@ -35,7 +35,6 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -12,8 +12,6 @@ import kotlin.math.round
|
||||
* Time: 11:50
|
||||
*/
|
||||
|
||||
@ExperimentalJsExport
|
||||
@JsExport
|
||||
enum class Note(
|
||||
val sharp: String,
|
||||
val flat: String
|
||||
|
||||
Reference in New Issue
Block a user