[go: up one dir, main page]

Menu

[r18]: / ref1 / APIs / jm / midi / SMF.java  Maximize  Restore  History

Download this file

356 lines (324 with data), 11.7 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/*
<This Java Class is part of the jMusic API version 1.5, March 2004.>
Copyright (C) 2000 Andrew Sorensen & Andrew Brown
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 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package jm.midi;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.Enumeration;
import java.util.Vector;
import jm.JMC;
import jm.midi.event.EndTrack;
import jm.midi.event.Event;
import jm.midi.event.VoiceEvt;
import ren.util.PO;
/**
* @author Andrew Sorensen
* @version 1.0,Sun Feb 25 18:43
*/
public final class SMF implements JMC{
//--------------------------------------
//attributes
//--------------------------------------
/** The standard MIDI file type to be read or written */
private short fileType;
/** The current number of tracks stored by this Class */
private short numOfTracks;
/** The number of bytes being read or written */
private int numOfBytes;
/** Pulses per quarter note value */
private short ppqn;
/** list of Tracks contained within this SMF */
private Vector trackList;
/* Print statements */
private boolean VERBOSE = false;
//------------------------------------------
//SMF Default Constructor
//------------------------------------------
/**
* Create a new empty SMF with default filetype and ppqn settings
* SMF objects are useful for retrieving statistical information
* about a MIDI file which has just been read or written.
*/
public SMF(){
this((short)1, (short)480);
}
/**
* Create a new empty SMF. Allows user to set filetype and ppqn settings
* SMF Objects are useful for retrieving statistical information
* about a MIDI file which has just been read or written.
*/
public SMF(short fileType, short ppqn){
this.fileType = fileType;
this.ppqn = ppqn;
this.numOfBytes = 0;
this.numOfTracks = 0;
this.trackList = new Vector();
}
public void setVerbose(boolean val) {
this.VERBOSE = val;
}
public Vector getTrackList(){
return this.trackList;
}
public short getPPQN(){
return this.ppqn;
}
public void clearTracks(){
if(!this.trackList.isEmpty()){
//remove any previous tracks
this.trackList.removeAllElements();
}
}
//------------------------------------------
//Read SMF
//------------------------------------------
/**
* Read from a standard MIDI file
* @params InputStream - the datasource to read from
* @params Score score - the score to place jMusic data translation into
* @exception IOException - any IO problems
*/
public void read(InputStream is)
throws IOException{
//Given the small size of MIDI files read all
//data into a ByteArrayStream for further processing
byte[] fileData = new byte[is.available()];
is.read(fileData);
ByteArrayInputStream bais =
new ByteArrayInputStream(fileData);
DataInputStream dis = new DataInputStream(bais);
//clear any SMF data
if(!this.trackList.isEmpty()){
this.trackList.removeAllElements(); //remove any previous tracks
}
//check header for MIDIfile validity
if(dis.readInt() != 0x4D546864){//Check for MIDI track validity
throw new IOException("This is NOT a MIDI file !!!");
}else{//If MIDI file passes skip length bytes
dis.readInt(); //skip over Length info
}
//get SMF Class data
try{
fileType = dis.readShort();
if(VERBOSE) System.out.println("MIDI file type = "+ fileType);
this.numOfTracks = dis.readShort();
if(VERBOSE) System.out.println("Number of tracks = " + numOfTracks);
this.ppqn = dis.readShort();
if(VERBOSE) System.out.println("ppqn = " + ppqn);
}catch(IOException e){
System.out.println(e);
e.printStackTrace();
}
// skip the tempo track fro type 1 files
/*
if(fileType == 1) {
skipATrack(dis);
numOfTracks--;
}
//*/
//Read all track chunks
for(int i = 0; i < numOfTracks; i++){
readTrackChunk( dis);
}
is.close();
dis.close();
}
// public void readWithJava()
//------------------------------------
//Write a SMF
//-------------------------------------
/**
* Write to a standard MIDI file
* @param OutputStream the datasource to write to
* @param Score score the Score to get data from
* @exception IOException did the write go ok
*/
public void write(OutputStream os)
throws IOException{
//IO Stream stuff
DataOutputStream dos = new DataOutputStream(os);
//find number of tracks
this.numOfTracks = (short) trackList.size();
//write header chunk
try{
dos.writeInt(0x4D546864); //MThd
dos.writeInt(6); //Length
dos.writeShort(1); //Midi File Type
dos.writeShort(numOfTracks); //Number of tracks
dos.writeShort(ppqn); //Pulses Per Quarter Note
}
catch(Exception e){
e.printStackTrace();
}
//write all tracks
Enumeration enumr = trackList.elements();
while(enumr.hasMoreElements()){
Track smfTrack = (Track) enumr.nextElement();
writeTrackChunk(dos, smfTrack);
}
os.flush();
os.close();
dos.flush();
dos.close();
}
/**
* Print all MIDI tracks and MIDI events
*/
public void print(){
Enumeration enumr = trackList.elements();
while(enumr.hasMoreElements()){
Track track = (Track) enumr.nextElement();
track.print();
}
}
/**
* Reads a MIDI track without doing anything to the data
*/
private void skipATrack( DataInputStream dos) throws IOException{
if(VERBOSE) System.out.println("Skipping the tempo track . . .");
dos.readInt();
dos.skipBytes(dos.readInt());
}
//----------------------------------------
//SMF Track Reads and Writes
//----------------------------------------
/**
* Reads a MIDI track chunk
* @param DataInputStream dis - the input stream to read from
* @exception IOException
*/
private void readTrackChunk(DataInputStream dis)
throws IOException{
//local variables for Track class
Track track = new Track();
//Insert new Track into a list of tracks
this.trackList.addElement(track);
int deltaTime = 0;
if(VERBOSE) System.out.println("Reading Track ..........");
PO.p("available " + dis.available());
PO.p(dis.toString());
//Read track header
int trackHead = 0;
try {
trackHead = dis.readInt();
}catch(EOFException eof) {
PO.p("track head " + trackHead + " was end of file... returning");
return;
}
PO.p("trackHeader = " + trackHead);
if(trackHead != 0x4D54726B){//If MTrk read is wrong
throw new IOException
("Track started in wrong place!!!! ABORTING");
}else{//If MTrk read ok get bytesRemaining
PO.p("bytes remaining?? = " + dis.readInt());
}
//loop variables
int status, oldStatus =0, eventNum = 0, eventLength = 0;
//Start gathering event data
Event event = null;
while(true){
try{
//get variable length timestamp
deltaTime = MidiUtil.readVarLength(dis);
if(deltaTime == Integer.MIN_VALUE)
break;
//mark stream so we can return if we need running status
dis.mark(2);
// status = 0;
// try {
status = dis.readUnsignedByte();
// } catch(EOFException eof) {
// System.out.println(eof.toString());
// }
//decide on running status
if(status < 0x80){ //set running status
status = oldStatus;
//return stream to before status read
dis.reset();
}
//create default event of correct type
if(status >= 0xFF){ //Meta Event
int type = dis.readUnsignedByte();
eventLength = MidiUtil.readVarLength(dis);
event = jm.midi.MidiUtil.createMetaEvent(type);
}else if(status >= 0xF0){ //System Exclusive --- NOT SUPPORTED
System.out.println("SysEX---");
eventLength = MidiUtil.readVarLength( dis);
}else if(status >= 0x80){ //MIDI voice event
short selection = (short) (status / 0x10);
short midiChannel = (short) (status - (selection * 0x10));
VoiceEvt evt = (VoiceEvt)MidiUtil.createVoiceEvent(selection);
evt.setMidiChannel(midiChannel);
event = evt;
if(event == null){
throw new IOException("MIDI file read error: invalid voice event type!");
}
}
oldStatus = status;
}catch(Exception e){
e.printStackTrace();
System.exit(1);
}
if(event != null){
//read data into the new event and
//add the new event to the Track object
event.setTime(deltaTime);
event.read(dis);
//if (VERBOSE) event.print();
track.addEvent(event);
//event.print();
if(event instanceof EndTrack)
break;
}else{
//skip the stream ahead to next valid event
dis.skipBytes(eventLength);
}
}
}
/**
* Write the Track Chunk
* @param DataOutputStream dos
* @param Track track - track to write
* @exception IOException
*/
private void writeTrackChunk(DataOutputStream odos, Track track)
throws IOException{
if(VERBOSE) System.out.println("Writing MIDI Track" );
//Write to temporary stream to buffer disk writes and
//calculate the number of bytes written to the stream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
int header = 0x4D54726B;
Enumeration enumr = track.getEvtList().elements();
enumr = track.getEvtList().elements();
//At this stage Except that all events are NoteOn events
while(enumr.hasMoreElements()){
Event evt = (Event) enumr.nextElement();
evt.write(dos);
if(DEBUG)evt.print();
}
//Write to the real stream
odos.writeInt(header);
odos.writeInt(baos.size());
odos.write(baos.toByteArray(),0,baos.size());
}
}