1
package org.herac.tuxguitar.io.midi;
3
import java.io.DataInputStream;
4
import java.io.IOException;
5
import java.io.InputStream;
7
import org.herac.tuxguitar.io.midi.base.MidiEvent;
8
import org.herac.tuxguitar.io.midi.base.MidiMessage;
9
import org.herac.tuxguitar.io.midi.base.MidiSequence;
10
import org.herac.tuxguitar.io.midi.base.MidiTrack;
12
public class MidiFileReader implements MidiFileHeader{
14
public static boolean CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX = true;
16
private static final int STATUS_NONE = 0;
17
private static final int STATUS_ONE_BYTE = 1;
18
private static final int STATUS_TWO_BYTES = 2;
19
private static final int STATUS_SYSEX = 3;
20
private static final int STATUS_META = 4;
22
public MidiSequence getSequence(InputStream stream)throws MidiFileException, IOException{
23
DataInputStream in = new DataInputStream(stream);
24
if (in.readInt() != HEADER_MAGIC){
25
throw new MidiFileException("not a MIDI file: wrong header magic");
27
int headerLength = in.readInt();
28
if (headerLength < HEADER_LENGTH){
29
throw new MidiFileException("corrupt MIDI file: wrong header length");
31
int type = in.readShort();
32
if (type < 0 || type > 2){
33
throw new MidiFileException("corrupt MIDI file: illegal type");
36
throw new MidiFileException("this implementation doesn't support type 2 MIDI files");
38
int trackCount = in.readShort();
40
throw new MidiFileException("corrupt MIDI file: number of tracks must be positive");
42
if (type == 0 && trackCount != 1){
43
throw new MidiFileException("corrupt MIDI file: type 0 files must contain exactely one track");
45
float divisionType = -1.0F;
47
int division = in.readUnsignedShort();
48
if ((division & 0x8000) != 0){
49
int frameType = -((division >>> 8) & 0xFF);
51
divisionType = MidiSequence.SMPTE_24;
52
}else if(frameType == 25){
53
divisionType = MidiSequence.SMPTE_25;
54
}else if(frameType == 29){
55
divisionType = MidiSequence.SMPTE_30DROP;
56
}else if(frameType == 30){
57
divisionType = MidiSequence.SMPTE_30;
59
throw new MidiFileException("corrupt MIDI file: illegal frame division type");
61
resolution = division & 0xff;
63
divisionType = MidiSequence.PPQ;
64
resolution = division & 0x7fff;
67
in.skip(headerLength - HEADER_LENGTH);
69
MidiSequence sequence = new MidiSequence(divisionType,resolution);
70
for (int i = 0; i < trackCount; i++){
71
MidiTrack track = new MidiTrack();
72
sequence.addTrack(track);
81
private void readTrack(DataInputStream in, MidiTrack track)throws MidiFileException, IOException{
83
if (in.readInt() == TRACK_MAGIC){
86
int chunkLength = in.readInt();
87
if (chunkLength % 2 != 0){
93
MidiTrackReaderHelper helper = new MidiTrackReaderHelper(0,in.readInt(),-1);
94
while (helper.remainingBytes > 0){
95
helper.ticks += readVariableLengthQuantity(in, helper);
96
MidiEvent event = readEvent(in, helper);
103
private static MidiEvent readEvent(DataInputStream in, MidiTrackReaderHelper helper)throws MidiFileException, IOException{
104
int statusByte = readUnsignedByte(in, helper);
106
boolean runningStatusApplies = false;
108
if (statusByte < 0x80){
109
if (helper.runningStatusByte != -1){
110
runningStatusApplies = true;
111
savedByte = statusByte;
112
statusByte = helper.runningStatusByte;
114
throw new MidiFileException("corrupt MIDI file: status byte missing");
118
int type = getType(statusByte);
119
if(type == STATUS_ONE_BYTE){
121
if (runningStatusApplies){
124
data = readUnsignedByte(in, helper);
125
helper.runningStatusByte = statusByte;
128
return new MidiEvent(MidiMessage.shortMessage((statusByte & 0xF0),(statusByte & 0x0F) , data), helper.ticks);
129
}else if(type == STATUS_TWO_BYTES){
131
if (runningStatusApplies){
134
data1 = readUnsignedByte(in, helper);
135
helper.runningStatusByte = statusByte;
138
return new MidiEvent(MidiMessage.shortMessage((statusByte & 0xF0),(statusByte & 0x0F) , data1, readUnsignedByte(in, helper)), helper.ticks);
139
}else if(type == STATUS_SYSEX){
140
if (CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX){
141
helper.runningStatusByte = -1;
143
int dataLength = (int) readVariableLengthQuantity(in, helper);
144
byte[] data = new byte[dataLength];
145
for (int i = 0; i < dataLength; i++){
146
data[i] = (byte) readUnsignedByte(in, helper);
148
}else if(type == STATUS_META){
149
if (CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX){
150
helper.runningStatusByte = -1;
152
int typeByte = readUnsignedByte(in, helper);
153
int dataLength = (int) readVariableLengthQuantity(in, helper);
154
byte[] data = new byte[dataLength];
155
for (int i = 0; i < dataLength; i++){
156
data[i] = (byte) readUnsignedByte(in, helper);
159
return new MidiEvent(MidiMessage.metaMessage(typeByte, data), helper.ticks);
165
private static int getType(int statusByte){
166
if (statusByte < 0xf0) {
167
int command = statusByte & 0xf0;
168
if(command == 0x80 || command == 0x90 || command == 0xa0 || command == 0xb0 || command == 0xe0){
169
return STATUS_TWO_BYTES;
171
else if(command == 0xc0 || command == 0xd0){
172
return STATUS_ONE_BYTE;
176
else if (statusByte == 0xf0 || statusByte == 0xf7){
179
else if (statusByte == 0xff){
187
public static long readVariableLengthQuantity(DataInputStream in, MidiTrackReaderHelper helper)throws MidiFileException, IOException{
191
int data = readUnsignedByte(in, helper);
194
value |= (data & 0x7f);
199
throw new MidiFileException("not a MIDI file: unterminated variable-length quantity");
202
public static int readUnsignedByte(DataInputStream dataInputStream, MidiTrackReaderHelper helper)throws IOException{
203
helper.remainingBytes--;
204
return dataInputStream.readUnsignedByte();
207
private class MidiTrackReaderHelper{
208
protected long ticks = 0;
209
protected long remainingBytes;
210
protected int runningStatusByte;
212
protected MidiTrackReaderHelper(long ticks,long remainingBytes,int runningStatusByte){
214
this.remainingBytes = remainingBytes;
215
this.runningStatusByte = runningStatusByte;
b'\\ No newline at end of file'