package jmsltestsuite; /** * Illustrate a bug opening and closing midi input device on Mac. Works ok on Windows. * * This program opens an midi device of choice. If it's an input device it'll get a transmitter and * give the transmitter a receiver which prints input that arrives from external midi hardware. * If it's an output device, it's send it a MIDI note. * * BUG: only works first time I open() and close() the device if it is an input device * * BEHAVIOR: * the requested device is opened and closed in a loop. During the first loop, moving sliders on external midi device * shows console output in response to fader moves for a couple of seconds. Then the device is closed. * Next times through the loop, the input no longer works, boo. Even worse, sometimes close() hangs. * If you comment out device.close(), the program works for multiple loops. * * This bug is limited to input devices on OSX. If you run the loop with output device, it opens, * plays a note, and closes repeatedly without incident. Or if you run this with an input device under Windows * it works fine, too. * * Nick Didkovsky Dec 3, 2017 * */ import java.awt.Dimension; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.sound.midi.*; import javax.swing.JFrame; public class TestMidiReceiverMultipleOpenClose { boolean isInputDevice(MidiDevice midiDevice) { boolean isInputDevice = true; try { midiDevice.getTransmitter(); } catch (MidiUnavailableException e) { isInputDevice = false; } return isInputDevice; } boolean isOutputDevice(MidiDevice midiDevice) { boolean isOutputDevice = true; try { midiDevice.getReceiver(); } catch (MidiUnavailableException e) { isOutputDevice = false; } return isOutputDevice; } public void listMidiDevices() { System.out.println("Your MIDI Devices"); MidiDevice midiDevice = null; MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo(); for (int i = 0; i < infos.length; i++) { try { midiDevice = MidiSystem.getMidiDevice(infos[i]); System.out.println(i + ") " + infos[i] + (isInputDevice(midiDevice) ? " INPUT" : "") + (isOutputDevice(midiDevice) ? " OUTPUT" : "")); } catch (MidiUnavailableException e) { System.out.println(midiDevice.getDeviceInfo().getName() + " " + e); } } } /* * open midi device. If it's an input device, set up a receiver for its * transmitter, wait a couple seconds so user can wiggle some knobs. * (receiver prints to console) Then close(). * * BUG: Receiving input from external devices only works the first time. * Once it's closed I can't open it again. BUG: Sometimes close() hangs. */ public void openAndClose(int deviceIndex) throws MidiUnavailableException, InterruptedException, InvalidMidiDataException { MidiDevice dev = MidiSystem.getMidiDevice(MidiSystem.getMidiDeviceInfo()[deviceIndex]); System.out.println("opening [" + deviceIndex + "], \"" + dev.getDeviceInfo().getName() + "\""); dev.open(); // now read some external data if it's an input device, or send a midi // note if it's an output device Receiver receiver = null; Transmitter transmitter = null; if (isInputDevice(dev)) { transmitter = dev.getTransmitter(); System.out.println("Got transmitter " + transmitter.toString()); receiver = createReceiver(dev.getDeviceInfo().getName(), transmitter); System.out.println("Created receiver; go ahead and wiggle some knobs on external midi control surface"); Thread.sleep(2000); } if (isOutputDevice(dev)) { long timeStamp = -1; ShortMessage msg = new ShortMessage(); System.out.print("note_on...."); msg.setMessage(ShortMessage.NOTE_ON, 1, 60, 80); javax.sound.midi.Receiver rcvr = dev.getReceiver(); rcvr.send(msg, timeStamp); Thread.sleep(200); System.out.println("note_off"); msg.setMessage(ShortMessage.NOTE_ON, 1, 0, 0); rcvr.send(msg, timeStamp); Thread.sleep(200); } System.out.print("dev.isOpen()=" + dev.isOpen()); boolean SHOW_BUG = true; // only run into trouble with input devices if (SHOW_BUG) { System.out.print(", closing device..."); dev.close(); System.out.print("closed. "); } System.out.println(" dev.isOpen()=" + dev.isOpen()); } private Receiver createReceiver(String name, Transmitter transmitter) throws MidiUnavailableException { MidiInputReceiver receiver = new MidiInputReceiver(name); transmitter.setReceiver(receiver); System.out.println("New receiver " + receiver.toString()); return receiver; } public static void main(String[] args) { JFrame jf = new JFrame("close to exit"); jf.setSize(new Dimension(200, 100)); jf.setVisible(true); jf.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent arg0) { System.exit(0); } }); TestMidiReceiverMultipleOpenClose test = new TestMidiReceiverMultipleOpenClose(); test.listMidiDevices(); // CHANGE ME TO AN INPUT DEVICE AFTER SEEING THE "Your MIDI Devices" // LISTING int deviceToTorture = 1; try { for (int i = 0; i < 10; i++) { System.out.println("\n\n***** Trial " + (i + 1) + "*****"); test.openAndClose(deviceToTorture); Thread.sleep(1000); } System.out.println("done"); } catch (MidiUnavailableException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (InvalidMidiDataException e) { e.printStackTrace(); } } } class MidiInputReceiver implements Receiver { private String name; private static int kount = 1; public MidiInputReceiver(String name) { this.name = name + "_" + kount++; } public void send(MidiMessage msg, long timeStamp) { System.out.println(name + ", midi msg " + msg.getStatus() + " received at " + timeStamp); } public void close() { } }