Fix/midi transceiver fails on short/system messages (#347)
This commit is contained in:
@@ -28,6 +28,7 @@ ktor = "3.2.3"
|
||||
jgit = "7.3.0.202506031305-r"
|
||||
javaosc = "0.9"
|
||||
jsoup = "1.21.1"
|
||||
mockk = "1.13.11"
|
||||
processing = "4.4.6"
|
||||
|
||||
[libraries]
|
||||
@@ -49,7 +50,10 @@ kotest-assertions = { group = "io.kotest", name = "kotest-assertions-core", vers
|
||||
kotest-runner = { group = "io.kotest", name = "kotest-runner-junit5", version.ref = "kotest" }
|
||||
kotest-framework-engine = { group = "io.kotest", name = "kotest-framework-engine", version.ref = "kotest" }
|
||||
|
||||
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk"}
|
||||
|
||||
processing-core = { group = "org.processing", name = "core", version.ref = "processing"}
|
||||
|
||||
openrndr-application = { group = "org.openrndr", name = "openrndr-application", version.ref = "openrndr" }
|
||||
openrndr-extensions = { group = "org.openrndr", name = "openrndr-extensions", version.ref = "openrndr" }
|
||||
openrndr-math = { group = "org.openrndr", name = "openrndr-math", version.ref = "openrndr" }
|
||||
|
||||
@@ -9,4 +9,7 @@ dependencies {
|
||||
implementation(libs.kotlin.coroutines)
|
||||
implementation(project(":orx-property-watchers"))
|
||||
implementation(project(":orx-parameters"))
|
||||
|
||||
testImplementation(libs.mockk)
|
||||
testImplementation(libs.kotest.assertions)
|
||||
}
|
||||
@@ -48,7 +48,7 @@ fun Program.bindMidiControl(
|
||||
val low = anno?.low ?: 0.0
|
||||
val high = anno?.high ?: 1.0
|
||||
transceiver.controlChanged.listen {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channel && it.control == control) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channel && it.control == control) {
|
||||
val value = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
property.set(value)
|
||||
}
|
||||
@@ -83,7 +83,7 @@ fun Program.bindMidiControl(
|
||||
control: Int
|
||||
) {
|
||||
transceiver.controlChanged.listen {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channel && it.control == control) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channel && it.control == control) {
|
||||
property.set(it.value >= 64)
|
||||
}
|
||||
}
|
||||
@@ -126,12 +126,12 @@ fun Program.bindMidiControl(
|
||||
var y = v.y
|
||||
var changed = false
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelX && it.control == controlX) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelX && it.control == controlX) {
|
||||
changed = true
|
||||
x = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelY && it.control == controlY) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelY && it.control == controlY) {
|
||||
changed = true
|
||||
y = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
@@ -187,17 +187,17 @@ fun Program.bindMidiControl(
|
||||
var z = v.z
|
||||
var changed = false
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelX && it.control == controlX) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelX && it.control == controlX) {
|
||||
changed = true
|
||||
x = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelY && it.control == controlY) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelY && it.control == controlY) {
|
||||
changed = true
|
||||
y = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelZ && it.control == controlZ) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelZ && it.control == controlZ) {
|
||||
changed = true
|
||||
z = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
@@ -257,22 +257,22 @@ fun Program.bindMidiControl(
|
||||
var a = v.alpha
|
||||
var changed = false
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelR && it.control == controlR) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelR && it.control == controlR) {
|
||||
changed = true
|
||||
r = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelG && it.control == controlG) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelG && it.control == controlG) {
|
||||
changed = true
|
||||
g = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelB && it.control == controlB) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelB && it.control == controlB) {
|
||||
changed = true
|
||||
b = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelA && it.control == controlA) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelA && it.control == controlA) {
|
||||
changed = true
|
||||
a = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
@@ -335,22 +335,22 @@ fun Program.bindMidiControl(
|
||||
var w = v.w
|
||||
var changed = false
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelX && it.control == controlX) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelX && it.control == controlX) {
|
||||
changed = true
|
||||
x = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelY && it.control == controlY) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelY && it.control == controlY) {
|
||||
changed = true
|
||||
y = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelZ && it.control == controlZ) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelZ && it.control == controlZ) {
|
||||
changed = true
|
||||
z = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGED && it.channel == channelW && it.control == controlW) {
|
||||
if (it.eventType == MidiEventType.CONTROL_CHANGE && it.channel == channelW && it.control == controlW) {
|
||||
changed = true
|
||||
w = it.value.toDouble().map(0.0, 127.0, low, high, clamp = true)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,47 @@
|
||||
package org.openrndr.extra.midi
|
||||
|
||||
enum class MidiEventType {
|
||||
NOTE_ON,
|
||||
NOTE_OFF,
|
||||
CONTROL_CHANGED,
|
||||
PROGRAM_CHANGE,
|
||||
CHANNEL_PRESSURE,
|
||||
PITCH_BEND
|
||||
import javax.sound.midi.MidiMessage
|
||||
import javax.sound.midi.ShortMessage
|
||||
|
||||
enum class MidiEventType(val status: Int) {
|
||||
|
||||
MIDI_TIME_CODE(ShortMessage.MIDI_TIME_CODE),
|
||||
SONG_POSITION_POINTER(ShortMessage.SONG_POSITION_POINTER),
|
||||
SONG_SELECT(ShortMessage.SONG_SELECT),
|
||||
TUNE_REQUEST(ShortMessage.TUNE_REQUEST),
|
||||
END_OF_EXCLUSIVE(ShortMessage.END_OF_EXCLUSIVE),
|
||||
TIMING_CLOCK(ShortMessage.TIMING_CLOCK),
|
||||
START(ShortMessage.START),
|
||||
CONTINUE(ShortMessage.CONTINUE),
|
||||
STOP(ShortMessage.STOP),
|
||||
ACTIVE_SENSING(ShortMessage.ACTIVE_SENSING),
|
||||
SYSTEM_RESET(ShortMessage.SYSTEM_RESET),
|
||||
NOTE_ON(ShortMessage.NOTE_ON),
|
||||
NOTE_OFF(ShortMessage.NOTE_OFF),
|
||||
CONTROL_CHANGE(ShortMessage.CONTROL_CHANGE),
|
||||
PROGRAM_CHANGE(ShortMessage.PROGRAM_CHANGE),
|
||||
CHANNEL_PRESSURE(ShortMessage.CHANNEL_PRESSURE),
|
||||
PITCH_BEND(ShortMessage.PITCH_BEND);
|
||||
|
||||
companion object {
|
||||
|
||||
private val statusMap: Map<Int, MidiEventType> =
|
||||
entries.associateBy { it.status }
|
||||
|
||||
fun fromStatus(
|
||||
status: Int
|
||||
): MidiEventType = requireNotNull(
|
||||
statusMap[if (status >= 0xf0) status else status and 0xf0]
|
||||
) {
|
||||
"Invalid MIDI status: $status"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val MidiMessage.eventType: MidiEventType get() = MidiEventType.fromStatus(status)
|
||||
|
||||
class MidiEvent(val eventType: MidiEventType) {
|
||||
var origin = Origin.DEVICE
|
||||
var control: Int = 0
|
||||
@@ -34,15 +67,16 @@ class MidiEvent(val eventType: MidiEventType) {
|
||||
return midiEvent
|
||||
}
|
||||
|
||||
fun noteOff(channel: Int, note: Int): MidiEvent {
|
||||
fun noteOff(channel: Int, note: Int, velocity: Int): MidiEvent {
|
||||
val midiEvent = MidiEvent(MidiEventType.NOTE_OFF)
|
||||
midiEvent.note = note
|
||||
midiEvent.channel = channel
|
||||
midiEvent.velocity = velocity
|
||||
return midiEvent
|
||||
}
|
||||
|
||||
fun controlChange(channel: Int, control: Int, value: Int): MidiEvent {
|
||||
val midiEvent = MidiEvent(MidiEventType.CONTROL_CHANGED)
|
||||
val midiEvent = MidiEvent(MidiEventType.CONTROL_CHANGE)
|
||||
midiEvent.channel = channel
|
||||
midiEvent.control = control
|
||||
midiEvent.value = value
|
||||
|
||||
@@ -118,45 +118,30 @@ class MidiTransceiver(program: Program, val receiverDevice: MidiDevice?, val tra
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
transmitter?.receiver = object : MidiDeviceReceiver {
|
||||
override fun getMidiDevice(): MidiDevice? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun send(message: MidiMessage, timeStamp: Long) {
|
||||
private fun trigger(message: MidiMessage) {
|
||||
val cmd = message.message
|
||||
val channel = (cmd[0].toInt() and 0xff) and 0x0f
|
||||
when (val eventType = message.eventType) {
|
||||
|
||||
MidiEventType.NOTE_ON -> {
|
||||
val key = cmd[1].toInt() and 0xff
|
||||
val velocity = cmd[2].toInt() and 0xff
|
||||
when ((cmd[0].toInt() and 0xff) and 0xf0) {
|
||||
|
||||
|
||||
|
||||
ShortMessage.NOTE_ON -> if (velocity > 0) {
|
||||
noteOn.trigger(
|
||||
MidiEvent.noteOn(
|
||||
channel,
|
||||
cmd[1].toInt() and 0xff,
|
||||
velocity
|
||||
)
|
||||
)
|
||||
if (velocity > 0) {
|
||||
noteOn.trigger(MidiEvent.noteOn(channel, key, velocity))
|
||||
} else {
|
||||
noteOff.trigger(
|
||||
MidiEvent.noteOff(
|
||||
channel,
|
||||
cmd[1].toInt() and 0xff
|
||||
)
|
||||
)
|
||||
noteOff.trigger(MidiEvent.noteOff(channel, key, velocity))
|
||||
}
|
||||
}
|
||||
|
||||
ShortMessage.NOTE_OFF -> noteOff.trigger(
|
||||
MidiEventType.NOTE_OFF -> noteOff.trigger(
|
||||
MidiEvent.noteOff(
|
||||
channel,
|
||||
cmd[1].toInt() and 0xff
|
||||
cmd[1].toInt() and 0xff,
|
||||
cmd[2].toInt() and 0xff
|
||||
)
|
||||
)
|
||||
|
||||
ShortMessage.CONTROL_CHANGE -> controlChanged.trigger(
|
||||
MidiEventType.CONTROL_CHANGE -> controlChanged.trigger(
|
||||
MidiEvent.controlChange(
|
||||
channel,
|
||||
cmd[1].toInt() and 0xff,
|
||||
@@ -164,19 +149,20 @@ class MidiTransceiver(program: Program, val receiverDevice: MidiDevice?, val tra
|
||||
)
|
||||
)
|
||||
|
||||
ShortMessage.PROGRAM_CHANGE -> programChanged.trigger(
|
||||
MidiEventType.PROGRAM_CHANGE -> programChanged.trigger(
|
||||
MidiEvent.programChange(
|
||||
channel,
|
||||
cmd[1].toInt() and 0xff
|
||||
)
|
||||
)
|
||||
|
||||
ShortMessage.CHANNEL_PRESSURE -> channelPressure.trigger(
|
||||
MidiEventType.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
|
||||
@@ -185,13 +171,27 @@ class MidiTransceiver(program: Program, val receiverDevice: MidiDevice?, val tra
|
||||
// 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(
|
||||
MidiEventType.PITCH_BEND -> pitchBend.trigger(
|
||||
MidiEvent.pitchBend(
|
||||
channel,
|
||||
(cmd[2].toInt() shl 25 shr 18) + cmd[1].toInt()
|
||||
)
|
||||
)
|
||||
|
||||
else -> {
|
||||
logger.trace { "Unsupported MIDI event type: $eventType" }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
transmitter?.receiver = object : MidiDeviceReceiver {
|
||||
override fun getMidiDevice(): MidiDevice? {
|
||||
return null
|
||||
}
|
||||
override fun send(message: MidiMessage, timeStamp: Long) {
|
||||
trigger(message)
|
||||
}
|
||||
override fun close() {
|
||||
}
|
||||
@@ -212,64 +212,45 @@ class MidiTransceiver(program: Program, val receiverDevice: MidiDevice?, val tra
|
||||
val pitchBend = Event<MidiEvent>("midi-transceiver::pitch-bend")
|
||||
|
||||
fun controlChange(channel: Int, control: Int, value: Int) {
|
||||
if (receiver != null && receiverDevice != null) {
|
||||
try {
|
||||
val msg = ShortMessage(ShortMessage.CONTROL_CHANGE, channel, control, value)
|
||||
receiver.send(msg, receiverDevice.microsecondPosition)
|
||||
} catch (e: InvalidMidiDataException) {
|
||||
logger.warn { e.message }
|
||||
}
|
||||
}
|
||||
send { ShortMessage(ShortMessage.CONTROL_CHANGE, channel, control, value) }
|
||||
}
|
||||
|
||||
fun programChange(channel: Int, program: Int) {
|
||||
if (receiver != null && receiverDevice != null) {
|
||||
try {
|
||||
val msg = ShortMessage(ShortMessage.PROGRAM_CHANGE, channel, program)
|
||||
receiver.send(msg, receiverDevice.microsecondPosition)
|
||||
} catch (e: InvalidMidiDataException) {
|
||||
logger.warn { e.message }
|
||||
}
|
||||
}
|
||||
send { ShortMessage(ShortMessage.PROGRAM_CHANGE, channel, program) }
|
||||
}
|
||||
|
||||
fun noteOn(channel: Int, key: Int, velocity: Int) {
|
||||
if (receiver != null && receiverDevice != null) {
|
||||
try {
|
||||
val msg = ShortMessage(ShortMessage.NOTE_ON, channel, key, velocity)
|
||||
receiver.send(msg, receiverDevice.microsecondPosition)
|
||||
} catch (e: InvalidMidiDataException) {
|
||||
logger.warn { e.message }
|
||||
}
|
||||
send { ShortMessage(ShortMessage.NOTE_ON, channel, key, velocity) }
|
||||
}
|
||||
|
||||
fun noteOff(channel: Int, key: Int, velocity: Int) {
|
||||
send { ShortMessage(ShortMessage.NOTE_OFF, channel, key, velocity) }
|
||||
}
|
||||
|
||||
fun channelPressure(channel: Int, value: Int) {
|
||||
if (receiver != null && receiverDevice != null) {
|
||||
try {
|
||||
val msg = ShortMessage(ShortMessage.CHANNEL_PRESSURE, channel, value)
|
||||
receiver.send(msg, receiverDevice.microsecondPosition)
|
||||
} catch (e: InvalidMidiDataException) {
|
||||
logger.warn { e.message }
|
||||
}
|
||||
}
|
||||
send { ShortMessage(ShortMessage.CHANNEL_PRESSURE, channel, value) }
|
||||
}
|
||||
|
||||
fun pitchBend(channel: Int, value: Int) {
|
||||
if (receiver != null && receiverDevice != null) {
|
||||
try {
|
||||
val msg = ShortMessage(ShortMessage.PITCH_BEND, channel, value)
|
||||
receiver.send(msg, receiverDevice.microsecondPosition)
|
||||
} catch (e: InvalidMidiDataException) {
|
||||
logger.warn { e.message }
|
||||
}
|
||||
}
|
||||
send { ShortMessage(ShortMessage.PITCH_BEND, channel, value) }
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
receiverDevice?.close()
|
||||
transmitterDevicer?.close()
|
||||
}
|
||||
|
||||
private fun send(block: () -> MidiMessage) {
|
||||
if (receiver != null && receiverDevice != null) {
|
||||
try {
|
||||
val msg = block()
|
||||
receiver.send(msg, receiverDevice.microsecondPosition)
|
||||
} catch (e: InvalidMidiDataException) {
|
||||
logger.warn { e.message }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
20
orx-jvm/orx-midi/src/test/kotlin/MidiTests.kt
Normal file
20
orx-jvm/orx-midi/src/test/kotlin/MidiTests.kt
Normal file
@@ -0,0 +1,20 @@
|
||||
package org.openrndr.extra.midi
|
||||
|
||||
import javax.sound.midi.Receiver
|
||||
import javax.sound.midi.Transmitter
|
||||
|
||||
class TestTransmitter : Transmitter {
|
||||
|
||||
private var receiver: Receiver? = null
|
||||
|
||||
override fun setReceiver(receiver: Receiver?) {
|
||||
this.receiver = receiver
|
||||
}
|
||||
|
||||
override fun getReceiver(): Receiver? = receiver
|
||||
|
||||
override fun close() {
|
||||
receiver?.close()
|
||||
}
|
||||
|
||||
}
|
||||
118
orx-jvm/orx-midi/src/test/kotlin/MidiTransceiverTest.kt
Normal file
118
orx-jvm/orx-midi/src/test/kotlin/MidiTransceiverTest.kt
Normal file
@@ -0,0 +1,118 @@
|
||||
package org.openrndr.extra.midi
|
||||
|
||||
import io.kotest.matchers.should
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.types.beInstanceOf
|
||||
import io.mockk.*
|
||||
import org.openrndr.Program
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.sound.midi.MidiDevice
|
||||
import javax.sound.midi.MidiMessage
|
||||
import javax.sound.midi.Receiver
|
||||
import javax.sound.midi.ShortMessage
|
||||
import kotlin.test.Test
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class MidiTransceiverTest {
|
||||
|
||||
// given
|
||||
val program = mockk<Program>(relaxed = true)
|
||||
val receiver = mockk<Receiver>()
|
||||
val receiverDevice = mockk<MidiDevice>(relaxed = true)
|
||||
val messageSlot = slot<MidiMessage>()
|
||||
|
||||
val transmitter = TestTransmitter()
|
||||
val transmitterDevice = mockk<MidiDevice>()
|
||||
|
||||
init {
|
||||
every { receiverDevice.receiver } returns receiver
|
||||
every { receiver.send(capture(messageSlot), any()) } just runs
|
||||
every { transmitterDevice.transmitter } returns transmitter
|
||||
}
|
||||
|
||||
val transceiver = MidiTransceiver(
|
||||
program,
|
||||
receiverDevice,
|
||||
transmitterDevice
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `should send out NOTE_ON message`() {
|
||||
// when
|
||||
transceiver.noteOn(5, 10, 100)
|
||||
|
||||
// then
|
||||
messageSlot.captured should beInstanceOf<ShortMessage>()
|
||||
(messageSlot.captured as ShortMessage).apply {
|
||||
command shouldBe ShortMessage.NOTE_ON
|
||||
channel shouldBe 5
|
||||
data1 shouldBe 10
|
||||
data2 shouldBe 100
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should send out NOTE_OFF message`() {
|
||||
// when
|
||||
transceiver.noteOff(1, 10, 62)
|
||||
|
||||
// then
|
||||
messageSlot.captured should beInstanceOf<ShortMessage>()
|
||||
(messageSlot.captured as ShortMessage).apply {
|
||||
command shouldBe ShortMessage.NOTE_OFF
|
||||
channel shouldBe 1
|
||||
data1 shouldBe 10
|
||||
data2 shouldBe 62
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should receive NOTE_ON event on receiving NOTE_ON message`() {
|
||||
// given
|
||||
val eventSlot = AtomicReference<MidiEvent>()
|
||||
transceiver.noteOn.listen {
|
||||
eventSlot.set(it)
|
||||
}
|
||||
|
||||
// when
|
||||
transmitter.receiver!!.send(
|
||||
ShortMessage(ShortMessage.NOTE_ON, 1, 2, 3), 1042
|
||||
)
|
||||
val noteOnEvent = eventSlot.get()
|
||||
|
||||
// then
|
||||
noteOnEvent.apply {
|
||||
eventType shouldBe MidiEventType.NOTE_ON
|
||||
origin shouldBe MidiEvent.Origin.DEVICE
|
||||
channel shouldBe 1
|
||||
note shouldBe 2
|
||||
velocity shouldBe 3
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should receive NOTE_OFF event on receiving NOTE_ON message with velocity 0`() {
|
||||
// given
|
||||
val eventSlot = AtomicReference<MidiEvent>()
|
||||
transceiver.noteOff.listen {
|
||||
eventSlot.set(it)
|
||||
}
|
||||
|
||||
// when
|
||||
transmitter.receiver!!.send(
|
||||
ShortMessage(ShortMessage.NOTE_ON, 2, 3, 0), 1042
|
||||
)
|
||||
val noteOnEvent = eventSlot.get()
|
||||
|
||||
// then
|
||||
noteOnEvent.apply {
|
||||
eventType shouldBe MidiEventType.NOTE_OFF
|
||||
origin shouldBe MidiEvent.Origin.DEVICE
|
||||
channel shouldBe 2
|
||||
note shouldBe 3
|
||||
velocity shouldBe 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user