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);
}
}
}
}