[orx-midi] Add pitch bend and pressure (#265)
This commit is contained in:
@@ -4,7 +4,9 @@ enum class MidiEventType {
|
|||||||
NOTE_ON,
|
NOTE_ON,
|
||||||
NOTE_OFF,
|
NOTE_OFF,
|
||||||
CONTROL_CHANGED,
|
CONTROL_CHANGED,
|
||||||
PROGRAM_CHANGE
|
PROGRAM_CHANGE,
|
||||||
|
CHANNEL_PRESSURE,
|
||||||
|
PITCH_BEND
|
||||||
}
|
}
|
||||||
|
|
||||||
class MidiEvent(val eventType: MidiEventType) {
|
class MidiEvent(val eventType: MidiEventType) {
|
||||||
@@ -13,6 +15,8 @@ class MidiEvent(val eventType: MidiEventType) {
|
|||||||
var program: Int = 0
|
var program: Int = 0
|
||||||
var note: Int = 0
|
var note: Int = 0
|
||||||
var channel: Int = 0
|
var channel: Int = 0
|
||||||
|
var pitchBend: Int = 0
|
||||||
|
var pressure: Int = 0
|
||||||
var value: Int = 0
|
var value: Int = 0
|
||||||
var velocity: Int = 0
|
var velocity: Int = 0
|
||||||
|
|
||||||
@@ -51,9 +55,32 @@ class MidiEvent(val eventType: MidiEventType) {
|
|||||||
midiEvent.program = program
|
midiEvent.program = program
|
||||||
return midiEvent
|
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 {
|
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)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 {
|
companion object {
|
||||||
fun list(): List<MidiDeviceDescription> {
|
fun list(): List<MidiDeviceDescription> {
|
||||||
val caps = mutableMapOf<MidiDeviceName, MidiDeviceCapabilities>()
|
val caps = mutableMapOf<MidiDeviceName, MidiDeviceCapabilities>()
|
||||||
@@ -22,7 +27,8 @@ data class MidiDeviceDescription(val name: String, val vendor: String, val recei
|
|||||||
for (info in infos) {
|
for (info in infos) {
|
||||||
val device = MidiSystem.getMidiDevice(info)
|
val device = MidiSystem.getMidiDevice(info)
|
||||||
val name = MidiDeviceName(info.name, info.vendor)
|
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 !is Sequencer && device !is Synthesizer) {
|
||||||
if (device.maxReceivers != 0 && device.maxTransmitters == 0) {
|
if (device.maxReceivers != 0 && device.maxTransmitters == 0) {
|
||||||
@@ -34,7 +40,12 @@ data class MidiDeviceDescription(val name: String, val vendor: String, val recei
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return caps.map {
|
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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,6 +97,7 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi
|
|||||||
|
|
||||||
private val receiver = receiverDevice.receiver
|
private val receiver = receiverDevice.receiver
|
||||||
private val transmitter = transmitterDevicer.transmitter
|
private val transmitter = transmitterDevicer.transmitter
|
||||||
|
|
||||||
private inner class Destroyer : Thread() {
|
private inner class Destroyer : Thread() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
destroy()
|
destroy()
|
||||||
@@ -103,10 +115,56 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi
|
|||||||
val channel = (cmd[0].toInt() and 0xff) and 0x0f
|
val channel = (cmd[0].toInt() and 0xff) and 0x0f
|
||||||
val status = (cmd[0].toInt() and 0xff) and 0xf0
|
val status = (cmd[0].toInt() and 0xff) and 0xf0
|
||||||
when (status) {
|
when (status) {
|
||||||
ShortMessage.NOTE_ON -> noteOn.trigger(MidiEvent.noteOn(channel, cmd[1].toInt() and 0xff, cmd[2].toInt() and 0xff))
|
ShortMessage.NOTE_ON -> noteOn.trigger(
|
||||||
ShortMessage.NOTE_OFF -> noteOff.trigger(MidiEvent.noteOff(channel, cmd[1].toInt() and 0xff))
|
MidiEvent.noteOn(
|
||||||
ShortMessage.CONTROL_CHANGE -> controlChanged.trigger(MidiEvent.controlChange(channel,cmd[1].toInt() and 0xff, cmd[2].toInt() and 0xff))
|
channel,
|
||||||
ShortMessage.PROGRAM_CHANGE -> programChanged.trigger(MidiEvent.programChange(channel,cmd[1].toInt() and 0xff))
|
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() {
|
override fun close() {
|
||||||
@@ -121,14 +179,13 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi
|
|||||||
val programChanged = Event<MidiEvent>("midi-transceiver::program-changed")
|
val programChanged = Event<MidiEvent>("midi-transceiver::program-changed")
|
||||||
val noteOn = Event<MidiEvent>("midi-transceiver::note-on")
|
val noteOn = Event<MidiEvent>("midi-transceiver::note-on")
|
||||||
val noteOff = Event<MidiEvent>("midi-transceiver::note-off")
|
val noteOff = Event<MidiEvent>("midi-transceiver::note-off")
|
||||||
|
val channelPressure = Event<MidiEvent>("midi-transceiver::channel-pressure")
|
||||||
|
val pitchBend = Event<MidiEvent>("midi-transceiver::pitch-bend")
|
||||||
|
|
||||||
fun controlChange(channel: Int, control: Int, value: Int) {
|
fun controlChange(channel: Int, control: Int, value: Int) {
|
||||||
try {
|
try {
|
||||||
val msg = ShortMessage(ShortMessage.CONTROL_CHANGE, channel, control, value)
|
val msg = ShortMessage(ShortMessage.CONTROL_CHANGE, channel, control, value)
|
||||||
if (receiverDevice != null) {
|
receiver.send(msg, receiverDevice.microsecondPosition)
|
||||||
val tc = receiverDevice.microsecondPosition
|
|
||||||
receiver.send(msg, tc)
|
|
||||||
}
|
|
||||||
} catch (e: InvalidMidiDataException) {
|
} catch (e: InvalidMidiDataException) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
@@ -137,10 +194,7 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi
|
|||||||
fun programChange(channel: Int, program: Int) {
|
fun programChange(channel: Int, program: Int) {
|
||||||
try {
|
try {
|
||||||
val msg = ShortMessage(ShortMessage.PROGRAM_CHANGE, channel, program)
|
val msg = ShortMessage(ShortMessage.PROGRAM_CHANGE, channel, program)
|
||||||
if (receiverDevice != null) {
|
receiver.send(msg, receiverDevice.microsecondPosition)
|
||||||
val tc = receiverDevice.microsecondPosition
|
|
||||||
receiver.send(msg, tc)
|
|
||||||
}
|
|
||||||
} catch (e: InvalidMidiDataException) {
|
} catch (e: InvalidMidiDataException) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
@@ -149,10 +203,25 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi
|
|||||||
fun noteOn(channel: Int, key: Int, velocity: Int) {
|
fun noteOn(channel: Int, key: Int, velocity: Int) {
|
||||||
try {
|
try {
|
||||||
val msg = ShortMessage(ShortMessage.NOTE_ON, channel, key, velocity)
|
val msg = ShortMessage(ShortMessage.NOTE_ON, channel, key, velocity)
|
||||||
if (receiverDevice != null) {
|
receiver.send(msg, receiverDevice.microsecondPosition)
|
||||||
val tc = receiverDevice.microsecondPosition
|
} catch (e: InvalidMidiDataException) {
|
||||||
receiver.send(msg, tc)
|
//
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
} catch (e: InvalidMidiDataException) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
@@ -165,11 +234,16 @@ class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: Mi
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
MidiDeviceDescription.list().forEach {
|
val deviceName = "BCR2000"
|
||||||
println("> ${it.name}, ${it.vendor} r:${it.receive} t:${it.transmit}")
|
MidiDeviceDescription.list().forEach(::println)
|
||||||
}
|
MidiDeviceDescription.list().firstOrNull { it.name.contains(deviceName) }
|
||||||
val dev = MidiTransceiver.fromDeviceVendor("BCR2000 [hw:2,0,0]", "ALSA (http://www.alsa-project.org)")
|
?.run {
|
||||||
dev.controlChanged.listen {
|
val controller = MidiTransceiver.fromDeviceVendor(name, vendor)
|
||||||
println("${it.channel} ${it.control} ${it.value}")
|
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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user