From e5e82425f28b41c910248ca40fca021077377ae9 Mon Sep 17 00:00:00 2001 From: Abe Pazos Date: Fri, 26 May 2023 09:08:38 +0200 Subject: [PATCH] [orx-midi] provide dummy MIDI device, improve logging (#312) --- .../orx-midi/src/main/kotlin/MidiConsole.kt | 2 +- .../src/main/kotlin/MidiTransceiver.kt | 45 ++++++++++++++----- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/orx-jvm/orx-midi/src/main/kotlin/MidiConsole.kt b/orx-jvm/orx-midi/src/main/kotlin/MidiConsole.kt index 084a7425..b18d371d 100644 --- a/orx-jvm/orx-midi/src/main/kotlin/MidiConsole.kt +++ b/orx-jvm/orx-midi/src/main/kotlin/MidiConsole.kt @@ -35,7 +35,7 @@ class MidiConsole : Extension { fun register(transceiver: MidiTransceiver) { transceiver.controlChanged.listen { synchronized(messages) { - messages.add("CC ${it.control}: ${it.value}") + messages.add("Ch=${it.channel} CC=${it.control}: ${it.value}") if (messages.size > historySize) { messages.removeAt(0) } diff --git a/orx-jvm/orx-midi/src/main/kotlin/MidiTransceiver.kt b/orx-jvm/orx-midi/src/main/kotlin/MidiTransceiver.kt index 1d46a8d4..d5ad5906 100644 --- a/orx-jvm/orx-midi/src/main/kotlin/MidiTransceiver.kt +++ b/orx-jvm/orx-midi/src/main/kotlin/MidiTransceiver.kt @@ -8,6 +8,7 @@ import javax.sound.midi.* private val logger = KotlinLogging.logger { } data class MidiDeviceName(val name: String, val vendor: String) + class MidiDeviceCapabilities { var receive: Boolean = false var transmit: Boolean = false @@ -56,7 +57,7 @@ data class MidiDeviceDescription( fun open(program: Program): MidiTransceiver { require(receive && transmit) { - "device should be a receiver and transmitter" + "MIDI device should be a receiver and transmitter" } return MidiTransceiver.fromDeviceVendor(program, name, vendor) @@ -76,23 +77,23 @@ class MidiTransceiver(program: Program, val receiverDevice: MidiDevice?, val tra val device = MidiSystem.getMidiDevice(info) if (device !is Sequencer && device !is Synthesizer) { if ((vendor == null || info.vendor == vendor) && info.name == name) { - logger.info { "found matching device $name / $vendor" } + logger.info { "found matching MIDI device $name / $vendor" } if (device.maxTransmitters != 0 && device.maxReceivers == 0) { transmitterDevice = device logger.debug { - "found transmitter" + "found MIDI transmitter" } } if (device.maxReceivers != 0 && device.maxTransmitters == 0) { receiverDevice = device logger.debug { - "found receiver" + "found MIDI receiver" } } } } } catch (e: MidiUnavailableException) { - throw IllegalStateException("no midi available") + error("no MIDI available") } } @@ -101,7 +102,7 @@ class MidiTransceiver(program: Program, val receiverDevice: MidiDevice?, val tra transmitterDevice.open() return MidiTransceiver(program, receiverDevice, transmitterDevice) } else { - throw IllegalArgumentException("midi device not found ${name}:${vendor} $receiverDevice $transmitterDevice") + error("MIDI device not found ${name}:${vendor} $receiverDevice $transmitterDevice") } } } @@ -279,9 +280,21 @@ fun listMidiDevices() = MidiDeviceDescription.list() * Open a MIDI device by name * @param name the name of the MIDI device to open. Either the * exact name or the first characters of the name. + * Throws an exception if the device name is not found. * @since 0.4.3 */ -fun Program.openMidiDevice(name: String): MidiTransceiver { +fun Program.openMidiDevice(name: String) = + openMidiDeviceOrNull(name) ?: error("MIDI device not found for query '$name'") + +/** + * Open a MIDI device by name + * + * @param name the name of the MIDI device to open. Either the + * exact name or the first characters of the name. + * Returns null if the device name is not found. + * @since 0.4.3 + */ +fun Program.openMidiDeviceOrNull(name: String): MidiTransceiver? { val devices = listMidiDevices() val matchingDevice = devices.firstOrNull { @@ -291,7 +304,19 @@ fun Program.openMidiDevice(name: String): MidiTransceiver { // Existing device name starts with `name` it.name.startsWith(name) } - val actualName = matchingDevice?.name ?: name - return MidiTransceiver.fromDeviceVendor(this, actualName) -} \ No newline at end of file + return if(matchingDevice != null) + MidiTransceiver.fromDeviceVendor(this, matchingDevice.name) + else + null +} + +/** + * Open a dummy MIDI device + * + * Enables running programs that depend on a specific MIDI device + * when that device is not available. + * Usage: `val dev = openMidiDeviceOrNull("Twister") ?: dummyMidiDevice()` + * @since 0.4.3 + */ +fun Program.dummyMidiDevice() = MidiTransceiver(this, null, null)