@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() 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()) } } }