Skip to content

Commit abf0d25

Browse files
SiriusZaelxian
authored andcommitted
Add JS MIDI support
1 parent 0f29a17 commit abf0d25

File tree

22 files changed

+642
-64
lines changed

22 files changed

+642
-64
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package baaahs.plugin.midi
2+
3+
import baaahs.ShowPlayer
4+
import baaahs.app.ui.dialog.DialogPanel
5+
import baaahs.app.ui.editor.EditableManager
6+
import baaahs.camelize
7+
import baaahs.randomId
8+
import baaahs.show.Control
9+
import baaahs.show.live.ControlProps
10+
import baaahs.show.live.OpenContext
11+
import baaahs.show.live.OpenControl
12+
import baaahs.show.mutable.MutableControl
13+
import baaahs.show.mutable.MutableShow
14+
import baaahs.show.mutable.ShowBuilder
15+
import baaahs.ui.View
16+
import kotlinx.serialization.SerialName
17+
import kotlinx.serialization.Serializable
18+
import kotlinx.serialization.Transient
19+
import kotlinx.serialization.json.JsonElement
20+
21+
@Serializable
22+
@SerialName("baaahs.Midi:Midi")
23+
data class MidiControl(@Transient private val `_`: Boolean = false) : Control {
24+
override val title: String get() = "Midi"
25+
26+
override fun createMutable(mutableShow: MutableShow): MutableControl {
27+
return MutableMidiControl()
28+
}
29+
30+
override fun open(id: String, openContext: OpenContext, showPlayer: ShowPlayer): OpenControl {
31+
return OpenMidiControl(id)
32+
}
33+
}
34+
35+
class MutableMidiControl : MutableControl {
36+
override val title: String get() = "Midi"
37+
38+
override var asBuiltId: String? = null
39+
40+
override fun getEditorPanels(editableManager: EditableManager<*>): List<DialogPanel> {
41+
return emptyList()
42+
}
43+
44+
override fun buildControl(showBuilder: ShowBuilder): MidiControl {
45+
return MidiControl()
46+
}
47+
48+
override fun previewOpen(): OpenControl {
49+
return OpenMidiControl(randomId(title.camelize()))
50+
}
51+
}
52+
53+
class OpenMidiControl(
54+
override val id: String
55+
) : OpenControl {
56+
override fun getState(): Map<String, JsonElement>? = null
57+
58+
override fun applyState(state: Map<String, JsonElement>) {}
59+
60+
override fun toNewMutable(mutableShow: MutableShow): MutableControl {
61+
return MutableMidiControl()
62+
}
63+
64+
override fun getView(controlProps: ControlProps): View =
65+
midiViews.forControl(this, controlProps)
66+
}

src/commonMain/kotlin/baaahs/plugin/midi/MidiPlugin.kt

+2-7
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,7 @@ class MidiPlugin internal constructor(
137137
MidiBridgePlugin(createServerMidiSource(pluginContext), pluginContext)
138138

139139
override fun getServerPlugin(pluginContext: PluginContext, bridgeClient: BridgeClient) =
140-
MidiPlugin(
141-
PubSubPublisher(
142-
PubSubSubscriber(bridgeClient.pubSub, simulatorDefaultMidi),
143-
pluginContext
144-
)
145-
)
140+
openForServer(pluginContext, object : Args { override val enableMidi: Boolean get() = true })
146141

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

154-
/** Copy beat data from [midiSource] to a bridge PubSub channel. */
149+
/** Copy midi data from [midiSource] to a bridge PubSub channel. */
155150
class MidiBridgePlugin(
156151
private val midiSource: MidiSource,
157152
pluginContext: PluginContext
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package baaahs.plugin.midi
2+
3+
import baaahs.show.live.ControlProps
4+
import baaahs.ui.View
5+
6+
interface MidiViews {
7+
fun forControl(openButtonControl: OpenMidiControl, controlProps: ControlProps): View
8+
}
9+
10+
val midiViews by lazy { getMidiViews() }
11+
expect fun getMidiViews(): MidiViews

src/commonMain/resources/templates/shows/Eve Rafters.sparkle

-3
Original file line numberDiff line numberDiff line change
@@ -2058,9 +2058,6 @@
20582058
"beatLink": {
20592059
"type": "baaahs.BeatLink:BeatLink"
20602060
},
2061-
"midi": {
2062-
"type": "baaahs.Midi:Midi"
2063-
},
20642061
"soundAnalysis": {
20652062
"type": "baaahs.SoundAnalysis:SoundAnalysis"
20662063
},

src/jsMain/kotlin/baaahs/JsMain.kt

+8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package baaahs
33
import baaahs.app.ui.PatchEditorApp
44
import baaahs.client.WebClient
55
import baaahs.di.*
6+
import baaahs.midi.MIDIUi
67
import baaahs.monitor.MonitorUi
78
import baaahs.net.BrowserNetwork
89
import baaahs.scene.SceneMonitor
@@ -84,6 +85,11 @@ private fun launchUi(appName: String?) {
8485
koin.createScope<WebClient>().get<WebClient>()
8586
}
8687

88+
"MIDIUi" -> {
89+
koin.loadModules(listOf(JsMidiWebClientModule().getModule()))
90+
koin.createScope<MIDIUi>().get<MIDIUi>()
91+
}
92+
8793
"PatchEditor" -> {
8894
koin.loadModules(listOf(JsUiWebClientModule().getModule()))
8995
koin.createScope<WebClient>().get<PatchEditorApp>()
@@ -122,6 +128,7 @@ private fun launchSimulator(
122128
JsSimPinkyModule(sceneMonitor, pinkySettings, Dispatchers.Main, simMappingManager).getModule(),
123129
JsUiWebClientModule().getModule(),
124130
JsMonitorWebClientModule().getModule(),
131+
JsMidiWebClientModule().getModule(),
125132
)
126133
}.koin
127134

@@ -130,6 +137,7 @@ private fun launchSimulator(
130137
val hostedWebApp = when (val app = queryParams["app"] ?: "UI") {
131138
"Monitor" -> simulator.createMonitorApp()
132139
"UI" -> simulator.createWebClientApp()
140+
"MIDIUi" -> simulator.createMIDIApp()
133141
else -> throw UnsupportedOperationException("unknown app $app")
134142
}
135143
hostedWebApp.onLaunch()

src/jsMain/kotlin/baaahs/SheepSimulator.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import baaahs.controller.ControllersManager
55
import baaahs.monitor.MonitorUi
66
import baaahs.sim.*
77
import baaahs.sim.ui.LaunchItem
8+
import baaahs.midi.MIDIUi
89
import baaahs.visualizer.Visualizer
910
import kotlinx.coroutines.coroutineScope
1011
import kotlinx.coroutines.launch
@@ -42,6 +43,7 @@ class SheepSimulator(
4243

4344
fun createWebClientApp(): WebClient = getKoin().createScope<WebClient>().get()
4445
fun createMonitorApp(): MonitorUi = getKoin().createScope<MonitorUi>().get()
46+
fun createMIDIApp(): MIDIUi = getKoin().createScope<MIDIUi>().get()
4547

4648
private suspend fun cleanUpBrowserStorage() {
4749
val fs = BrowserSandboxFs("BrowserSandboxFs")
@@ -78,7 +80,8 @@ class SheepSimulator(
7880
val launchItems: List<LaunchItem> =
7981
listOf(
8082
launchItem("Web UI") { createWebClientApp() },
81-
launchItem("Monitor") { createMonitorApp() }
83+
launchItem("Monitor") { createMonitorApp() },
84+
launchItem("MIDIUi") { createMIDIApp() }
8285
)
8386
}
8487
}

src/jsMain/kotlin/baaahs/di/JsModules.kt

+27-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import baaahs.gl.Toolchain
1414
import baaahs.io.PubSubRemoteFsClientBackend
1515
import baaahs.io.RemoteFsSerializer
1616
import baaahs.mapper.JsMapper
17+
import baaahs.midi.MIDIUi
1718
import baaahs.midi.MidiDevices
1819
import baaahs.midi.RemoteMidiDevices
1920
import baaahs.monitor.MonitorUi
@@ -93,8 +94,8 @@ open class JsUiWebClientModule : WebClientModule() {
9394
}
9495
}
9596
}
96-
9797
class JsMonitorWebClientModule : KModule {
98+
9899
override fun getModule(): Module = module {
99100
scope<MonitorUi> {
100101
scoped { get<Network>().link("monitor") }
@@ -123,6 +124,31 @@ class JsMonitorWebClientModule : KModule {
123124
}
124125
}
125126

127+
private fun Scope.pinkyAddress(): Network.Address =
128+
get(named(WebClientModule.Qualifier.PinkyAddress))
129+
}
130+
131+
class JsMidiWebClientModule : KModule {
132+
133+
override fun getModule(): Module = module {
134+
scope<MIDIUi> {
135+
scoped { get<Network>().link("midi") }
136+
scoped { PluginContext(get(), get()) }
137+
scoped { PubSub.Client(get(), pinkyAddress(), Ports.PINKY_UI_TCP) }
138+
scoped<PubSub.Endpoint> { get<PubSub.Client>() }
139+
scoped { Plugins.buildForClient(get(), get(named(PluginsModule.Qualifier.ActivePlugins))) }
140+
scoped<Plugins> { get<ClientPlugins>() }
141+
scoped<RemoteFsSerializer> { PubSubRemoteFsClientBackend(get()) }
142+
scoped { SceneManager(get(), get(), get(), get(), get(), get()) }
143+
scoped { SceneMonitor() }
144+
scoped<SceneProvider> { get<SceneMonitor>() }
145+
scoped { FileDialog() }
146+
scoped<IFileDialog> { get<FileDialog>() }
147+
scoped { Notifier(get()) }
148+
scoped { MIDIUi() }
149+
}
150+
}
151+
126152
private fun Scope.pinkyAddress(): Network.Address =
127153
get(named(WebClientModule.Qualifier.PinkyAddress))
128154
}

src/jsMain/kotlin/baaahs/di/JsSimulatorModules.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import baaahs.io.Fs
1111
import baaahs.mapper.PinkyMapperHandlers
1212
import baaahs.mapping.MappingManager
1313
import baaahs.midi.MidiDevices
14-
import baaahs.midi.NullMidiDevices
14+
import baaahs.midi.BrowserMidiDevices
1515
import baaahs.net.BrowserNetwork
1616
import baaahs.net.Network
1717
import baaahs.plugin.Plugins
@@ -101,7 +101,7 @@ class JsSimPinkyModule(
101101
override val Scope.dmxDriver: Dmx.Driver
102102
get() = SimDmxDriver(get(named("Fallback")))
103103
override val Scope.midiDevices: MidiDevices
104-
get() = NullMidiDevices()
104+
get() = BrowserMidiDevices()
105105
override val Scope.pinkySettings: PinkySettings
106106
get() = pinkySettings_
107107
override val Scope.sceneMonitor: SceneMonitor

src/jsMain/kotlin/baaahs/midi/BrowserMidiDevices.kt

+42-35
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,60 @@
11
package baaahs.midi
22

33
import baaahs.util.Logger
4+
import external.midi.MIDIAccess
5+
import external.midi.MIDIInput
6+
import web.navigator.navigator
47

58
class BrowserMidiDevices : MidiDevices {
6-
// private val midiAccess = window.navigator.requestMIDIAccess()
7-
private val transmitters = mutableMapOf<String, MidiTransmitter>()
9+
private lateinit var midiAccess: MIDIAccess;
10+
11+
init {
12+
navigator.asDynamic().requestMIDIAccess().then {midiAccessResult: MIDIAccess ->
13+
midiAccess = midiAccessResult
14+
midiAccessResult
15+
}
16+
}
817

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

21+
1222
return buildList {
13-
// MidiSystem.getMidiDeviceInfo().mapNotNull { info ->
14-
// println("${info.name}: ${info.javaClass.simpleName}\n DESC=${info.description}\n VENDOR=${info.vendor}\n VERSION=${info.version}")
15-
// val device = MidiSystem.getMidiDevice(info)
16-
// val id = info.name.let {
23+
// midiAccess.inputs.forEach { inputEntry ->
24+
// val input = inputEntry.value
25+
// println("${input.name}: DESC=${input.description}\n VENDOR=${input.manufacturer}\n VERSION=${input.version}")
26+
// val id = input.name.let {
1727
// val idNum = ids.getOrPut(it) { Counter() }.count()
1828
// if (idNum == 0) it else "it #$idNum"
1929
// }
2030
//
21-
// val maxTransmitters = device.maxTransmitters
22-
// if (maxTransmitters == -1 || maxTransmitters > 0) {
23-
// add(JvmMidiTransmitterTransmitter(id, device))
24-
// }
31+
// add(JsMidiTransmitterTransmitter(id, input))
2532
// }
2633
}
2734
}
2835

29-
// class JvmMidiTransmitterTransmitter(
30-
// override val id: String,
31-
// private val device: MidiDevice
32-
// ) : MidiTransmitter {
33-
// override val name: String
34-
// get() = device.deviceInfo.name
35-
// override val vendor: String
36-
// get() = device.deviceInfo.vendor
37-
// override val description: String
38-
// get() = device.deviceInfo.description
39-
// override val version: String
40-
// get() = device.deviceInfo.version
41-
//
36+
class JsMidiTransmitterTransmitter(
37+
override val id: String,
38+
private val input: MIDIInput
39+
) : MidiTransmitter {
40+
override val name: String
41+
get() = input.name
42+
override val vendor: String
43+
get() = input.manufacturer
44+
override val description: String
45+
get() = ""
46+
override val version: String
47+
get() = input.version
48+
4249
// private var transmitter: Transmitter? = null
43-
//
44-
// override fun listen(callback: (MidiMessage) -> Unit) {
50+
51+
override fun listen(callback: (MidiMessage) -> Unit) {
4552
// if (transmitter == null) {
4653
// transmitter = run {
47-
// device.transmitter.also { device.open() }
54+
// input.open()
4855
// }
4956
// }
50-
//
57+
5158
// transmitter!!.receiver = object : Receiver {
5259
// override fun close() {
5360
// logger.debug { "$name closed." }
@@ -67,16 +74,16 @@ class BrowserMidiDevices : MidiDevices {
6774
// }
6875
// }
6976
// }
70-
// }
71-
//
72-
// override fun close() {
77+
}
78+
79+
override fun close() {
7380
// if (transmitter != null) {
74-
// device.close()
81+
// input.close()
7582
// }
76-
// logger.debug { "$name closed." }
77-
// }
78-
//
79-
// }
83+
logger.debug { "$name closed." }
84+
}
85+
86+
}
8087

8188
private class Counter(var value: Int = 0) {
8289
fun count(): Int = value++

0 commit comments

Comments
 (0)