Open chriscoomber opened 4 years ago
From looking a bit more, it looks like upon open()
, SequencerImpl does two things (among other things):
device.getTransmitter().setReceiver(foo)
).SequencerImpl doesn't have its own Transmitters, and I think it should.
The bits in bold are bugs. There's no way I can see that you could configure a collection of MIDI devices that includes the SequencerImpl to talk to each other in any way other then all of them talking to all of them. You can't finely tune who the SequencerImpl sends events to.
For anyone else seeing this issue, I've managed to hack around it for my use-case (Sequencer -> Custom Processing -> Synth) by doing the following (kotlin code):
// create synth
synth = SoftSynthesizer()
synth.open()
synth.loadAllInstruments(SF2Soundbank(activity!!.assets.open("test.sf2")))
// get sequencer
val sequencer = MidiSystem.getSequencer(false)
// create custom processor
val customEventProcessor = CustomEventProcessor()
// Sequencer -> custom processor. Due to a bug, we have to go via the MidiSystem in a
// special way.
MidiSystem.addMidiDevice(customEventProcessor)
// Custom processor -> synth
customEventProcessor.transmitter.receiver = synth.receiver
// Play
sequencer.open()
val fileReader = StandardMidiFileReader()
val sequence = fileReader.getSequence(activity!!.assets.open("test.mid"))
sequencer.sequence = sequence
sequencer.start()
where your custom processing (between the sequencer and the synth) goes in this class:
class CustomEventProcessor : MidiDevice {
private val recv = object : Receiver {
override fun send(message: MidiMessage, ms: Long) {
// Custom message processing goes here
Log.d("test", "got message: $message")
// Pass on to the transmitter
trans.receiver?.send(message, ms)
}
override fun close() {}
}
private val trans = WeakTransmitter()
override fun getDeviceInfo(): MidiDevice.Info {
return MidiDevice.Info("test", "example", "custom processor", "0.1")
}
// Register one receiver with the MidiSystem. This is for the sequencer to connect to us.
override fun getMaxReceivers() = 1
override fun getReceiver(): Receiver = recv
override fun getReceivers(): MutableList<Receiver> = MutableList(1) { recv }
// Don't register the transmitter with MidiSystem - advertise a count of 0. The sequencer
// implementation is bugged and will point this at itself, stealing it from legitimate
// devices. We can still use the transmitter manually.
override fun getMaxTransmitters() = 0
override fun getTransmitter(): Transmitter = trans
override fun getTransmitters(): MutableList<Transmitter> = mutableListOf()
override fun open() {}
override fun isOpen() = true
override fun close() {}
override fun getMicrosecondPosition(): Long { TODO("Not yet implemented") }
}
// Convenient simple implementation of a Transmitter
class WeakTransmitter : Transmitter {
private var destination: WeakReference<Receiver>? = null
override fun getReceiver(): Receiver? {
return destination?.get()
}
override fun setReceiver(recv: Receiver?) {
destination = recv?.let { WeakReference(recv) }
}
override fun close() {
}
}
From looking a bit more, it looks like upon
open()
, SequencerImpl does two things (among other things):
- Find all the receivers of all devices that have been added to MidiSystem. It will send events (during playback) to all of these. You cannot control which of these receive the events.
- Finds all the transmitters of all devices that have been added to MidiSystem. It sets itself as the destination receiver (for recording) for those transmitter, potentially stealing that transmitter from another device that was set up properly (with
device.getTransmitter().setReceiver(foo)
).SequencerImpl doesn't have its own Transmitters, and I think it should.
The bits in bold are bugs. There's no way I can see that you could configure a collection of MIDI devices that includes the SequencerImpl to talk to each other in any way other then all of them talking to all of them. You can't finely tune who the SequencerImpl sends events to.
For anyone else seeing this issue, I've managed to hack around it for my use-case (Sequencer -> Custom Processing -> Synth) by doing the following (kotlin code):
// create synth synth = SoftSynthesizer() synth.open() synth.loadAllInstruments(SF2Soundbank(activity!!.assets.open("test.sf2"))) // get sequencer val sequencer = MidiSystem.getSequencer(false) // create custom processor val customEventProcessor = CustomEventProcessor() // Sequencer -> custom processor. Due to a bug, we have to go via the MidiSystem in a // special way. MidiSystem.addMidiDevice(customEventProcessor) // Custom processor -> synth customEventProcessor.transmitter.receiver = synth.receiver // Play sequencer.open() val fileReader = StandardMidiFileReader() val sequence = fileReader.getSequence(activity!!.assets.open("test.mid")) sequencer.sequence = sequence sequencer.start()
where your custom processing (between the sequencer and the synth) goes in this class:
class CustomEventProcessor : MidiDevice { private val recv = object : Receiver { override fun send(message: MidiMessage, ms: Long) { // Custom message processing goes here Log.d("test", "got message: $message") // Pass on to the transmitter trans.receiver?.send(message, ms) } override fun close() {} } private val trans = WeakTransmitter() override fun getDeviceInfo(): MidiDevice.Info { return MidiDevice.Info("test", "example", "custom processor", "0.1") } // Register one receiver with the MidiSystem. This is for the sequencer to connect to us. override fun getMaxReceivers() = 1 override fun getReceiver(): Receiver = recv override fun getReceivers(): MutableList<Receiver> = MutableList(1) { recv } // Don't register the transmitter with MidiSystem - advertise a count of 0. The sequencer // implementation is bugged and will point this at itself, stealing it from legitimate // devices. We can still use the transmitter manually. override fun getMaxTransmitters() = 0 override fun getTransmitter(): Transmitter = trans override fun getTransmitters(): MutableList<Transmitter> = mutableListOf() override fun open() {} override fun isOpen() = true override fun close() {} override fun getMicrosecondPosition(): Long { TODO("Not yet implemented") } } // Convenient simple implementation of a Transmitter class WeakTransmitter : Transmitter { private var destination: WeakReference<Receiver>? = null override fun getReceiver(): Receiver? { return destination?.get() } override fun setReceiver(recv: Receiver?) { destination = recv?.let { WeakReference(recv) } } override fun close() { } }
Thanks a lot. It was really helpful :)
I'm trying to write code which plays MIDI notes from file.
I want to be able to intercept packets sent by the sequencer, and then process them, before sending them on to the synthesizer.
Sequencer -> Custom processing -> Synthesizer
I don't want the sequencer to send events directly to the Synthesizer!
Based on reading the JavaX docs, I think I should be able to do the following:
However, I'm seeing
jp.kshoji.javax.sound.midi.MidiUnavailableException: Transmitter not found
. From looking at the code, it seems like the Sequencer isn't created with any Transmitters. Bizarrely, it seems to get its transmitters from other devices, usingMidiSystemUtils.getTransmitters()
. This doesn't seem right. It is contrary to the documentation at https://docs.oracle.com/javase/7/docs/api/javax/sound/midi/MidiSystem.html#getSequencer(boolean), which indicates that the Sequencer should have no open transmitters, but that there should still be transmitters that I can get so that I can set their receivers.This issue seems related to #2, but the resolution of that doesn't seem applicable to me, because I want to control which device is connected to which other device, using transmitters/receivers.