Files
vst-string/src/jsMain/kotlin/nl/astraeus/vst/chip/midi/Broadcaster.kt
2024-06-21 21:32:08 +02:00

111 lines
2.5 KiB
Kotlin

@file:OptIn(ExperimentalJsExport::class)
package nl.astraeus.vst.chip.midi
import kotlinx.browser.window
import org.khronos.webgl.Uint8Array
import org.w3c.dom.BroadcastChannel
import org.w3c.dom.MessageEvent
import kotlin.math.min
@JsExport
enum class MessageType {
SYNC,
MIDI
}
@JsExport
class SyncMessage(
@JsName("timeOrigin")
val timeOrigin: Double = window.performance.asDynamic().timeOrigin,
@JsName("now")
val now: Double = window.performance.now()
) {
@JsName("type")
val type: String = MessageType.SYNC.name
}
// time -> syncOrigin
// receive syn message
// syncOrigin = my timeOrigin - sync.timeOrigin
// - sync.timeOrigin = 50
// - my.timeOrigin = 100
// - syncOrigin = -50
// - sync.timeOrigin = 49
// - my.timeOrigin = 100
// - syncOrigin = update to -51
@JsExport
class MidiMessage(
@JsName("data")
val data: Uint8Array,
@JsName("timestamp")
val timestamp: dynamic = null,
@JsName("timeOrigin")
val timeOrigin: dynamic = window.performance.asDynamic().timeOrigin
) {
@JsName("type")
val type = MessageType.MIDI.name
}
object Sync {
var syncOrigin = 0.0
fun update(sync: SyncMessage) {
syncOrigin = min(syncOrigin, window.performance.asDynamic().timeOrigin - sync.timeOrigin)
}
fun now(): Double = window.performance.now() + syncOrigin
}
object Broadcaster {
val channels = mutableMapOf<Int, BroadcastChannel>()
fun getChannel(channel: Int): BroadcastChannel = channels.getOrPut(channel) {
println("Opening broadcast channel $channel")
val bcChannel = BroadcastChannel("audio-worklet-$channel")
bcChannel.onmessage = { event ->
onMessage(channel, event)
}
bcChannel
}
private fun onMessage(channel: Int, event: MessageEvent) {
val data: dynamic = event.data.asDynamic()
console.log(
"Received broadcast message on channel $channel",
event
)
if (data.type == MessageType.SYNC.name) {
val syncMessage = SyncMessage(
data.timeOrigin,
data.now
)
Sync.update(syncMessage)
} else {
console.log(
"Received broadcast message on channel $channel",
event,
window.performance,
window.performance.now() - event.timeStamp.toDouble()
)
}
}
fun send(channel: Int, message: Any) {
console.log("Sending broadcast message on channel $channel:", message)
getChannel(channel).postMessage(message)
}
fun sync() {
for (channel in channels.values) {
channel.postMessage(SyncMessage())
}
}
}