/*
* ReadTrack.java
* Copyright 2001 Michael Castleman, mlc67@columbia.edu
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.io.*;
/**
* Reads a chunk from a {@link InputStream} and aids in parsing it if it's
* a track.
*/
final class ReadTrack {
/** Little data structure representing a MIDI event */
public static class Event {
/** Time since previous event */
public int deltaTime;
/** Status byte (indicates type of event) */
public byte status;
/** actual data for the event. */
public byte[] data;
/** creates a new Event
*/
public Event(int deltaTime, byte status, byte[] data) {
this.deltaTime = deltaTime;
this.status = status;
this.data = data;
}
}
protected String chunkType;
protected byte [] data;
private int inPos;
private byte lastStatus;
/**
* Reads a chunk from the stream and makes it ready for parsing.
* @throws IOException if there's a problem reading the data.
*/
public ReadTrack(InputStream in) throws IOException {
byte[] b = new byte[4];
in.read(b);
chunkType = new String(b);
in.read(b);
int chunkLen = (((b[0]&0xFF) << 24) |
((b[1]&0xFF) << 16) |
((b[2]&0xFF) << 8) |
((b[3]&0xFF)));
System.err.println(chunkType + " " + chunkLen + " (" + b[0] + "," + b[1] + "," + b[2] + "," + b[3] + ")");
data = new byte[chunkLen];
in.read(data);
inPos = 0;
}
/**
* Constructs a reader for the specified chunk type and data
*/
public ReadTrack(String chunkType, byte [] data) {
this.data = (byte[])(data.clone());
this.chunkType = chunkType;
inPos = 0;
}
public String toString() {
return "ReadTrack " + chunkType + ", " + data.length + " bytes";
}
/**
* Reads a byte from the buffer
*/
public byte readByte() {
return data[inPos++];
}
/**
* Reads from the buffer
* @param buf A buffer to store data into
* @param start Where in the buffer to start storing data
* @param len How many bytes to store
*/
public void read(byte [] buf, int start, int len) {
System.arraycopy(data, inPos, buf, start, len);
inPos += len;
}
/**
* Reads from the buffer
* @param buf The buffer to fill
*/
public void read(byte [] buf) {
read(buf, 0, buf.length);
}
/**
* Reads a variable length value
* @see TrackChunk#addVarLen(int)
*/
public int readVarLen() {
int value;
byte c;
if (((value = readByte()) & 0x80) != 0) {
value &= 0x7F;
do {
value = (value << 7) + ((c = readByte()) & 0x7F);
} while ((c & (byte)(0x80)) != 0);
}
return value;
}
/**
* Pushes back the previously read byte
*/
public void unReadByte() {
inPos--;
}
/**
* Reads a MIDI event
* @return an {@link Event} or null
if the track is done
*/
public Event readEvent() {
if (this.inPos >= this.data.length)
return null;
int deltaTime = readVarLen();
byte status = readByte();
if ((status & (byte)(0x80)) == 0) {
System.out.println(status + " " + (status&(byte)(0x80)) + " " + lastStatus);
System.out.println((status & (byte)(0x80)) == 0);
unReadByte();
status = lastStatus;
} else {
lastStatus = status;
}
byte [] data = null;
if (status == (byte)(0xFF)) { // midi file special stuff
byte type = readByte();
int len = readVarLen();
data = new byte[1 + len];
data[0] = type;
read(data, 1, len);
} else {
int len;
switch (status & (byte)(0xF0)) { // get 'status nibble'
case (byte)(0x80): // note off
case (byte)(0x90): // note on
case (byte)(0xA0): // after touch
case (byte)(0xB0): // controller
case (byte)(0xE0): // pitch wheel
len = 2;
break;
case (byte)(0xC0): // program change
case (byte)(0xD0): // channel pressure
len = 1;
break;
case (byte)(0xF0):
switch (status & (byte)(0x0F)) {
// case 0xFF is special and handled above
case (byte)(0x00): // System exclusive
case (byte)(0x07): // system exclusive continuation
len = readVarLen();
break;
case (byte)(0x01): // MTC quarter frame
case (byte)(0x03): // Song select
len = 1;
break;
case (byte)(0x02): // Song position pointer
len = 2;
break;
case (byte)(0x06): // Tune request
case (byte)(0x08): // MIDI clock
case (byte)(0x09): // Tick
case (byte)(0x0A): // start
case (byte)(0x0B): // continue
case (byte)(0x0C): // stop
case (byte)(0x0E): // active sense
len = 0;
break;
default:
throw new RuntimeException("Invalid MIDI byte or broken parser?");
}
break;
default:
throw new RuntimeException("I'm confused");
}
data = new byte[len];
read(data);
}
return new Event(deltaTime, status, data);
}
}