package org.herac.tuxguitar.io.midi; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import org.herac.tuxguitar.io.midi.base.MidiEvent; import org.herac.tuxguitar.io.midi.base.MidiMessage; import org.herac.tuxguitar.io.midi.base.MidiSequence; import org.herac.tuxguitar.io.midi.base.MidiTrack; public class MidiFileReader implements MidiFileHeader { private class MidiTrackReaderHelper { protected long remainingBytes; protected int runningStatusByte; protected long ticks = 0; protected MidiTrackReaderHelper(long ticks, long remainingBytes, int runningStatusByte) { this.ticks = ticks; this.remainingBytes = remainingBytes; this.runningStatusByte = runningStatusByte; } } public static boolean CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX = true; private static final int STATUS_META = 4; private static final int STATUS_NONE = 0; private static final int STATUS_ONE_BYTE = 1; private static final int STATUS_SYSEX = 3; private static final int STATUS_TWO_BYTES = 2; private static int getType(int statusByte) { if (statusByte < 0xf0) { int command = statusByte & 0xf0; if (command == 0x80 || command == 0x90 || command == 0xa0 || command == 0xb0 || command == 0xe0) { return STATUS_TWO_BYTES; } else if (command == 0xc0 || command == 0xd0) { return STATUS_ONE_BYTE; } return STATUS_NONE; } else if (statusByte == 0xf0 || statusByte == 0xf7) { return STATUS_SYSEX; } else if (statusByte == 0xff) { return STATUS_META; } else { return STATUS_NONE; } } private static MidiEvent readEvent(DataInputStream in, MidiTrackReaderHelper helper) throws MidiFileException, IOException { int statusByte = readUnsignedByte(in, helper); int savedByte = 0; boolean runningStatusApplies = false; if (statusByte < 0x80) { if (helper.runningStatusByte != -1) { runningStatusApplies = true; savedByte = statusByte; statusByte = helper.runningStatusByte; } else { throw new MidiFileException("corrupt MIDI file: status byte missing"); } } int type = getType(statusByte); if (type == STATUS_ONE_BYTE) { int data = 0; if (runningStatusApplies) { data = savedByte; } else { data = readUnsignedByte(in, helper); helper.runningStatusByte = statusByte; } return new MidiEvent(MidiMessage.shortMessage((statusByte & 0xF0), (statusByte & 0x0F), data), helper.ticks); } else if (type == STATUS_TWO_BYTES) { int data1 = 0; if (runningStatusApplies) { data1 = savedByte; } else { data1 = readUnsignedByte(in, helper); helper.runningStatusByte = statusByte; } return new MidiEvent(MidiMessage.shortMessage((statusByte & 0xF0), (statusByte & 0x0F), data1, readUnsignedByte(in, helper)), helper.ticks); } else if (type == STATUS_SYSEX) { if (CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX) { helper.runningStatusByte = -1; } int dataLength = (int) readVariableLengthQuantity(in, helper); byte[] data = new byte[dataLength]; for (int i = 0; i < dataLength; i++) { data[i] = (byte) readUnsignedByte(in, helper); } } else if (type == STATUS_META) { if (CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX) { helper.runningStatusByte = -1; } int typeByte = readUnsignedByte(in, helper); int dataLength = (int) readVariableLengthQuantity(in, helper); byte[] data = new byte[dataLength]; for (int i = 0; i < dataLength; i++) { data[i] = (byte) readUnsignedByte(in, helper); } return new MidiEvent(MidiMessage.metaMessage(typeByte, data), helper.ticks); } return null; } public static int readUnsignedByte(DataInputStream dataInputStream, MidiTrackReaderHelper helper) throws IOException { helper.remainingBytes--; return dataInputStream.readUnsignedByte(); } public static long readVariableLengthQuantity(DataInputStream in, MidiTrackReaderHelper helper) throws MidiFileException, IOException { int count = 0; long value = 0; while (count < 4) { int data = readUnsignedByte(in, helper); count++; value <<= 7; value |= (data & 0x7f); if (data < 128) { return value; } } throw new MidiFileException( "not a MIDI file: unterminated variable-length quantity"); } public MidiSequence getSequence(InputStream stream) throws MidiFileException, IOException { DataInputStream in = new DataInputStream(stream); if (in.readInt() != HEADER_MAGIC) { throw new MidiFileException("not a MIDI file: wrong header magic"); } int headerLength = in.readInt(); if (headerLength < HEADER_LENGTH) { throw new MidiFileException("corrupt MIDI file: wrong header length"); } int type = in.readShort(); if (type < 0 || type > 2) { throw new MidiFileException("corrupt MIDI file: illegal type"); } if (type == 2) { throw new MidiFileException( "this implementation doesn't support type 2 MIDI files"); } int trackCount = in.readShort(); if (trackCount <= 0) { throw new MidiFileException( "corrupt MIDI file: number of tracks must be positive"); } if (type == 0 && trackCount != 1) { throw new MidiFileException( "corrupt MIDI file: type 0 files must contain exactely one track"); } float divisionType = -1.0F; int resolution = -1; int division = in.readUnsignedShort(); if ((division & 0x8000) != 0) { int frameType = -((division >>> 8) & 0xFF); if (frameType == 24) { divisionType = MidiSequence.SMPTE_24; } else if (frameType == 25) { divisionType = MidiSequence.SMPTE_25; } else if (frameType == 29) { divisionType = MidiSequence.SMPTE_30DROP; } else if (frameType == 30) { divisionType = MidiSequence.SMPTE_30; } else { throw new MidiFileException( "corrupt MIDI file: illegal frame division type"); } resolution = division & 0xff; } else { divisionType = MidiSequence.PPQ; resolution = division & 0x7fff; } in.skip(headerLength - HEADER_LENGTH); MidiSequence sequence = new MidiSequence(divisionType, resolution); for (int i = 0; i < trackCount; i++) { MidiTrack track = new MidiTrack(); sequence.addTrack(track); readTrack(in, track); } in.close(); return sequence; } private void readTrack(DataInputStream in, MidiTrack track) throws MidiFileException, IOException { while (true) { if (in.readInt() == TRACK_MAGIC) { break; } int chunkLength = in.readInt(); if (chunkLength % 2 != 0) { chunkLength++; } in.skip(chunkLength); } MidiTrackReaderHelper helper = new MidiTrackReaderHelper(0, in.readInt(), -1); while (helper.remainingBytes > 0) { helper.ticks += readVariableLengthQuantity(in, helper); MidiEvent event = readEvent(in, helper); if (event != null) { track.add(event); } } } }