111 lines
2.5 KiB
Kotlin
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())
|
|
}
|
|
}
|
|
|
|
}
|