1 /* MidiFileReader.java -- Read MIDI files. 2 Copyright (C) 2006, 2012 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 package gnu.javax.sound.midi.file; 39 40 import java.io.DataInputStream; 41 import java.io.File; 42 import java.io.FileInputStream; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.net.URL; 46 47 import javax.sound.midi.InvalidMidiDataException; 48 import javax.sound.midi.MetaMessage; 49 import javax.sound.midi.MidiEvent; 50 import javax.sound.midi.MidiFileFormat; 51 import javax.sound.midi.MidiMessage; 52 import javax.sound.midi.Sequence; 53 import javax.sound.midi.ShortMessage; 54 import javax.sound.midi.SysexMessage; 55 import javax.sound.midi.Track; 56 57 /** 58 * A MIDI file reader. 59 * 60 * This code reads MIDI file types 0 and 1. 61 * 62 * There are many decent documents on the web describing the MIDI file 63 * format. I didn't bother looking for the official document. If it 64 * exists, I'm not even sure if it is freely available. We should 65 * update this comment if we find out anything helpful here. 66 * 67 * @author Anthony Green (green@redhat.com) 68 * 69 */ 70 public class MidiFileReader extends javax.sound.midi.spi.MidiFileReader 71 { 72 /* Get the MidiFileFormat for the given input stream. 73 * @see javax.sound.midi.spi.MidiFileReader#getMidiFileFormat(java.io.InputStream) 74 */ getMidiFileFormat(InputStream in)75 public MidiFileFormat getMidiFileFormat(InputStream in) 76 throws InvalidMidiDataException, IOException 77 { 78 DataInputStream din; 79 if (in instanceof DataInputStream) 80 din = (DataInputStream) in; 81 else 82 din = new DataInputStream(in); 83 84 int type, ntracks, division, resolution, bytes; 85 float divisionType; 86 87 if (din.readInt() != 0x4d546864) // "MThd" 88 throw new InvalidMidiDataException("Invalid MIDI chunk header."); 89 90 bytes = din.readInt(); 91 if (bytes < 6) 92 throw new 93 InvalidMidiDataException("Invalid MIDI chunk header length: " + bytes); 94 95 type = din.readShort(); 96 if (type < 0 || type > 2) 97 throw new 98 InvalidMidiDataException("Invalid MIDI file type value: " + type); 99 100 ntracks = din.readShort(); 101 if (ntracks <= 0) 102 throw new 103 InvalidMidiDataException("Invalid number of MIDI tracks: " + ntracks); 104 105 division = din.readShort(); 106 if ((division & 0x8000) != 0) 107 { 108 division = -((division >>> 8) & 0xFF); 109 switch (division) 110 { 111 case 24: 112 divisionType = Sequence.SMPTE_24; 113 break; 114 115 case 25: 116 divisionType = Sequence.SMPTE_25; 117 break; 118 119 case 29: 120 divisionType = Sequence.SMPTE_30DROP; 121 break; 122 123 case 30: 124 divisionType = Sequence.SMPTE_30; 125 break; 126 127 default: 128 throw new 129 InvalidMidiDataException("Invalid MIDI frame division type: " 130 + division); 131 } 132 resolution = division & 0xff; 133 } 134 else 135 { 136 divisionType = Sequence.PPQ; 137 resolution = division & 0x7fff; 138 } 139 140 // If we haven't read every byte in the header now, just skip the rest. 141 din.skip(bytes - 6); 142 143 return new ExtendedMidiFileFormat(type, divisionType, resolution, 144 MidiFileFormat.UNKNOWN_LENGTH, 145 MidiFileFormat.UNKNOWN_LENGTH, ntracks); 146 } 147 148 /* Get the MidiFileFormat from the given URL. 149 * @see javax.sound.midi.spi.MidiFileReader#getMidiFileFormat(java.net.URL) 150 */ getMidiFileFormat(URL url)151 public MidiFileFormat getMidiFileFormat(URL url) 152 throws InvalidMidiDataException, IOException 153 { 154 InputStream is = url.openStream(); 155 try 156 { 157 return getMidiFileFormat(is); 158 } 159 finally 160 { 161 is.close(); 162 } 163 } 164 165 /* Get the MidiFileFormat from the given file. 166 * @see javax.sound.midi.spi.MidiFileReader#getMidiFileFormat(java.io.File) 167 */ getMidiFileFormat(File file)168 public MidiFileFormat getMidiFileFormat(File file) 169 throws InvalidMidiDataException, IOException 170 { 171 InputStream is = new FileInputStream(file); 172 try 173 { 174 return getMidiFileFormat(is); 175 } 176 finally 177 { 178 is.close(); 179 } 180 } 181 182 /* Get the MIDI Sequence found in this input stream. 183 * @see javax.sound.midi.spi.MidiFileReader#getSequence(java.io.InputStream) 184 */ getSequence(InputStream is)185 public Sequence getSequence(InputStream is) throws InvalidMidiDataException, 186 IOException 187 { 188 MidiDataInputStream din = new MidiDataInputStream(is); 189 ExtendedMidiFileFormat mff = (ExtendedMidiFileFormat) getMidiFileFormat(din); 190 191 Sequence seq = new Sequence(mff.getDivisionType(), mff.getResolution()); 192 193 int ntracks = mff.getNumberTracks(); 194 195 while (ntracks-- > 0) 196 { 197 Track track = seq.createTrack(); 198 int Mtrk = din.readInt(); 199 if (Mtrk != 0x4d54726b) 200 throw new InvalidMidiDataException("Invalid MIDI track header."); 201 din.readInt(); // length 202 203 int runningStatus = -1; 204 int click = 0; 205 206 // Set this to true when we've hit an End of Track meta event. 207 boolean done = false; 208 209 // Read all events. 210 while (! done) 211 { 212 MidiMessage mm; 213 int dtime = din.readVariableLengthInt(); 214 click += dtime; 215 216 int sbyte = din.readUnsignedByte(); 217 218 if (sbyte < 0xf0) 219 { 220 ShortMessage sm; 221 switch (sbyte & 0xf0) 222 { 223 case ShortMessage.NOTE_OFF: 224 case ShortMessage.NOTE_ON: 225 case ShortMessage.POLY_PRESSURE: 226 case ShortMessage.CONTROL_CHANGE: 227 case ShortMessage.PITCH_BEND: 228 case ShortMessage.SONG_POSITION_POINTER: 229 sm = new ShortMessage(); 230 sm.setMessage(sbyte, din.readByte(), din.readByte()); 231 runningStatus = sbyte; 232 break; 233 234 case ShortMessage.PROGRAM_CHANGE: 235 case ShortMessage.CHANNEL_PRESSURE: 236 case ShortMessage.SONG_SELECT: 237 case 0xF5: // FIXME: unofficial bus select. Not in spec?? 238 sm = new ShortMessage(); 239 sm.setMessage(sbyte, din.readByte(), 0); 240 runningStatus = sbyte; 241 break; 242 243 case ShortMessage.TUNE_REQUEST: 244 case ShortMessage.END_OF_EXCLUSIVE: 245 case ShortMessage.TIMING_CLOCK: 246 case ShortMessage.START: 247 case ShortMessage.CONTINUE: 248 case ShortMessage.STOP: 249 case ShortMessage.ACTIVE_SENSING: 250 case ShortMessage.SYSTEM_RESET: 251 sm = new ShortMessage(); 252 sm.setMessage(sbyte, 0, 0); 253 runningStatus = sbyte; 254 break; 255 256 default: 257 if (runningStatus != - 1) 258 { 259 switch (runningStatus & 0xf0) 260 { 261 case ShortMessage.NOTE_OFF: 262 case ShortMessage.NOTE_ON: 263 case ShortMessage.POLY_PRESSURE: 264 case ShortMessage.CONTROL_CHANGE: 265 case ShortMessage.PITCH_BEND: 266 case ShortMessage.SONG_POSITION_POINTER: 267 sm = new ShortMessage(); 268 sm.setMessage(runningStatus, sbyte, din.readByte()); 269 break; 270 271 case ShortMessage.PROGRAM_CHANGE: 272 case ShortMessage.CHANNEL_PRESSURE: 273 case ShortMessage.SONG_SELECT: 274 case 0xF5: // FIXME: unofficial bus select. Not in 275 // spec?? 276 sm = new ShortMessage(); 277 sm.setMessage(runningStatus, sbyte, 0); 278 continue; 279 280 case ShortMessage.TUNE_REQUEST: 281 case ShortMessage.END_OF_EXCLUSIVE: 282 case ShortMessage.TIMING_CLOCK: 283 case ShortMessage.START: 284 case ShortMessage.CONTINUE: 285 case ShortMessage.STOP: 286 case ShortMessage.ACTIVE_SENSING: 287 case ShortMessage.SYSTEM_RESET: 288 sm = new ShortMessage(); 289 sm.setMessage(runningStatus, 0, 0); 290 continue; 291 292 default: 293 throw new 294 InvalidMidiDataException("Invalid Short MIDI Event: " 295 + sbyte); 296 } 297 } 298 else 299 throw new 300 InvalidMidiDataException("Invalid Short MIDI Event: " 301 + sbyte); 302 } 303 mm = sm; 304 } 305 else if (sbyte == 0xf0 || sbyte == 0xf7) 306 { 307 // System Exclusive event 308 int slen = din.readVariableLengthInt(); 309 byte sysex[] = new byte[slen]; 310 din.readFully(sysex); 311 SysexMessage sm = new SysexMessage(); 312 sm.setMessage(sbyte, sysex, slen); 313 mm = sm; 314 runningStatus = - 1; 315 } 316 else if (sbyte == 0xff) 317 { 318 // Meta Message 319 byte mtype = din.readByte(); 320 int mlen = din.readVariableLengthInt(); 321 byte meta[] = new byte[mlen]; 322 din.readFully(meta); 323 MetaMessage metam = new MetaMessage(); 324 metam.setMessage(mtype, meta, mlen); 325 mm = metam; 326 327 if (mtype == 0x2f) // End of Track 328 done = true; 329 330 runningStatus = - 1; 331 } 332 else 333 { 334 throw new InvalidMidiDataException("Invalid status byte: " 335 + sbyte); 336 } 337 338 track.add(new MidiEvent(mm, click)); 339 } 340 } 341 342 return seq; 343 } 344 345 /* Get the MIDI Sequence found at the given URL. 346 * @see javax.sound.midi.spi.MidiFileReader#getSequence(java.net.URL) 347 */ getSequence(URL url)348 public Sequence getSequence(URL url) throws InvalidMidiDataException, 349 IOException 350 { 351 InputStream is = url.openStream(); 352 try 353 { 354 return getSequence(is); 355 } 356 finally 357 { 358 is.close(); 359 } 360 } 361 362 /* Get the MIDI Sequence found in the given file. 363 * @see javax.sound.midi.spi.MidiFileReader#getSequence(java.io.File) 364 */ getSequence(File file)365 public Sequence getSequence(File file) throws InvalidMidiDataException, 366 IOException 367 { 368 InputStream is = new FileInputStream(file); 369 try 370 { 371 return getSequence(is); 372 } 373 finally 374 { 375 is.close(); 376 } 377 } 378 } 379