Also search on name when setting midi port

This commit is contained in:
2024-07-13 16:44:33 +02:00
parent f2269c8865
commit 8df6a4fff6
44 changed files with 2379 additions and 926 deletions

View File

@@ -0,0 +1,264 @@
@file:OptIn(ExperimentalJsExport::class)
package nl.astraeus.vst.string
import nl.astraeus.vst.AudioWorkletProcessor
import nl.astraeus.vst.Note
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
import kotlin.math.PI
import kotlin.math.min
val POLYPHONICS = 10
val PI2 = PI * 2
@ExperimentalJsExport
@JsExport
class PlayingNote(
val note: Int,
var velocity: Int = 0
) {
fun retrigger(velocity: Int) {
this.velocity = velocity
sample = 0
noteStart = currentTime
noteRelease = null
}
var noteStart = currentTime
var noteRelease: Double? = null
var cycleOffset = 0.0
var sample = 0
var actualVolume = 0f
}
@ExperimentalJsExport
@JsExport
enum class RecordingState {
STOPPED,
WAITING_TO_START,
RECORDING
}
@ExperimentalJsExport
@JsExport
class VstStringProcessor : AudioWorkletProcessor() {
var midiChannel = 0
var volume = 0.75f
var damping = 0.996
val recordingBuffer = Float32Array(sampleRate / 60)
var recordingState = RecordingState.STOPPED
var recordingSample = 0
var recordingStart = 0
val strings = Array(POLYPHONICS) {
PhysicalString(sampleRate, damping)
}
init {
this.port.onmessage = ::handleMessage
Note.updateSampleRate(sampleRate)
}
private fun handleMessage(message: MessageEvent) {
//console.log("VstChipProcessor: Received message:", message.data)
val data = message.data
try {
when (data) {
is String -> {
when (data) {
"start_recording" -> {
port.postMessage(recordingBuffer)
if (recordingState == RecordingState.STOPPED) {
recordingState = RecordingState.WAITING_TO_START
recordingSample = 0
}
}
else ->
if (data.startsWith("set_channel")) {
val parts = data.split('\n')
if (parts.size == 2) {
midiChannel = parts[1].toInt()
println("Setting channel: $midiChannel")
}
}
}
}
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)
}
else ->
console.error("Don't kow how to handle message", message)
}
} catch (e: Exception) {
console.log(e.message, e)
}
}
private fun playMidi(bytes: Int32Array) {
console.log("playMidi", bytes)
if (bytes.length > 0) {
var cmdByte = bytes[0]
val channelCmd = ((cmdByte shr 4) and 0xf) != 0xf0
val channel = cmdByte and 0xf
println("Channel cmd: $channelCmd")
if (channelCmd && channel != midiChannel) {
console.log("Wrong channel", midiChannel, bytes)
return
}
cmdByte = cmdByte and 0xf0
//console.log("Received", bytes)
when (cmdByte) {
0x90 -> {
if (bytes.length == 3) {
val note = bytes[1]
val velocity = bytes[2]
if (velocity > 0) {
noteOn(note, velocity)
} else {
noteOff(note)
}
}
}
0x80 -> {
if (bytes.length >= 2) {
val note = bytes[1]
noteOff(note)
}
}
0xb0 -> {
if (bytes.length == 3) {
val knob = bytes[1]
val value = bytes[2]
when (knob) {
7 -> {
volume = value / 127f
}
0x47 -> {
damping = 0.8 + value / 127.0
}
}
}
}
}
}
}
private fun noteOn(midiNote: Int, velocity: Int) {
val note = Note.fromMidi(midiNote)
for (i in 0 until POLYPHONICS) {
if (strings[i].currentNote == note) {
strings[i].pluck(note, velocity / 127.0)
strings[i].damping = damping
return
}
}
for (i in 0 until POLYPHONICS) {
if (strings[i].available) {
strings[i].pluck(note, velocity / 127.0)
strings[i].damping = damping
break
}
}
}
private fun noteOff(midiNote: Int) {
val note = Note.fromMidi(midiNote)
for (i in 0 until POLYPHONICS) {
if (strings[i].currentNote.ordinal == note.ordinal) {
strings[i].available = true
strings[i].damping = damping * 0.9
}
}
}
override fun process(
inputs: Array<Array<Float32Array>>,
outputs: Array<Array<Float32Array>>,
parameters: dynamic
): Boolean {
val samples = outputs[0][0].length
val left = outputs[0][0]
val right = outputs[0][1]
var lowestNote = 200
for (string in strings) {
if (string.available) {
lowestNote = min(lowestNote, string.currentNote.ordinal)
}
}
if (lowestNote == 200 && recordingState == RecordingState.WAITING_TO_START) {
recordingState = RecordingState.RECORDING
recordingSample = 0
recordingStart = 0
}
for (string in strings) {
for (i in 0 until samples) {
val waveValue: Float = string.tick().toFloat()
left[i] = left[i] + waveValue * volume
right[i] = right[i] + waveValue * volume
if (lowestNote == string.currentNote.ordinal && recordingState == RecordingState.WAITING_TO_START && string.index == 0) {
recordingState = RecordingState.RECORDING
recordingSample = 0
recordingStart = 0
}
}
}
if (recordingState == RecordingState.RECORDING) {
for (i in recordingStart until samples) {
recordingBuffer[recordingSample] = (left[i] + right[i]) / 2f
if (recordingSample < recordingBuffer.length - 1) {
recordingSample++
} else {
recordingState = RecordingState.STOPPED
}
}
recordingStart = 0
}
return true
}
}
fun main() {
registerProcessor("vst-string-processor", VstStringProcessor::class.js)
println("VstStringProcessor registered!")
}