1 /* 2 * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.sound; 27 28 import java.io.BufferedInputStream; 29 import java.io.DataInputStream; 30 import java.io.EOFException; 31 import java.io.File; 32 import java.io.FileInputStream; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.net.URL; 36 37 import javax.sound.midi.InvalidMidiDataException; 38 import javax.sound.midi.MetaMessage; 39 import javax.sound.midi.MidiEvent; 40 import javax.sound.midi.MidiFileFormat; 41 import javax.sound.midi.MidiMessage; 42 import javax.sound.midi.Sequence; 43 import javax.sound.midi.SysexMessage; 44 import javax.sound.midi.Track; 45 import javax.sound.midi.spi.MidiFileReader; 46 47 /** 48 * MIDI file reader. 49 * 50 * @author Kara Kytle 51 * @author Jan Borgersen 52 * @author Florian Bomers 53 */ 54 public final class StandardMidiFileReader extends MidiFileReader { 55 56 private static final int MThd_MAGIC = 0x4d546864; // 'MThd' 57 58 private static final int bisBufferSize = 1024; // buffer size in buffered input streams 59 60 @Override 61 public MidiFileFormat getMidiFileFormat(InputStream stream) 62 throws InvalidMidiDataException, IOException { 63 return getMidiFileFormatFromStream(stream, MidiFileFormat.UNKNOWN_LENGTH, null); 64 } 65 66 // $$fb 2002-04-17: part of fix for 4635286: MidiSystem.getMidiFileFormat() 67 // returns format having invalid length 68 private MidiFileFormat getMidiFileFormatFromStream(InputStream stream, 69 int fileLength, 70 SMFParser smfParser) 71 throws InvalidMidiDataException, IOException{ 72 int maxReadLength = 16; 73 int duration = MidiFileFormat.UNKNOWN_LENGTH; 74 DataInputStream dis; 75 76 if (stream instanceof DataInputStream) { 77 dis = (DataInputStream) stream; 78 } else { 79 dis = new DataInputStream(stream); 80 } 81 if (smfParser == null) { 82 dis.mark(maxReadLength); 83 } else { 84 smfParser.stream = dis; 85 } 86 87 int type; 88 int numtracks; 89 float divisionType; 90 int resolution; 91 92 try { 93 int magic = dis.readInt(); 94 if( !(magic == MThd_MAGIC) ) { 95 // not MIDI 96 throw new InvalidMidiDataException("not a valid MIDI file"); 97 } 98 99 // read header length 100 int bytesRemaining = dis.readInt() - 6; 101 type = dis.readShort(); 102 numtracks = dis.readShort(); 103 int timing = dis.readShort(); 104 105 // decipher the timing code 106 if (timing > 0) { 107 // tempo based timing. value is ticks per beat. 108 divisionType = Sequence.PPQ; 109 resolution = timing; 110 } else { 111 // SMPTE based timing. first decipher the frame code. 112 int frameCode = -1 * (timing >> 8); 113 switch(frameCode) { 114 case 24: 115 divisionType = Sequence.SMPTE_24; 116 break; 117 case 25: 118 divisionType = Sequence.SMPTE_25; 119 break; 120 case 29: 121 divisionType = Sequence.SMPTE_30DROP; 122 break; 123 case 30: 124 divisionType = Sequence.SMPTE_30; 125 break; 126 default: 127 throw new InvalidMidiDataException("Unknown frame code: " + frameCode); 128 } 129 // now determine the timing resolution in ticks per frame. 130 resolution = timing & 0xFF; 131 } 132 if (smfParser != null) { 133 // remainder of this chunk 134 dis.skip(bytesRemaining); 135 smfParser.tracks = numtracks; 136 } 137 } finally { 138 // if only reading the file format, reset the stream 139 if (smfParser == null) { 140 dis.reset(); 141 } 142 } 143 MidiFileFormat format = new MidiFileFormat(type, divisionType, resolution, fileLength, duration); 144 return format; 145 } 146 147 @Override 148 public MidiFileFormat getMidiFileFormat(URL url) throws InvalidMidiDataException, IOException { 149 InputStream urlStream = url.openStream(); // throws IOException 150 BufferedInputStream bis = new BufferedInputStream( urlStream, bisBufferSize ); 151 MidiFileFormat fileFormat = null; 152 try { 153 fileFormat = getMidiFileFormat( bis ); // throws InvalidMidiDataException 154 } finally { 155 bis.close(); 156 } 157 return fileFormat; 158 } 159 160 @Override 161 public MidiFileFormat getMidiFileFormat(File file) throws InvalidMidiDataException, IOException { 162 FileInputStream fis = new FileInputStream(file); // throws IOException 163 BufferedInputStream bis = new BufferedInputStream(fis, bisBufferSize); 164 165 // $$fb 2002-04-17: part of fix for 4635286: MidiSystem.getMidiFileFormat() returns format having invalid length 166 long length = file.length(); 167 if (length > Integer.MAX_VALUE) { 168 length = MidiFileFormat.UNKNOWN_LENGTH; 169 } 170 MidiFileFormat fileFormat = null; 171 try { 172 fileFormat = getMidiFileFormatFromStream(bis, (int) length, null); 173 } finally { 174 bis.close(); 175 } 176 return fileFormat; 177 } 178 179 @Override 180 public Sequence getSequence(InputStream stream) throws InvalidMidiDataException, IOException { 181 SMFParser smfParser = new SMFParser(); 182 MidiFileFormat format = getMidiFileFormatFromStream(stream, 183 MidiFileFormat.UNKNOWN_LENGTH, 184 smfParser); 185 186 // must be MIDI Type 0 or Type 1 187 if ((format.getType() != 0) && (format.getType() != 1)) { 188 throw new InvalidMidiDataException("Invalid or unsupported file type: " + format.getType()); 189 } 190 191 // construct the sequence object 192 Sequence sequence = new Sequence(format.getDivisionType(), format.getResolution()); 193 194 // for each track, go to the beginning and read the track events 195 for (int i = 0; i < smfParser.tracks; i++) { 196 if (smfParser.nextTrack()) { 197 smfParser.readTrack(sequence.createTrack()); 198 } else { 199 break; 200 } 201 } 202 return sequence; 203 } 204 205 @Override 206 public Sequence getSequence(URL url) throws InvalidMidiDataException, IOException { 207 InputStream is = url.openStream(); // throws IOException 208 is = new BufferedInputStream(is, bisBufferSize); 209 Sequence seq = null; 210 try { 211 seq = getSequence(is); 212 } finally { 213 is.close(); 214 } 215 return seq; 216 } 217 218 @Override 219 public Sequence getSequence(File file) throws InvalidMidiDataException, IOException { 220 InputStream is = new FileInputStream(file); // throws IOException 221 is = new BufferedInputStream(is, bisBufferSize); 222 Sequence seq = null; 223 try { 224 seq = getSequence(is); 225 } finally { 226 is.close(); 227 } 228 return seq; 229 } 230 } 231 232 //============================================================================================================= 233 234 /** 235 * State variables during parsing of a MIDI file. 236 */ 237 final class SMFParser { 238 private static final int MTrk_MAGIC = 0x4d54726b; // 'MTrk' 239 240 // set to true to not allow corrupt MIDI files tombe loaded 241 private static final boolean STRICT_PARSER = false; 242 243 private static final boolean DEBUG = false; 244 245 int tracks; // number of tracks 246 DataInputStream stream; // the stream to read from 247 248 private int trackLength = 0; // remaining length in track 249 private byte[] trackData = null; 250 private int pos = 0; 251 252 SMFParser() { 253 } 254 255 private int readUnsigned() throws IOException { 256 return trackData[pos++] & 0xFF; 257 } 258 259 private void read(byte[] data) throws IOException { 260 System.arraycopy(trackData, pos, data, 0, data.length); 261 pos += data.length; 262 } 263 264 private long readVarInt() throws IOException { 265 long value = 0; // the variable-lengh int value 266 int currentByte = 0; 267 do { 268 currentByte = trackData[pos++] & 0xFF; 269 value = (value << 7) + (currentByte & 0x7F); 270 } while ((currentByte & 0x80) != 0); 271 return value; 272 } 273 274 private int readIntFromStream() throws IOException { 275 try { 276 return stream.readInt(); 277 } catch (EOFException eof) { 278 throw new EOFException("invalid MIDI file"); 279 } 280 } 281 282 boolean nextTrack() throws IOException, InvalidMidiDataException { 283 int magic; 284 trackLength = 0; 285 do { 286 // $$fb 2003-08-20: fix for 4910986: MIDI file parser breaks up on http connection 287 if (stream.skipBytes(trackLength) != trackLength) { 288 if (!STRICT_PARSER) { 289 return false; 290 } 291 throw new EOFException("invalid MIDI file"); 292 } 293 magic = readIntFromStream(); 294 trackLength = readIntFromStream(); 295 } while (magic != MTrk_MAGIC); 296 if (!STRICT_PARSER) { 297 if (trackLength < 0) { 298 return false; 299 } 300 } 301 // now read track in a byte array 302 try { 303 trackData = new byte[trackLength]; 304 } catch (final OutOfMemoryError oom) { 305 throw new IOException("Track length too big", oom); 306 } 307 try { 308 // $$fb 2003-08-20: fix for 4910986: MIDI file parser breaks up on http connection 309 stream.readFully(trackData); 310 } catch (EOFException eof) { 311 if (!STRICT_PARSER) { 312 return false; 313 } 314 throw new EOFException("invalid MIDI file"); 315 } 316 pos = 0; 317 return true; 318 } 319 320 private boolean trackFinished() { 321 return pos >= trackLength; 322 } 323 324 void readTrack(Track track) throws IOException, InvalidMidiDataException { 325 try { 326 // reset current tick to 0 327 long tick = 0; 328 329 // reset current status byte to 0 (invalid value). 330 // this should cause us to throw an InvalidMidiDataException if we don't 331 // get a valid status byte from the beginning of the track. 332 int status = 0; 333 boolean endOfTrackFound = false; 334 335 while (!trackFinished() && !endOfTrackFound) { 336 MidiMessage message; 337 338 int data1 = -1; // initialize to invalid value 339 int data2 = 0; 340 341 // each event has a tick delay and then the event data. 342 343 // first read the delay (a variable-length int) and update our tick value 344 tick += readVarInt(); 345 346 // check for new status 347 int byteValue = readUnsigned(); 348 349 if (byteValue >= 0x80) { 350 status = byteValue; 351 } else { 352 data1 = byteValue; 353 } 354 355 switch (status & 0xF0) { 356 case 0x80: 357 case 0x90: 358 case 0xA0: 359 case 0xB0: 360 case 0xE0: 361 // two data bytes 362 if (data1 == -1) { 363 data1 = readUnsigned(); 364 } 365 data2 = readUnsigned(); 366 message = new FastShortMessage(status | (data1 << 8) | (data2 << 16)); 367 break; 368 case 0xC0: 369 case 0xD0: 370 // one data byte 371 if (data1 == -1) { 372 data1 = readUnsigned(); 373 } 374 message = new FastShortMessage(status | (data1 << 8)); 375 break; 376 case 0xF0: 377 // sys-ex or meta 378 switch(status) { 379 case 0xF0: 380 case 0xF7: 381 // sys ex 382 int sysexLength = (int) readVarInt(); 383 byte[] sysexData = new byte[sysexLength]; 384 read(sysexData); 385 386 SysexMessage sysexMessage = new SysexMessage(); 387 sysexMessage.setMessage(status, sysexData, sysexLength); 388 message = sysexMessage; 389 break; 390 391 case 0xFF: 392 // meta 393 int metaType = readUnsigned(); 394 int metaLength = (int) readVarInt(); 395 final byte[] metaData; 396 try { 397 metaData = new byte[metaLength]; 398 } catch (final OutOfMemoryError oom) { 399 throw new IOException("Meta length too big", oom); 400 } 401 402 read(metaData); 403 404 MetaMessage metaMessage = new MetaMessage(); 405 metaMessage.setMessage(metaType, metaData, metaLength); 406 message = metaMessage; 407 if (metaType == 0x2F) { 408 // end of track means it! 409 endOfTrackFound = true; 410 } 411 break; 412 default: 413 throw new InvalidMidiDataException("Invalid status byte: " + status); 414 } // switch sys-ex or meta 415 break; 416 default: 417 throw new InvalidMidiDataException("Invalid status byte: " + status); 418 } // switch 419 track.add(new MidiEvent(message, tick)); 420 } // while 421 } catch (ArrayIndexOutOfBoundsException e) { 422 if (DEBUG) e.printStackTrace(); 423 // fix for 4834374 424 throw new EOFException("invalid MIDI file"); 425 } 426 } 427 }