Skip to content

Commit

Permalink
WIP: midi device simulator
Browse files Browse the repository at this point in the history
  • Loading branch information
xian committed Jul 23, 2024
1 parent 5e1a156 commit c7ec7a8
Show file tree
Hide file tree
Showing 19 changed files with 267 additions and 117 deletions.
25 changes: 24 additions & 1 deletion src/commonMain/kotlin/baaahs/ShowPlayer.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package baaahs

import baaahs.gl.GlContext
import baaahs.gl.Toolchain
import baaahs.gl.data.EngineFeedContext
import baaahs.gl.data.FeedContext
import baaahs.gl.data.ProgramFeedContext
import baaahs.gl.glsl.GlslProgram
import baaahs.gl.shader.OpenShader
import baaahs.gl.withCache
import baaahs.plugin.Plugins
Expand All @@ -11,6 +15,9 @@ import baaahs.show.live.OpenShow
import baaahs.show.live.ShowOpener
import baaahs.ui.addObserver
import baaahs.util.Clock
import baaahs.util.Logger
import baaahs.util.RefCounted
import baaahs.util.RefCounter

interface ShowPlayer {
fun <T : Gadget> registerGadget(id: String, gadget: T, controlledFeed: Feed? = null)
Expand Down Expand Up @@ -43,7 +50,19 @@ abstract class BaseShowPlayer(
// TODO: This is another reference to feeds, so we should .use() it... but then we'll never release them!
// TODO: Also, it could conceivably be handed out after it's had onRelease() called. How should we handle this?
return feeds.getOrPut(feed) {
feed.open(this, id)
try {
feed.open(this, id)
} catch (e: Error) {
logger.error(e) { "Can't open feed $id" }

return object : FeedContext, RefCounted by RefCounter() {
override fun bind(gl: GlContext): EngineFeedContext = object : EngineFeedContext {
override fun bind(glslProgram: GlslProgram): ProgramFeedContext {
return object : ProgramFeedContext {}
}
}
}
}
}
}

Expand Down Expand Up @@ -76,4 +95,8 @@ abstract class BaseShowPlayer(
if (!openShader.inUse()) shaders.remove(shader)
}
}

companion object {
private val logger = Logger<BaseShowPlayer>()
}
}
2 changes: 2 additions & 0 deletions src/commonMain/kotlin/baaahs/di/Modules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import baaahs.scene.SceneProvider
import baaahs.sim.FakeDmxUniverse
import baaahs.sim.FakeFs
import baaahs.sim.FakeNetwork
import baaahs.sim.SimulatorSettingsManager
import baaahs.sm.brain.BrainManager
import baaahs.sm.brain.FirmwareDaddy
import baaahs.sm.brain.ProdBrainSimulator
Expand Down Expand Up @@ -202,6 +203,7 @@ interface SimulatorModule : KModule {
single(named(Qualifier.MapperFs)) { FakeFs("Temporary Mapping Files") }
single<Fs>(named(Qualifier.MapperFs)) { get<FakeFs>(named(Qualifier.MapperFs)) }
single(named(WebClientModule.Qualifier.PinkyAddress)) { get<Network.Link>(named(Qualifier.PinkyLink)).myAddress }
single { SimulatorSettingsManager(get(named(Qualifier.PinkyFs))) }
}

enum class Qualifier {
Expand Down
11 changes: 10 additions & 1 deletion src/commonMain/kotlin/baaahs/plugin/OpenPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import baaahs.show.FeedBuilder
import baaahs.show.mutable.MutableControl
import baaahs.show.mutable.MutableShow
import baaahs.sim.BridgeClient
import baaahs.sim.SimulatorSettingsManager
import baaahs.ui.Icon
import baaahs.util.Clock
import kotlinx.cli.ArgParser
Expand Down Expand Up @@ -99,6 +100,14 @@ interface OpenSimulatorPlugin {

/** This plugin is used on the client when running in the Simulator. */
fun getClientPlugin(pluginContext: PluginContext): OpenClientPlugin

fun getHardwareSimulators(): List<HardwareSimulator> = emptyList()
}

interface HardwareSimulator {
val title: String

suspend fun start() {}
}

class SerializerRegistrar<T : Any>(val klass: KClass<T>, val serializer: KSerializer<T>) {
Expand Down Expand Up @@ -149,7 +158,7 @@ interface Plugin<T> {
* client plugins via [#openForSimulator].
*/
interface SimulatorPlugin {
fun openForSimulator(): OpenSimulatorPlugin
fun openForSimulator(simulatorSettingsManager: SimulatorSettingsManager): OpenSimulatorPlugin
}

class PluginContext(
Expand Down
18 changes: 15 additions & 3 deletions src/commonMain/kotlin/baaahs/plugin/Plugins.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import baaahs.scene.MutableControllerConfig
import baaahs.show.*
import baaahs.show.mutable.MutableFeedPort
import baaahs.sim.BridgeClient
import baaahs.sim.SimulatorSettingsManager
import baaahs.sm.brain.BrainControllerConfig
import baaahs.sm.brain.BrainManager
import baaahs.sm.server.PinkyArgs
Expand Down Expand Up @@ -110,18 +111,20 @@ class ClientPlugins : Plugins {
}

class SimulatorPlugins(
simulatorSettingsManager: SimulatorSettingsManager,
private val bridgeClient: BridgeClient,
plugins: List<Plugin<*>>
) {
private val simulatorPlugins: List<OpenSimulatorPlugin>
private var pluginsToSimulatorPlugins: List<Pair<Plugin<*>, OpenSimulatorPlugin?>>
lateinit var hardwareSimulators: List<HardwareSimulator>

init {
val forSimulator = mutableListOf<OpenSimulatorPlugin>()

pluginsToSimulatorPlugins = plugins.map {
it as Plugin<Any>
it to (it as? SimulatorPlugin)?.openForSimulator()
it to (it as? SimulatorPlugin)?.openForSimulator(simulatorSettingsManager)
}
simulatorPlugins = forSimulator
}
Expand Down Expand Up @@ -150,6 +153,11 @@ class SimulatorPlugins(
},
pluginContext
)

fun getHardwareSimulators() =
pluginsToSimulatorPlugins.flatMap { (plugin, simulatorPlugin) ->
simulatorPlugin?.getHardwareSimulators() ?: emptyList()
}
}

sealed class Plugins(
Expand Down Expand Up @@ -436,8 +444,12 @@ sealed class Plugins(
fun buildForClient(pluginContext: PluginContext, plugins: List<Plugin<*>>): ClientPlugins =
ClientPlugins(pluginContext, listOf(CorePlugin) + plugins)

fun buildForSimulator(bridgeClient: BridgeClient, plugins: List<Plugin<*>>): SimulatorPlugins =
SimulatorPlugins(bridgeClient, listOf(CorePlugin) + plugins)
fun buildForSimulator(
simulatorSettingsManager: SimulatorSettingsManager,
bridgeClient: BridgeClient,
plugins: List<Plugin<*>>
): SimulatorPlugins =
SimulatorPlugins(simulatorSettingsManager, bridgeClient, listOf(CorePlugin) + plugins)

fun safe(pluginContext: PluginContext): Plugins =
SafePlugins(pluginContext, listOf(CorePlugin.openSafe(pluginContext)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import baaahs.PubSub
import baaahs.app.ui.CommonIcons
import baaahs.gl.patch.ContentType
import baaahs.plugin.*
import baaahs.show.Feed
import baaahs.show.FeedBuilder
import baaahs.sim.BridgeClient
import baaahs.sim.SimulatorSettingsManager
import baaahs.ui.Facade
import baaahs.ui.Observable
import baaahs.util.Logger
Expand Down Expand Up @@ -85,7 +84,7 @@ class BeatLinkPlugin internal constructor(
override fun openForClient(pluginContext: PluginContext): OpenClientPlugin =
BeatLinkPlugin(PubSubSubscriber(pluginContext.pubSub), pluginContext)

override fun openForSimulator(): OpenSimulatorPlugin =
override fun openForSimulator(simulatorSettingsManager: SimulatorSettingsManager): OpenSimulatorPlugin =
object : OpenSimulatorPlugin {
override fun getBridgePlugin(pluginContext: PluginContext): OpenBridgePlugin =
BeatLinkBridgePlugin(createServerBeatSource(pluginContext), pluginContext)
Expand Down
16 changes: 15 additions & 1 deletion src/commonMain/kotlin/baaahs/plugin/midi/MidiData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,26 @@ data class MidiData(


interface MidiSource : IObservable {
val name: String

fun getMidiData(): MidiData

object None : Observable(), MidiSource {
val none = MidiData(0, 0)
override val name = "None"

private val none = MidiData(0, 0)

override fun getMidiData(): MidiData = none
}
}

interface MidiSystem : IObservable {
val midiSources: List<MidiSource>

suspend fun start() {}

object None : Observable(), MidiSystem {
override val midiSources: List<MidiSource>
get() = emptyList()
}
}
119 changes: 60 additions & 59 deletions src/commonMain/kotlin/baaahs/plugin/midi/MidiPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package baaahs.plugin.midi

import baaahs.PubSub
import baaahs.ShowPlayer
import baaahs.gl.GlContext
import baaahs.gl.data.EngineFeedContext
Expand All @@ -14,19 +13,24 @@ import baaahs.plugin.*
import baaahs.show.Feed
import baaahs.show.FeedBuilder
import baaahs.sim.BridgeClient
import baaahs.sim.SimulatorSettingsManager
import baaahs.ui.Observable
import baaahs.ui.addObserver
import baaahs.util.Logger
import baaahs.util.RefCounted
import baaahs.util.RefCounter
import baaahs.util.globalLaunch
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.default
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

class MidiPlugin internal constructor(
internal val midiSource: MidiSource,
internal val midiSystem: MidiSystem,
) : OpenServerPlugin, OpenClientPlugin {
private val midiSource = midiSystem.midiSources.firstOrNull() ?: MidiSource.None

override val packageName: String = MidiPlugin.id
override val title: String = "Midi"

Expand Down Expand Up @@ -98,7 +102,6 @@ class MidiPlugin internal constructor(

interface Args {
val enableMidi: Boolean
val midiSource: MidiSource? get() = null
}

companion object : Plugin<Args>, SimulatorPlugin {
Expand All @@ -120,85 +123,83 @@ class MidiPlugin internal constructor(
override fun getArgs(parser: ArgParser): Args = ParserArgs(parser)

override fun openForServer(pluginContext: PluginContext, args: Args): OpenServerPlugin {
val midiSource = if (args.enableMidi) {
args.midiSource ?: createServerMidiSource(pluginContext)
} else MidiSource.None
return MidiPlugin(
PubSubPublisher(midiSource, pluginContext)
)
val midiSystem = if (args.enableMidi) {
createMidiSystem(pluginContext)
} else MidiSystem.None

return MidiPlugin(midiSystem)
}

override fun openForClient(pluginContext: PluginContext): OpenClientPlugin =
MidiPlugin(PubSubSubscriber(pluginContext.pubSub))
MidiPlugin(MidiSystem.None)

override fun openForSimulator(): OpenSimulatorPlugin =
override fun openForSimulator(
simulatorSettingsManager: SimulatorSettingsManager
): OpenSimulatorPlugin =
object : OpenSimulatorPlugin {
override fun getBridgePlugin(pluginContext: PluginContext): OpenBridgePlugin =
MidiBridgePlugin(createServerMidiSource(pluginContext), pluginContext)
private val midiHardwareSimulator = MidiHardwareSimulator(simulatorSettingsManager)

override fun getBridgePlugin(pluginContext: PluginContext): OpenBridgePlugin? = null

override fun getServerPlugin(pluginContext: PluginContext, bridgeClient: BridgeClient) =
openForServer(pluginContext, object : Args { override val enableMidi: Boolean get() = true })

override fun getClientPlugin(pluginContext: PluginContext): OpenClientPlugin =
openForClient(pluginContext)
}

private val midiDataTopic = PubSub.Topic("plugins/$id/midiData", MidiData.serializer())
}

/** Copy midi data from [midiSource] to a bridge PubSub channel. */
class MidiBridgePlugin(
private val midiSource: MidiSource,
pluginContext: PluginContext
) : OpenBridgePlugin {
private val channel = pluginContext.pubSub.openChannel(midiDataTopic, unknownMidi) { }

init {
midiSource.addObserver { channel.onChange(it.getMidiData()) }
}
override fun getHardwareSimulators(): List<HardwareSimulator> =
listOf(midiHardwareSimulator)
}
}
}

class PubSubPublisher(
midiSource: MidiSource,
pluginContext: PluginContext
) : Observable(), MidiSource {
private var midiData: MidiData = midiSource.getMidiData()
class MidiHardwareSimulator(
private val simulatorSettingsManager: SimulatorSettingsManager
) : HardwareSimulator, MidiSystem, Observable() {
override val title: String = "MIDI"

val channel = pluginContext.pubSub.openChannel(midiDataTopic, midiData) {
logger.warn { "MidiData update from client? Huh?" }
midiData = it
notifyChanged()
}
override var midiSources: List<MidiSource> = emptyList()
private set

init {
midiSource.addObserver {
val newMidiData = it.getMidiData()
midiData = newMidiData
notifyChanged()
channel.onChange(newMidiData)
override suspend fun start() {
logger.info { "Starting MIDI hardware simulator." }
simulatorSettingsManager.addObserver(fireImmediately = true) {
globalLaunch {
onSettingsChange()
}
}

override fun getMidiData(): MidiData = midiData

}

class PubSubSubscriber(
pubSub: PubSub.Endpoint,
defaultMidiData: MidiData = unknownMidi
) : Observable(), MidiSource {
private var midiData: MidiData = defaultMidiData
private fun onSettingsChange() {
val config = simulatorSettingsManager.simSettings
.getConfig(MidiPlugin.id, MidiHardwareSimulatorSettings.serializer())
?: MidiHardwareSimulatorSettings(emptyList())

init {
pubSub.openChannel(midiDataTopic, midiData) {
midiData = it
notifyChanged()
}
}
println("config = $config")
midiSources = config.devices.map { SimMidiSource(it.name) }
}

override fun getMidiData(): MidiData = midiData
companion object {
private val logger = Logger<MidiHardwareSimulator>()
}
}

@Serializable
data class MidiHardwareSimulatorSettings(
val devices: List<SimMidiDevice>
)

@Serializable
data class SimMidiDevice(
val name: String
)

class SimMidiSource(
override val name: String
) : MidiSource, Observable() {
override fun getMidiData(): MidiData {
TODO("not implemented")
}
}

internal expect fun createServerMidiSource(pluginContext: PluginContext): MidiSource
internal expect fun createMidiSystem(pluginContext: PluginContext): MidiSystem
Loading

0 comments on commit c7ec7a8

Please sign in to comment.