Upgrade to OPENRNDR 0.4 snapshot

This commit is contained in:
Edwin Jakobs
2021-06-22 11:08:07 +02:00
parent 579ddf9bb5
commit 9435907ef9
339 changed files with 460 additions and 497 deletions

View File

@@ -0,0 +1,30 @@
# orx-midi
Basic MIDI support for keyboards and controllers. Send and receive note and control change events.
Orx-midi is a wrapper around javax.midi.
## Usage
```kotlin
// -- list all midi devices
MidiDeviceDescription.list().forEach {
println("${it.name}, ${it.vendor} r:${it.receive} t:${it.transmit}")
}
// -- open a midi controller and listen for control changes
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}")
}
// or program changes
dev.programChange.listen {
println("${it.channel} ${it.program}")
}
```
## Further reading
The OPENRNDR guide has a [section on orx-midi](https://guide.openrndr.org/#/10_OPENRNDR_Extras/C04_Midi_controllers) that provides step-by-step documentation for using orx-midi in combination with OPENRNDR.

View File

@@ -0,0 +1,59 @@
package org.openrndr.extra.midi
enum class MidiEventType {
NOTE_ON,
NOTE_OFF,
CONTROL_CHANGED,
PROGRAM_CHANGE
}
class MidiEvent(val eventType: MidiEventType) {
var origin = Origin.DEVICE
var control: Int = 0
var program: Int = 0
var note: Int = 0
var channel: Int = 0
var value: Int = 0
var velocity: Int = 0
enum class Origin {
DEVICE,
USER
}
companion object {
fun noteOn(channel: Int, note: Int, velocity: Int): MidiEvent {
val midiEvent = MidiEvent(MidiEventType.NOTE_ON)
midiEvent.velocity = velocity
midiEvent.note = note
midiEvent.channel = channel
return midiEvent
}
fun noteOff(channel: Int, note: Int): MidiEvent {
val midiEvent = MidiEvent(MidiEventType.NOTE_OFF)
midiEvent.note = note
midiEvent.channel = channel
return midiEvent
}
fun controlChange(channel:Int, control: Int, value: Int): MidiEvent {
val midiEvent = MidiEvent(MidiEventType.CONTROL_CHANGED)
midiEvent.channel = channel
midiEvent.control = control
midiEvent.value = value
return midiEvent
}
fun programChange(channel:Int, program: Int): MidiEvent {
val midiEvent = MidiEvent(MidiEventType.PROGRAM_CHANGE)
midiEvent.channel = channel
midiEvent.program = program
return midiEvent
}
}
override fun toString(): String {
return "MidiEvent(eventType=$eventType, origin=$origin, program=$program, control=$control, note=$note, channel=$channel, value=$value, velocity=$velocity)"
}
}

View File

@@ -0,0 +1,175 @@
package org.openrndr.extra.midi
import org.openrndr.events.Event
import javax.sound.midi.*
data class MidiDeviceName(val name: String, val vendor: String)
class MidiDeviceCapabilities {
var receive: Boolean = false
var transmit: Boolean = false
override fun toString(): String {
return "MidiDeviceCapabilities(receive=$receive, transmit=$transmit)"
}
}
data class MidiDeviceDescription(val name: String, val vendor: String, val receive: Boolean, val transmit: Boolean) {
companion object {
fun list(): List<MidiDeviceDescription> {
val caps = mutableMapOf<MidiDeviceName, MidiDeviceCapabilities>()
val infos = MidiSystem.getMidiDeviceInfo()
for (info in infos) {
val device = MidiSystem.getMidiDevice(info)
val name = MidiDeviceName(info.name, info.vendor)
val deviceCaps = caps.getOrPut(name) { MidiDeviceCapabilities() }
if (device !is Sequencer && device !is Synthesizer) {
if (device.maxReceivers != 0 && device.maxTransmitters == 0) {
deviceCaps.receive = true
}
if (device.maxTransmitters != 0 && device.maxReceivers == 0) {
deviceCaps.transmit = true
}
}
}
return caps.map {
MidiDeviceDescription(it.key.name, it.key.vendor, it.value.receive, it.value.transmit)
}
}
}
fun open() : MidiTransceiver {
require(receive && transmit) {
"device should be a receiver and transmitter"
}
return MidiTransceiver.fromDeviceVendor(name, vendor)
}
}
class MidiTransceiver(val receiverDevice: MidiDevice, val transmitterDevicer: MidiDevice) {
companion object {
fun fromDeviceVendor(name: String, vendor: String): MidiTransceiver {
val infos = MidiSystem.getMidiDeviceInfo()
var receiverDevice: MidiDevice? = null
var transmitterDevice: MidiDevice? = null
for (info in infos) {
try {
val device = MidiSystem.getMidiDevice(info)
if (device !is Sequencer && device !is Synthesizer) {
if (info.vendor == vendor && info.name == name) {
if (device.maxTransmitters != 0 && device.maxReceivers == 0) {
transmitterDevice = device
}
if (device.maxReceivers != 0 && device.maxTransmitters == 0) {
receiverDevice = device
}
}
}
} catch (e: MidiUnavailableException) {
throw IllegalStateException("no midi available")
}
}
if (receiverDevice != null && transmitterDevice != null) {
receiverDevice.open()
transmitterDevice.open()
return MidiTransceiver(receiverDevice, transmitterDevice)
} else {
throw IllegalArgumentException("midi device not found ${name}:${vendor} ${receiverDevice} ${transmitterDevice}")
}
}
}
private val receiver = receiverDevice.receiver
private val transmitter = transmitterDevicer.transmitter
private inner class Destroyer: Thread() {
override fun run() {
destroy()
}
}
init {
transmitter.receiver = object : MidiDeviceReceiver {
override fun getMidiDevice(): MidiDevice? {
return null
}
override fun send(message: MidiMessage, timeStamp: Long) {
val cmd = message.message
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))
}
}
override fun close() {
}
}
// shut down midi if user calls `exitProcess(0)`
Runtime.getRuntime().addShutdownHook(Destroyer())
}
val controlChanged = Event<MidiEvent>("midi-transceiver::controller-changed")
val programChanged = Event<MidiEvent>("midi-transceiver::program-changed")
val noteOn = Event<MidiEvent>("midi-transceiver::note-on")
val noteOff = Event<MidiEvent>("midi-transceiver::note-off")
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)
}
} catch (e: InvalidMidiDataException) {
//
}
}
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)
}
} catch (e: InvalidMidiDataException) {
//
}
}
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)
}
} catch (e: InvalidMidiDataException) {
//
}
}
fun destroy() {
receiverDevice.close()
transmitterDevicer.close()
}
}
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}")
}
}