Skip to content

Commit

Permalink
Add JS MIDI support
Browse files Browse the repository at this point in the history
  • Loading branch information
SiriusZael authored and xian committed Aug 16, 2023
1 parent 0f29a17 commit abf0d25
Show file tree
Hide file tree
Showing 22 changed files with 642 additions and 64 deletions.
66 changes: 66 additions & 0 deletions src/commonMain/kotlin/baaahs/plugin/midi/MidiControl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package baaahs.plugin.midi

import baaahs.ShowPlayer
import baaahs.app.ui.dialog.DialogPanel
import baaahs.app.ui.editor.EditableManager
import baaahs.camelize
import baaahs.randomId
import baaahs.show.Control
import baaahs.show.live.ControlProps
import baaahs.show.live.OpenContext
import baaahs.show.live.OpenControl
import baaahs.show.mutable.MutableControl
import baaahs.show.mutable.MutableShow
import baaahs.show.mutable.ShowBuilder
import baaahs.ui.View
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.json.JsonElement

@Serializable
@SerialName("baaahs.Midi:Midi")
data class MidiControl(@Transient private val `_`: Boolean = false) : Control {
override val title: String get() = "Midi"

override fun createMutable(mutableShow: MutableShow): MutableControl {
return MutableMidiControl()
}

override fun open(id: String, openContext: OpenContext, showPlayer: ShowPlayer): OpenControl {
return OpenMidiControl(id)
}
}

class MutableMidiControl : MutableControl {
override val title: String get() = "Midi"

override var asBuiltId: String? = null

override fun getEditorPanels(editableManager: EditableManager<*>): List<DialogPanel> {
return emptyList()
}

override fun buildControl(showBuilder: ShowBuilder): MidiControl {
return MidiControl()
}

override fun previewOpen(): OpenControl {
return OpenMidiControl(randomId(title.camelize()))
}
}

class OpenMidiControl(
override val id: String
) : OpenControl {
override fun getState(): Map<String, JsonElement>? = null

override fun applyState(state: Map<String, JsonElement>) {}

override fun toNewMutable(mutableShow: MutableShow): MutableControl {
return MutableMidiControl()
}

override fun getView(controlProps: ControlProps): View =
midiViews.forControl(this, controlProps)
}
9 changes: 2 additions & 7 deletions src/commonMain/kotlin/baaahs/plugin/midi/MidiPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,7 @@ class MidiPlugin internal constructor(
MidiBridgePlugin(createServerMidiSource(pluginContext), pluginContext)

override fun getServerPlugin(pluginContext: PluginContext, bridgeClient: BridgeClient) =
MidiPlugin(
PubSubPublisher(
PubSubSubscriber(bridgeClient.pubSub, simulatorDefaultMidi),
pluginContext
)
)
openForServer(pluginContext, object : Args { override val enableMidi: Boolean get() = true })

override fun getClientPlugin(pluginContext: PluginContext): OpenClientPlugin =
openForClient(pluginContext)
Expand All @@ -151,7 +146,7 @@ class MidiPlugin internal constructor(
private val midiDataTopic = PubSub.Topic("plugins/$id/midiData", MidiData.serializer())
}

/** Copy beat data from [midiSource] to a bridge PubSub channel. */
/** Copy midi data from [midiSource] to a bridge PubSub channel. */
class MidiBridgePlugin(
private val midiSource: MidiSource,
pluginContext: PluginContext
Expand Down
11 changes: 11 additions & 0 deletions src/commonMain/kotlin/baaahs/plugin/midi/MidiViews.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package baaahs.plugin.midi

import baaahs.show.live.ControlProps
import baaahs.ui.View

interface MidiViews {
fun forControl(openButtonControl: OpenMidiControl, controlProps: ControlProps): View
}

val midiViews by lazy { getMidiViews() }
expect fun getMidiViews(): MidiViews
3 changes: 0 additions & 3 deletions src/commonMain/resources/templates/shows/Eve Rafters.sparkle
Original file line number Diff line number Diff line change
Expand Up @@ -2058,9 +2058,6 @@
"beatLink": {
"type": "baaahs.BeatLink:BeatLink"
},
"midi": {
"type": "baaahs.Midi:Midi"
},
"soundAnalysis": {
"type": "baaahs.SoundAnalysis:SoundAnalysis"
},
Expand Down
8 changes: 8 additions & 0 deletions src/jsMain/kotlin/baaahs/JsMain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package baaahs
import baaahs.app.ui.PatchEditorApp
import baaahs.client.WebClient
import baaahs.di.*
import baaahs.midi.MIDIUi
import baaahs.monitor.MonitorUi
import baaahs.net.BrowserNetwork
import baaahs.scene.SceneMonitor
Expand Down Expand Up @@ -84,6 +85,11 @@ private fun launchUi(appName: String?) {
koin.createScope<WebClient>().get<WebClient>()
}

"MIDIUi" -> {
koin.loadModules(listOf(JsMidiWebClientModule().getModule()))
koin.createScope<MIDIUi>().get<MIDIUi>()
}

"PatchEditor" -> {
koin.loadModules(listOf(JsUiWebClientModule().getModule()))
koin.createScope<WebClient>().get<PatchEditorApp>()
Expand Down Expand Up @@ -122,6 +128,7 @@ private fun launchSimulator(
JsSimPinkyModule(sceneMonitor, pinkySettings, Dispatchers.Main, simMappingManager).getModule(),
JsUiWebClientModule().getModule(),
JsMonitorWebClientModule().getModule(),
JsMidiWebClientModule().getModule(),
)
}.koin

Expand All @@ -130,6 +137,7 @@ private fun launchSimulator(
val hostedWebApp = when (val app = queryParams["app"] ?: "UI") {
"Monitor" -> simulator.createMonitorApp()
"UI" -> simulator.createWebClientApp()
"MIDIUi" -> simulator.createMIDIApp()
else -> throw UnsupportedOperationException("unknown app $app")
}
hostedWebApp.onLaunch()
Expand Down
5 changes: 4 additions & 1 deletion src/jsMain/kotlin/baaahs/SheepSimulator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import baaahs.controller.ControllersManager
import baaahs.monitor.MonitorUi
import baaahs.sim.*
import baaahs.sim.ui.LaunchItem
import baaahs.midi.MIDIUi
import baaahs.visualizer.Visualizer
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -42,6 +43,7 @@ class SheepSimulator(

fun createWebClientApp(): WebClient = getKoin().createScope<WebClient>().get()
fun createMonitorApp(): MonitorUi = getKoin().createScope<MonitorUi>().get()
fun createMIDIApp(): MIDIUi = getKoin().createScope<MIDIUi>().get()

private suspend fun cleanUpBrowserStorage() {
val fs = BrowserSandboxFs("BrowserSandboxFs")
Expand Down Expand Up @@ -78,7 +80,8 @@ class SheepSimulator(
val launchItems: List<LaunchItem> =
listOf(
launchItem("Web UI") { createWebClientApp() },
launchItem("Monitor") { createMonitorApp() }
launchItem("Monitor") { createMonitorApp() },
launchItem("MIDIUi") { createMIDIApp() }
)
}
}
28 changes: 27 additions & 1 deletion src/jsMain/kotlin/baaahs/di/JsModules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import baaahs.gl.Toolchain
import baaahs.io.PubSubRemoteFsClientBackend
import baaahs.io.RemoteFsSerializer
import baaahs.mapper.JsMapper
import baaahs.midi.MIDIUi
import baaahs.midi.MidiDevices
import baaahs.midi.RemoteMidiDevices
import baaahs.monitor.MonitorUi
Expand Down Expand Up @@ -93,8 +94,8 @@ open class JsUiWebClientModule : WebClientModule() {
}
}
}

class JsMonitorWebClientModule : KModule {

override fun getModule(): Module = module {
scope<MonitorUi> {
scoped { get<Network>().link("monitor") }
Expand Down Expand Up @@ -123,6 +124,31 @@ class JsMonitorWebClientModule : KModule {
}
}

private fun Scope.pinkyAddress(): Network.Address =
get(named(WebClientModule.Qualifier.PinkyAddress))
}

class JsMidiWebClientModule : KModule {

override fun getModule(): Module = module {
scope<MIDIUi> {
scoped { get<Network>().link("midi") }
scoped { PluginContext(get(), get()) }
scoped { PubSub.Client(get(), pinkyAddress(), Ports.PINKY_UI_TCP) }
scoped<PubSub.Endpoint> { get<PubSub.Client>() }
scoped { Plugins.buildForClient(get(), get(named(PluginsModule.Qualifier.ActivePlugins))) }
scoped<Plugins> { get<ClientPlugins>() }
scoped<RemoteFsSerializer> { PubSubRemoteFsClientBackend(get()) }
scoped { SceneManager(get(), get(), get(), get(), get(), get()) }
scoped { SceneMonitor() }
scoped<SceneProvider> { get<SceneMonitor>() }
scoped { FileDialog() }
scoped<IFileDialog> { get<FileDialog>() }
scoped { Notifier(get()) }
scoped { MIDIUi() }
}
}

private fun Scope.pinkyAddress(): Network.Address =
get(named(WebClientModule.Qualifier.PinkyAddress))
}
4 changes: 2 additions & 2 deletions src/jsMain/kotlin/baaahs/di/JsSimulatorModules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import baaahs.io.Fs
import baaahs.mapper.PinkyMapperHandlers
import baaahs.mapping.MappingManager
import baaahs.midi.MidiDevices
import baaahs.midi.NullMidiDevices
import baaahs.midi.BrowserMidiDevices
import baaahs.net.BrowserNetwork
import baaahs.net.Network
import baaahs.plugin.Plugins
Expand Down Expand Up @@ -101,7 +101,7 @@ class JsSimPinkyModule(
override val Scope.dmxDriver: Dmx.Driver
get() = SimDmxDriver(get(named("Fallback")))
override val Scope.midiDevices: MidiDevices
get() = NullMidiDevices()
get() = BrowserMidiDevices()
override val Scope.pinkySettings: PinkySettings
get() = pinkySettings_
override val Scope.sceneMonitor: SceneMonitor
Expand Down
77 changes: 42 additions & 35 deletions src/jsMain/kotlin/baaahs/midi/BrowserMidiDevices.kt
Original file line number Diff line number Diff line change
@@ -1,53 +1,60 @@
package baaahs.midi

import baaahs.util.Logger
import external.midi.MIDIAccess
import external.midi.MIDIInput
import web.navigator.navigator

class BrowserMidiDevices : MidiDevices {
// private val midiAccess = window.navigator.requestMIDIAccess()
private val transmitters = mutableMapOf<String, MidiTransmitter>()
private lateinit var midiAccess: MIDIAccess;

init {
navigator.asDynamic().requestMIDIAccess().then {midiAccessResult: MIDIAccess ->
midiAccess = midiAccessResult
midiAccessResult
}
}

override suspend fun listTransmitters(): List<MidiTransmitter> {
val ids = mutableMapOf<String, Counter>()


return buildList {
// MidiSystem.getMidiDeviceInfo().mapNotNull { info ->
// println("${info.name}: ${info.javaClass.simpleName}\n DESC=${info.description}\n VENDOR=${info.vendor}\n VERSION=${info.version}")
// val device = MidiSystem.getMidiDevice(info)
// val id = info.name.let {
// midiAccess.inputs.forEach { inputEntry ->
// val input = inputEntry.value
// println("${input.name}: DESC=${input.description}\n VENDOR=${input.manufacturer}\n VERSION=${input.version}")
// val id = input.name.let {
// val idNum = ids.getOrPut(it) { Counter() }.count()
// if (idNum == 0) it else "it #$idNum"
// }
//
// val maxTransmitters = device.maxTransmitters
// if (maxTransmitters == -1 || maxTransmitters > 0) {
// add(JvmMidiTransmitterTransmitter(id, device))
// }
// add(JsMidiTransmitterTransmitter(id, input))
// }
}
}

// class JvmMidiTransmitterTransmitter(
// override val id: String,
// private val device: MidiDevice
// ) : MidiTransmitter {
// override val name: String
// get() = device.deviceInfo.name
// override val vendor: String
// get() = device.deviceInfo.vendor
// override val description: String
// get() = device.deviceInfo.description
// override val version: String
// get() = device.deviceInfo.version
//
class JsMidiTransmitterTransmitter(
override val id: String,
private val input: MIDIInput
) : MidiTransmitter {
override val name: String
get() = input.name
override val vendor: String
get() = input.manufacturer
override val description: String
get() = ""
override val version: String
get() = input.version

// private var transmitter: Transmitter? = null
//
// override fun listen(callback: (MidiMessage) -> Unit) {

override fun listen(callback: (MidiMessage) -> Unit) {
// if (transmitter == null) {
// transmitter = run {
// device.transmitter.also { device.open() }
// input.open()
// }
// }
//

// transmitter!!.receiver = object : Receiver {
// override fun close() {
// logger.debug { "$name closed." }
Expand All @@ -67,16 +74,16 @@ class BrowserMidiDevices : MidiDevices {
// }
// }
// }
// }
//
// override fun close() {
}

override fun close() {
// if (transmitter != null) {
// device.close()
// input.close()
// }
// logger.debug { "$name closed." }
// }
//
// }
logger.debug { "$name closed." }
}

}

private class Counter(var value: Int = 0) {
fun count(): Int = value++
Expand Down
Loading

0 comments on commit abf0d25

Please sign in to comment.