/* * DumpReceiver.java * * This file is part of jsresources.org */ /* * Copyright (c) 1999 - 2001 by Matthias Pfisterer * Copyright (c) 2003 by Florian Bomers * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ import java.io.PrintStream; import javax.sound.midi.MidiSystem; import javax.sound.midi.InvalidMidiDataException; import javax.sound.midi.Sequence; import javax.sound.midi.Track; import javax.sound.midi.MidiEvent; import javax.sound.midi.MidiMessage; import javax.sound.midi.ShortMessage; import javax.sound.midi.MetaMessage; import javax.sound.midi.SysexMessage; import javax.sound.midi.Receiver; /** Displays the file format information of a MIDI file. */ public class DumpReceiver implements Receiver { public static long seByteCount = 0; public static long smByteCount = 0; public static long seCount = 0; public static long smCount = 0; private static final String[] sm_astrKeyNames = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; private static final String[] sm_astrKeySignatures = {"Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#"}; private static final String[] SYSTEM_MESSAGE_TEXT = { "System Exclusive (should not be in ShortMessage!)", "MTC Quarter Frame: ", "Song Position: ", "Song Select: ", "Undefined", "Undefined", "Tune Request", "End of SysEx (should not be in ShortMessage!)", "Timing clock", "Undefined", "Start", "Continue", "Stop", "Undefined", "Active Sensing", "System Reset" }; private static final String[] QUARTER_FRAME_MESSAGE_TEXT = { "frame count LS: ", "frame count MS: ", "seconds count LS: ", "seconds count MS: ", "minutes count LS: ", "minutes count MS: ", "hours count LS: ", "hours count MS: " }; private static final String[] FRAME_TYPE_TEXT = { "24 frames/second", "25 frames/second", "30 frames/second (drop)", "30 frames/second (non-drop)", }; private PrintStream m_printStream; private boolean m_bDebug; private boolean m_bPrintTimeStampAsTicks; public DumpReceiver(PrintStream printStream) { this(printStream, false); } public DumpReceiver(PrintStream printStream, boolean bPrintTimeStampAsTicks) { m_printStream = printStream; m_bDebug = false; m_bPrintTimeStampAsTicks = bPrintTimeStampAsTicks; } public void close() { } public void send(MidiMessage message, long lTimeStamp) { String strMessage = null; if (message instanceof ShortMessage) { strMessage = decodeMessage((ShortMessage) message); } else if (message instanceof SysexMessage) { strMessage = decodeMessage((SysexMessage) message); } else if (message instanceof MetaMessage) { strMessage = decodeMessage((MetaMessage) message); } else { strMessage = "unknown message type"; } String strTimeStamp = null; if (m_bPrintTimeStampAsTicks) { strTimeStamp = "tick " + lTimeStamp + ": "; } else { if (lTimeStamp == -1L) { strTimeStamp = "timestamp [unknown]: "; } else { strTimeStamp = "timestamp " + lTimeStamp + " us: "; } } m_printStream.println(strTimeStamp + strMessage); } public String decodeMessage(ShortMessage message) { String strMessage = null; switch (message.getCommand()) { case 0x80: strMessage = "note Off " + getKeyName(message.getData1()) + " velocity: " + message.getData2(); break; case 0x90: strMessage = "note On " + getKeyName(message.getData1()) + " velocity: " + message.getData2(); break; case 0xa0: strMessage = "polyphonic key pressure " + getKeyName(message.getData1()) + " pressure: " + message.getData2(); break; case 0xb0: strMessage = "control change " + message.getData1() + " value: " + message.getData2(); break; case 0xc0: strMessage = "program change " + message.getData1(); break; case 0xd0: strMessage = "key pressure " + getKeyName(message.getData1()) + " pressure: " + message.getData2(); break; case 0xe0: strMessage = "pitch wheel change " + get14bitValue(message.getData1(), message.getData2()); break; case 0xF0: strMessage = SYSTEM_MESSAGE_TEXT[message.getChannel()]; switch (message.getChannel()) { case 0x1: int nQType = (message.getData1() & 0x70) >> 4; int nQData = message.getData1() & 0x0F; if (nQType == 7) { nQData = nQData & 0x1; } strMessage += QUARTER_FRAME_MESSAGE_TEXT[nQType] + nQData; if (nQType == 7) { int nFrameType = (message.getData1() & 0x06) >> 1; strMessage += ", frame type: " + FRAME_TYPE_TEXT[nFrameType]; } break; case 0x2: strMessage += get14bitValue(message.getData1(), message.getData2()); break; case 0x3: strMessage += message.getData1(); break; } break; default: strMessage = "unknown message: status = " + message.getStatus() + ", byte1 = " + message.getData1() + ", byte2 = " + message.getData2(); break; } if (message.getCommand() != 0xF0) { int nChannel = message.getChannel() + 1; String strChannel = "channel " + nChannel + ": "; strMessage = strChannel + strMessage; } smCount++; smByteCount+=message.getLength(); return "["+getHexString(message)+"] "+strMessage; } public String decodeMessage(SysexMessage message) { byte[] abData = message.getData(); String strMessage = null; // System.out.println("sysex status: " + message.getStatus()); if (message.getStatus() == SysexMessage.SYSTEM_EXCLUSIVE) { strMessage = "Sysex message: F0" + getHexString(abData); } else if (message.getStatus() == SysexMessage.SPECIAL_SYSTEM_EXCLUSIVE) { strMessage = "Continued Sysex message F7" + getHexString(abData); seByteCount--; // do not count the F7 } seByteCount += abData.length + 1; seCount++; // for the status byte return strMessage; } public String decodeMessage(MetaMessage message) { byte[] abMessage = message.getMessage(); byte[] abData = message.getData(); int nDataLength = message.getLength(); String strMessage = null; // System.out.println("data array length: " + abData.length); switch (message.getType()) { case 0: int nSequenceNumber = ((abData[0] & 0xFF) << 8) | (abData[1] & 0xFF); strMessage = "Sequence Number: " + nSequenceNumber; break; case 1: String strText = new String(abData); strMessage = "Text Event: " + strText; break; case 2: String strCopyrightText = new String(abData); strMessage = "Copyright Notice: " + strCopyrightText; break; case 3: String strTrackName = new String(abData); strMessage = "Sequence/Track Name: " + strTrackName; break; case 4: String strInstrumentName = new String(abData); strMessage = "Instrument Name: " + strInstrumentName; break; case 5: String strLyrics = new String(abData); strMessage = "Lyric: " + strLyrics; break; case 6: String strMarkerText = new String(abData); strMessage = "Marker: " + strMarkerText; break; case 7: String strCuePointText = new String(abData); strMessage = "Cue Point: " + strCuePointText; break; case 0x20: int nChannelPrefix = abData[0] & 0xFF; strMessage = "MIDI Channel Prefix: " + nChannelPrefix; break; case 0x2F: strMessage = "End of Track"; break; case 0x51: int nTempo = ((abData[0] & 0xFF) << 16) | ((abData[1] & 0xFF) << 8) | (abData[2] & 0xFF); // tempo in microseconds per beat float bpm = convertTempo(nTempo); // truncate it to 2 digits after dot bpm = (float) (Math.round(bpm*100.0f)/100.0f); strMessage = "Set Tempo: "+bpm+" bpm"; break; case 0x54: // System.out.println("data array length: " + abData.length); strMessage = "SMTPE Offset: " + (abData[0] & 0xFF) + ":" + (abData[1] & 0xFF) + ":" + (abData[2] & 0xFF) + "." + (abData[3] & 0xFF) + "." + (abData[4] & 0xFF); break; case 0x58: strMessage = "Time Signature: " + (abData[0] & 0xFF) + "/" + (1 << (abData[1] & 0xFF)) + ", MIDI clocks per metronome tick: " + (abData[2] & 0xFF) + ", 1/32 per 24 MIDI clocks: " + (abData[3] & 0xFF); break; case 0x59: String strGender = (abData[1] == 1) ? "minor" : "major"; strMessage = "Key Signature: " + sm_astrKeySignatures[abData[0] + 7] + " " + strGender; break; case 0x7F: // TODO: decode vendor code, dump data in rows String strDataDump = getHexString(abData); strMessage = "Sequencer-Specific Meta event: " + strDataDump; break; default: String strUnknownDump = getHexString(abData); strMessage = "unknown Meta event: " + strUnknownDump; break; } return strMessage; } public static String getKeyName(int nKeyNumber) { if (nKeyNumber > 127) { return "illegal value"; } else { int nNote = nKeyNumber % 12; int nOctave = nKeyNumber / 12; return sm_astrKeyNames[nNote] + (nOctave - 1); } } public static int get14bitValue(int nLowerPart, int nHigherPart) { return (nLowerPart & 0x7F) | ((nHigherPart & 0x7F) << 7); } private static int signedByteToUnsigned(byte b) { return b & 0xFF; } // convert from microseconds per quarter note to beats per minute and vice versa private static float convertTempo(float value) { if (value <= 0) { value = 0.1f; } return 60000000.0f / value; } private static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; public static String getHexString(byte[] aByte) { StringBuffer sbuf = new StringBuffer(aByte.length * 3 + 2); for (int i = 0; i < aByte.length; i++) { sbuf.append(' '); sbuf.append(hexDigits[(aByte[i] & 0xF0) >> 4]); sbuf.append(hexDigits[aByte[i] & 0x0F]); /*byte bhigh = (byte) ((aByte[i] & 0xf0) >> 4); sbuf.append((char) (bhigh > 9 ? bhigh + 'A' - 10: bhigh + '0')); byte blow = (byte) (aByte[i] & 0x0f); sbuf.append((char) (blow > 9 ? blow + 'A' - 10: blow + '0'));*/ } return new String(sbuf); } private static String intToHex(int i) { return ""+hexDigits[(i & 0xF0) >> 4] +hexDigits[i & 0x0F]; } public static String getHexString(ShortMessage sm) { // bug in J2SDK 1.4.1 // return getHexString(sm.getMessage()); int status = sm.getStatus(); String res = intToHex(sm.getStatus()); // if one-byte message, return switch (status) { case 0xF6: // Tune Request case 0xF7: // EOX // System real-time messages case 0xF8: // Timing Clock case 0xF9: // Undefined case 0xFA: // Start case 0xFB: // Continue case 0xFC: // Stop case 0xFD: // Undefined case 0xFE: // Active Sensing case 0xFF: return res; } res += ' '+intToHex(sm.getData1()); // if 2-byte message, return switch (status) { case 0xF1: // MTC Quarter Frame case 0xF3: // Song Select return res; } switch (sm.getCommand()) { case 0xC0: case 0xD0: return res; } // 3-byte messages left res += ' '+intToHex(sm.getData2()); return res; } } /*** DumpReceiver.java ***/