From 775abf35f92d2b76aaa4d49bd9e29c4b50e5c4f6 Mon Sep 17 00:00:00 2001 From: Abe Pazos Date: Mon, 22 Aug 2022 15:27:12 +0000 Subject: [PATCH] [orx-midi] Add pitch bend and pressure (#265) --- orx-jvm/orx-midi/src/main/kotlin/MidiEvent.kt | 31 ++++- .../src/main/kotlin/MidiTransceiver.kt | 130 ++++++++++++++---- 2 files changed, 131 insertions(+), 30 deletions(-) diff --git a/orx-jvm/orx-midi/src/main/kotlin/MidiEvent.kt b/orx-jvm/orx-midi/src/main/kotlin/MidiEvent.kt index aa0651e4..cc16ecb1 100644 --- a/orx-jvm/orx-midi/src/main/kotlin/MidiEvent.kt +++ b/orx-jvm/orx-midi/src/main/kotlin/MidiEvent.kt @@ -4,7 +4,9 @@ enum class MidiEventType { NOTE_ON, NOTE_OFF, CONTROL_CHANGED, - PROGRAM_CHANGE + PROGRAM_CHANGE, + CHANNEL_PRESSURE, + PITCH_BEND } class MidiEvent(val eventType: MidiEventType) { @@ -13,6 +15,8 @@ class MidiEvent(val eventType: MidiEventType) { var program: Int = 0 var note: Int = 0 var channel: Int = 0 + var pitchBend: Int = 0 + var pressure: Int = 0 var value: Int = 0 var velocity: Int = 0 @@ -51,9 +55,32 @@ class MidiEvent(val eventType: MidiEventType) { midiEvent.program = program return midiEvent } + + fun channelPressure(channel:Int, pressure: Int): MidiEvent { + val midiEvent = MidiEvent(MidiEventType.CHANNEL_PRESSURE) + midiEvent.channel = channel + midiEvent.pressure = pressure + return midiEvent + } + + fun pitchBend(channel:Int, pitchBend: Int): MidiEvent { + val midiEvent = MidiEvent(MidiEventType.PITCH_BEND) + midiEvent.channel = channel + midiEvent.pitchBend = pitchBend + return midiEvent + } } override fun toString(): String { - return "MidiEvent(eventType=$eventType, origin=$origin, program=$program, control=$control, note=$note, channel=$channel, value=$value, velocity=$velocity)" + return "MidiEvent(eventType=$eventType, " + + "origin=$origin, " + + "program=$program, " + + "control=$control, " + + "note=$note, " + + "channel=$channel, " + + "pitchBend=$pitchBend, " + + "pressure=$pressure, " + + "value=$value, " + + "velocity=$velocity)" } } \ No newline at end of file diff --git a/orx-jvm/orx-midi/src/main/kotlin/MidiTransceiver.kt b/orx-jvm/orx-midi/src/main/kotlin/MidiTransceiver.kt index 8b047828..bcd293b6 100644 --- a/orx-jvm/orx-midi/src/main/kotlin/MidiTransceiver.kt +++ b/orx-jvm/orx-midi/src/main/kotlin/MidiTransceiver.kt @@ -13,7 +13,12 @@ class MidiDeviceCapabilities { } } -data class MidiDeviceDescription(val name: String, val vendor: String, val receive: Boolean, val transmit: Boolean) { +data class MidiDeviceDescription( + val name: String, + val vendor: String, + val receive: Boolean, + val transmit: Boolean +) { companion object { fun list(): List { val caps = mutableMapOf() @@ -22,7 +27,8 @@ data class MidiDeviceDescription(val name: String, val vendor: String, val recei for (info in infos) { val device = MidiSystem.getMidiDevice(info) val name = MidiDeviceName(info.name, info.vendor) - val deviceCaps = caps.getOrPut(name) { MidiDeviceCapabilities() } + val deviceCaps = + caps.getOrPut(name) { MidiDeviceCapabilities() } if (device !is Sequencer && device !is Synthesizer) { if (device.maxReceivers != 0 && device.maxTransmitters == 0) { @@ -34,12 +40,17 @@ data class MidiDeviceDescription(val name: String, val vendor: String, val recei } } return caps.map { - MidiDeviceDescription(it.key.name, it.key.vendor, it.value.receive, it.value.transmit) + MidiDeviceDescription( + it.key.name, + it.key.vendor, + it.value.receive, + it.value.transmit + ) } } } - fun open() : MidiTransceiver { + fun open(): MidiTransceiver { require(receive && transmit) { "device should be a receiver and transmitter" } @@ -86,7 +97,8 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi private val receiver = receiverDevice.receiver private val transmitter = transmitterDevicer.transmitter - private inner class Destroyer: Thread() { + + private inner class Destroyer : Thread() { override fun run() { destroy() } @@ -103,10 +115,56 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi val channel = (cmd[0].toInt() and 0xff) and 0x0f val status = (cmd[0].toInt() and 0xff) and 0xf0 when (status) { - ShortMessage.NOTE_ON -> noteOn.trigger(MidiEvent.noteOn(channel, cmd[1].toInt() and 0xff, cmd[2].toInt() and 0xff)) - ShortMessage.NOTE_OFF -> noteOff.trigger(MidiEvent.noteOff(channel, cmd[1].toInt() and 0xff)) - ShortMessage.CONTROL_CHANGE -> controlChanged.trigger(MidiEvent.controlChange(channel,cmd[1].toInt() and 0xff, cmd[2].toInt() and 0xff)) - ShortMessage.PROGRAM_CHANGE -> programChanged.trigger(MidiEvent.programChange(channel,cmd[1].toInt() and 0xff)) + ShortMessage.NOTE_ON -> noteOn.trigger( + MidiEvent.noteOn( + channel, + cmd[1].toInt() and 0xff, + cmd[2].toInt() and 0xff + ) + ) + + ShortMessage.NOTE_OFF -> noteOff.trigger( + MidiEvent.noteOff( + channel, + cmd[1].toInt() and 0xff + ) + ) + + ShortMessage.CONTROL_CHANGE -> controlChanged.trigger( + MidiEvent.controlChange( + channel, + cmd[1].toInt() and 0xff, + cmd[2].toInt() and 0xff + ) + ) + + ShortMessage.PROGRAM_CHANGE -> programChanged.trigger( + MidiEvent.programChange( + channel, + cmd[1].toInt() and 0xff + ) + ) + + ShortMessage.CHANNEL_PRESSURE -> channelPressure.trigger( + MidiEvent.channelPressure( + channel, + cmd[1].toInt() and 0xff + ) + ) + // https://sites.uci.edu/camp2014/2014/04/30/managing-midi-pitchbend-messages/ + // The next operation to combine two 7bit values + // was verified to give the same results as the Linux + // `midisnoop` program while using an `Alesis Vortex + // Wireless 2` device. This MIDI device does not provide a + // full range 14 bit pitch-bend resolution though, so + // a different device is needed to confirm the pitch bend + // values slide as expected from -8192 to +8191. + ShortMessage.PITCH_BEND -> pitchBend.trigger( + MidiEvent.pitchBend( + channel, + (cmd[2].toInt() shl 25 shr 18) + cmd[1].toInt() + ) + ) } } override fun close() { @@ -121,14 +179,13 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi val programChanged = Event("midi-transceiver::program-changed") val noteOn = Event("midi-transceiver::note-on") val noteOff = Event("midi-transceiver::note-off") + val channelPressure = Event("midi-transceiver::channel-pressure") + val pitchBend = Event("midi-transceiver::pitch-bend") fun controlChange(channel: Int, control: Int, value: Int) { try { val msg = ShortMessage(ShortMessage.CONTROL_CHANGE, channel, control, value) - if (receiverDevice != null) { - val tc = receiverDevice.microsecondPosition - receiver.send(msg, tc) - } + receiver.send(msg, receiverDevice.microsecondPosition) } catch (e: InvalidMidiDataException) { // } @@ -137,10 +194,7 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi fun programChange(channel: Int, program: Int) { try { val msg = ShortMessage(ShortMessage.PROGRAM_CHANGE, channel, program) - if (receiverDevice != null) { - val tc = receiverDevice.microsecondPosition - receiver.send(msg, tc) - } + receiver.send(msg, receiverDevice.microsecondPosition) } catch (e: InvalidMidiDataException) { // } @@ -149,10 +203,25 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi fun noteOn(channel: Int, key: Int, velocity: Int) { try { val msg = ShortMessage(ShortMessage.NOTE_ON, channel, key, velocity) - if (receiverDevice != null) { - val tc = receiverDevice.microsecondPosition - receiver.send(msg, tc) - } + receiver.send(msg, receiverDevice.microsecondPosition) + } catch (e: InvalidMidiDataException) { + // + } + } + + fun channelPressure(channel: Int, value: Int) { + try { + val msg = ShortMessage(ShortMessage.CHANNEL_PRESSURE, channel, value) + receiver.send(msg, receiverDevice.microsecondPosition) + } catch (e: InvalidMidiDataException) { + // + } + } + + fun pitchBend(channel: Int, value: Int) { + try { + val msg = ShortMessage(ShortMessage.PITCH_BEND, channel, value) + receiver.send(msg, receiverDevice.microsecondPosition) } catch (e: InvalidMidiDataException) { // } @@ -165,11 +234,16 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi } fun main() { - MidiDeviceDescription.list().forEach { - println("> ${it.name}, ${it.vendor} r:${it.receive} t:${it.transmit}") - } - val dev = MidiTransceiver.fromDeviceVendor("BCR2000 [hw:2,0,0]", "ALSA (http://www.alsa-project.org)") - dev.controlChanged.listen { - println("${it.channel} ${it.control} ${it.value}") - } + val deviceName = "BCR2000" + MidiDeviceDescription.list().forEach(::println) + MidiDeviceDescription.list().firstOrNull { it.name.contains(deviceName) } + ?.run { + val controller = MidiTransceiver.fromDeviceVendor(name, vendor) + controller.controlChanged.listen { println(it) } + controller.programChanged.listen { println(it) } + controller.noteOn.listen { println(it) } + controller.noteOff.listen { println(it) } + controller.channelPressure.listen { println(it) } + controller.pitchBend.listen { println(it) } + } } \ No newline at end of file