/* * Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.ByteArrayOutputStream; import javax.sound.midi.MidiDevice; import javax.sound.midi.MidiMessage; import javax.sound.midi.MidiSystem; import javax.sound.midi.Receiver; import javax.sound.midi.ShortMessage; import javax.sound.midi.SysexMessage; import javax.sound.midi.Transmitter; /** * @test * @bug 4782924 * @bug 4812168 * @bug 4356787 * @summary MIDI i/o. This is an interactive test! Start it and follow the * instructions. * @run main/manual IOLoop */ public class IOLoop { private static final int LONG_SYSEX_LENGTH = 2000; private static Receiver receiver; private static Transmitter transmitter; private static MidiMessage receivedMessage; private static ByteArrayOutputStream baos; private static int expectedBytes; private static int receivedBytes; private static Object lock = new Object(); private static long lastTimestamp; public static void main(String[] args) throws Exception { ShortMessage sMsg = new ShortMessage(); SysexMessage syMsg = new SysexMessage(); boolean isTestPassed = true; boolean sysExTestPassed = true; boolean isTestExecuted = true; out("To run this test successfully, you need to have attached"); out(" your MIDI out port with the MIDI in port."); MidiDevice inDev = null; MidiDevice outDev = null; // setup try { MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo(); int devNum = Integer.decode(args[0]).intValue(); out("-> opening Transmitter from "+infos[devNum]); inDev = MidiSystem.getMidiDevice(infos[devNum]); inDev.open(); transmitter = inDev.getTransmitter(); Receiver testReceiver = new TestReceiver(); transmitter.setReceiver(testReceiver); devNum = Integer.decode(args[1]).intValue(); out("-> opening Receiver from "+infos[devNum]); outDev = MidiSystem.getMidiDevice(infos[devNum]); outDev.open(); receiver = outDev.getReceiver(); } catch (Exception e) { System.out.println(e); System.out.println("Cannot test!"); return; } // test sMsg.setMessage(ShortMessage.NOTE_OFF | 0, 27, 100); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.NOTE_OFF | 0, 0, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.NOTE_OFF | 15, 127, 127); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.NOTE_ON | 4, 27, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.NOTE_ON | 0, 0, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.NOTE_ON | 15, 127, 127); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.POLY_PRESSURE | 11, 98, 99); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.POLY_PRESSURE | 0, 0, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.POLY_PRESSURE | 15, 127, 127); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.CONTROL_CHANGE | 13, 1, 63); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.CONTROL_CHANGE | 0, 0, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.CONTROL_CHANGE | 15, 127, 127); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.PROGRAM_CHANGE | 2, 120, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.PROGRAM_CHANGE | 0, 0, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.PROGRAM_CHANGE | 15, 127, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.CHANNEL_PRESSURE | 6, 30, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.CHANNEL_PRESSURE | 0, 0, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.CHANNEL_PRESSURE | 15, 127, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.PITCH_BEND | 6, 56, 4); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.PITCH_BEND | 0, 0, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.PITCH_BEND | 15, 127, 127); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.MIDI_TIME_CODE, 0, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.MIDI_TIME_CODE, 127, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.SONG_POSITION_POINTER, 1, 77); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.SONG_POSITION_POINTER, 0, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.SONG_POSITION_POINTER, 127, 127); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.SONG_SELECT, 51, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.SONG_SELECT, 0, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.SONG_SELECT, 127, 0); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.TUNE_REQUEST); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.TIMING_CLOCK); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.START); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.CONTINUE); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.STOP); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.ACTIVE_SENSING); isTestPassed &= testMessage(sMsg); sMsg.setMessage(ShortMessage.SYSTEM_RESET); isTestPassed &= testMessage(sMsg); syMsg.setMessage(new byte[]{(byte) 0xF0, (byte) 0xF7}, 2); isTestPassed &= testMessage(syMsg); syMsg.setMessage(new byte[]{(byte) 0xF0, 0x01, (byte) 0xF7}, 3); isTestPassed &= testMessage(syMsg); syMsg.setMessage(new byte[]{(byte) 0xF0, 0x02, 0x03, (byte) 0xF7}, 4); isTestPassed &= testMessage(syMsg); syMsg.setMessage(new byte[]{(byte) 0xF0, 0x04, 0x05, 0x06, (byte) 0xF7}, 5); isTestPassed &= testMessage(syMsg); if (isTestPassed) { byte[] sysexArray = new byte[LONG_SYSEX_LENGTH]; sysexArray[0] = (byte) 0xF0; for (int i = 1; i < sysexArray.length; i++) { sysexArray[i] = (byte) (i % 0x80); } // syMsg.setMessage(new byte[]{(byte) 0xF7, (byte) ShortMessage.START}, 2); // sMsg.setMessage(ShortMessage.START); // isTestPassed &= testMessage(syMsg, sMsg, DEFAULT_SLEEP_INTERVALL); for (int trial = sysexArray.length; trial > 4; trial -= 1234) { sleep(500); sysexArray[trial - 1] = (byte) 0xF7; syMsg.setMessage(sysexArray, trial); sysExTestPassed &= testMessage(syMsg); break; } } // cleanup receiver.close(); transmitter.close(); inDev.close(); outDev.close(); if (isTestExecuted) { if (isTestPassed && sysExTestPassed) { out("Test PASSED."); } else { if (isTestPassed && !sysExTestPassed && (System.getProperty("os.name").startsWith("Windows"))) { out("Some Windows MIDI i/o drivers have a problem with larger "); out("sys ex messages. The failing sys ex cases are OK, therefore."); out("Test PASSED."); } else { throw new Exception("Test FAILED."); } } } else { out("Test NOT FAILED"); } } private static boolean testMessage(MidiMessage message) { receivedMessage = null; baos = new ByteArrayOutputStream(); expectedBytes = message.getLength(); receivedBytes = 0; System.out.print("Sending message " + getMessageString(message.getMessage())+"..."); receiver.send(message, -1); /* sending 3 bytes can roughly be done in 1 millisecond, * so this estimate waits at max 3 times longer than the message takes, * plus a little offset to allow the MIDI subsystem some processing time */ int offset = 300; // standard offset 100 millis if (message instanceof SysexMessage) { // add a little processing time to sysex messages offset += 1000; } if (receivedBytes < expectedBytes) { sleep(expectedBytes + offset); } boolean equal; byte[] data = baos.toByteArray(); if (data.length > 0) { equal = messagesEqual(message.getMessage(), data); } else { equal = messagesEqual(message, receivedMessage); if (receivedMessage != null) { data = receivedMessage.getMessage(); } else { data = null; } } if (!equal) { if ((message.getStatus() & 0xF0) == ShortMessage.PITCH_BEND) { out("NOT failed (may expose a bug in ALSA)"); equal = true; sleep(100); } if ((message.getStatus() == 0xF6) && (message.getLength() == 1)) { out("NOT failed (may expose an issue on Solaris)"); equal = true; sleep(100); } else if ((message.getStatus()) == 0xF0 && message.getLength() < 4) { out("NOT failed (not a correct sys ex message)"); equal = true; sleep(200); } else { out("FAILED:"); out(" received as " + getMessageString(data)); } } else { System.out.println("OK"); } return equal; } private static void sleep(int milliseconds) { synchronized(lock) { try { lock.wait(milliseconds); } catch (InterruptedException e) { } } } private static String getMessageString(byte[] data) { String s; if (data == null) { s = "<null>"; } else if (data.length == 0) { s = "0-sized array"; } else { int status = data[0] & 0xFF; if (data.length <= 3) { if (status < 240) { s = "command 0x" + Integer.toHexString(status & 0xF0) + " channel " + (status & 0x0F); } else { s = "status 0x" + Integer.toHexString(status); } if (data.length > 1) { s += " data 0x" + Integer.toHexString(data[1] & 0xFF); if (data.length > 2) { s += " 0x" + Integer.toHexString(data[2] & 0xFF); } } } else { s = "status " + Integer.toHexString(status)+" and length "+data.length+" bytes"; } } return s; } private static boolean messagesEqual(MidiMessage m1, MidiMessage m2) { if (m1 == null || m2 == null) { return false; } if (m1.getLength() != m2.getLength()) { return false; } byte[] array1 = m1.getMessage(); byte[] array2 = m2.getMessage(); return messagesEqual(array1, array2); } private static boolean messagesEqual(byte[] a1, byte[] a2) { if (a1.length != a2.length) return false; for (int i = 0; i < a1.length; i++) { if (a1[i] != a2[i]) { return false; } } return true; } private static void out(String s) { System.out.println(s); System.out.flush(); } private static String canIn(MidiDevice dev) { if (dev.getMaxTransmitters() != 0) { return "IN "; } return " "; } private static String canOut(MidiDevice dev) { if (dev.getMaxReceivers() != 0) { return "OUT "; } return " "; } private static void checkTimestamp(long timestamp) { // out("checking timestamp..."); if (timestamp < 1) { out("timestamp 0 or negative!"); } if (timestamp < lastTimestamp) { out("timestamp not progressive!"); } lastTimestamp = timestamp; } private static class TestReceiver implements Receiver { public void send(MidiMessage message, long timestamp) { //System.out.print(""+message.getLength()+".."); checkTimestamp(timestamp); try { receivedMessage = message; if (message.getStatus() == 0xF0 || (message.getLength() > 3 && message.getStatus() != 0xF7)) { // sys ex message byte[] data = message.getMessage(); baos.write(data); receivedBytes += data.length; } else if (message.getStatus() == 0xF7) { // sys ex cont'd message byte[] data = message.getMessage(); // ignore the prepended 0xF7 baos.write(data, 1, data.length-1); receivedBytes += (data.length - 1); } else { receivedBytes += message.getLength(); } if (receivedBytes >= expectedBytes) { synchronized(lock) { lock.notify(); } } System.out.print(""+receivedBytes+".."); } catch (Exception e) { e.printStackTrace(); } } public void close() { } } }