|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  */ | 
|  |  | 
|  | package com.sun.media.sound; | 
|  |  | 
|  | import java.io.BufferedInputStream; | 
|  | import java.io.DataInputStream; | 
|  | import java.io.EOFException; | 
|  | import java.io.File; | 
|  | import java.io.FileInputStream; | 
|  | import java.io.IOException; | 
|  | import java.io.InputStream; | 
|  | import java.net.URL; | 
|  |  | 
|  | import javax.sound.midi.InvalidMidiDataException; | 
|  | import javax.sound.midi.MetaMessage; | 
|  | import javax.sound.midi.MidiEvent; | 
|  | import javax.sound.midi.MidiFileFormat; | 
|  | import javax.sound.midi.MidiMessage; | 
|  | import javax.sound.midi.Sequence; | 
|  | import javax.sound.midi.SysexMessage; | 
|  | import javax.sound.midi.Track; | 
|  | import javax.sound.midi.spi.MidiFileReader; | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  */ | 
|  | public final class StandardMidiFileReader extends MidiFileReader { | 
|  |  | 
|  |     private static final int MThd_MAGIC = 0x4d546864;   | 
|  |  | 
|  |     private static final int bisBufferSize = 1024;  | 
|  |  | 
|  |     public MidiFileFormat getMidiFileFormat(InputStream stream) | 
|  |             throws InvalidMidiDataException, IOException { | 
|  |         return getMidiFileFormatFromStream(stream, MidiFileFormat.UNKNOWN_LENGTH, null); | 
|  |     } | 
|  |  | 
|  |     // $$fb 2002-04-17: part of fix for 4635286: MidiSystem.getMidiFileFormat() | 
|  |      | 
|  |     private MidiFileFormat getMidiFileFormatFromStream(InputStream stream, | 
|  |                                                        int fileLength, | 
|  |                                                        SMFParser smfParser) | 
|  |             throws InvalidMidiDataException, IOException{ | 
|  |         int maxReadLength = 16; | 
|  |         int duration = MidiFileFormat.UNKNOWN_LENGTH; | 
|  |         DataInputStream dis; | 
|  |  | 
|  |         if (stream instanceof DataInputStream) { | 
|  |             dis = (DataInputStream) stream; | 
|  |         } else { | 
|  |             dis = new DataInputStream(stream); | 
|  |         } | 
|  |         if (smfParser == null) { | 
|  |             dis.mark(maxReadLength); | 
|  |         } else { | 
|  |             smfParser.stream = dis; | 
|  |         } | 
|  |  | 
|  |         int type; | 
|  |         int numtracks; | 
|  |         float divisionType; | 
|  |         int resolution; | 
|  |  | 
|  |         try { | 
|  |             int magic = dis.readInt(); | 
|  |             if( !(magic == MThd_MAGIC) ) { | 
|  |                  | 
|  |                 throw new InvalidMidiDataException("not a valid MIDI file"); | 
|  |             } | 
|  |  | 
|  |              | 
|  |             int bytesRemaining = dis.readInt() - 6; | 
|  |             type = dis.readShort(); | 
|  |             numtracks = dis.readShort(); | 
|  |             int timing = dis.readShort(); | 
|  |  | 
|  |              | 
|  |             if (timing > 0) { | 
|  |                  | 
|  |                 divisionType = Sequence.PPQ; | 
|  |                 resolution = timing; | 
|  |             } else { | 
|  |                  | 
|  |                 int frameCode = -1 * (timing >> 8); | 
|  |                 switch(frameCode) { | 
|  |                 case 24: | 
|  |                     divisionType = Sequence.SMPTE_24; | 
|  |                     break; | 
|  |                 case 25: | 
|  |                     divisionType = Sequence.SMPTE_25; | 
|  |                     break; | 
|  |                 case 29: | 
|  |                     divisionType = Sequence.SMPTE_30DROP; | 
|  |                     break; | 
|  |                 case 30: | 
|  |                     divisionType = Sequence.SMPTE_30; | 
|  |                     break; | 
|  |                 default: | 
|  |                     throw new InvalidMidiDataException("Unknown frame code: " + frameCode); | 
|  |                 } | 
|  |                  | 
|  |                 resolution = timing & 0xFF; | 
|  |             } | 
|  |             if (smfParser != null) { | 
|  |                  | 
|  |                 dis.skip(bytesRemaining); | 
|  |                 smfParser.tracks = numtracks; | 
|  |             } | 
|  |         } finally { | 
|  |              | 
|  |             if (smfParser == null) { | 
|  |                 dis.reset(); | 
|  |             } | 
|  |         } | 
|  |         MidiFileFormat format = new MidiFileFormat(type, divisionType, resolution, fileLength, duration); | 
|  |         return format; | 
|  |     } | 
|  |  | 
|  |  | 
|  |     public MidiFileFormat getMidiFileFormat(URL url) throws InvalidMidiDataException, IOException { | 
|  |         InputStream urlStream = url.openStream();  | 
|  |         BufferedInputStream bis = new BufferedInputStream( urlStream, bisBufferSize ); | 
|  |         MidiFileFormat fileFormat = null; | 
|  |         try { | 
|  |             fileFormat = getMidiFileFormat( bis );  | 
|  |         } finally { | 
|  |             bis.close(); | 
|  |         } | 
|  |         return fileFormat; | 
|  |     } | 
|  |  | 
|  |  | 
|  |     public MidiFileFormat getMidiFileFormat(File file) throws InvalidMidiDataException, IOException { | 
|  |         FileInputStream fis = new FileInputStream(file);  | 
|  |         BufferedInputStream bis = new BufferedInputStream(fis, bisBufferSize); | 
|  |  | 
|  |          | 
|  |         long length = file.length(); | 
|  |         if (length > Integer.MAX_VALUE) { | 
|  |             length = MidiFileFormat.UNKNOWN_LENGTH; | 
|  |         } | 
|  |         MidiFileFormat fileFormat = null; | 
|  |         try { | 
|  |             fileFormat = getMidiFileFormatFromStream(bis, (int) length, null); | 
|  |         } finally { | 
|  |             bis.close(); | 
|  |         } | 
|  |         return fileFormat; | 
|  |     } | 
|  |  | 
|  |  | 
|  |     public Sequence getSequence(InputStream stream) throws InvalidMidiDataException, IOException { | 
|  |         SMFParser smfParser = new SMFParser(); | 
|  |         MidiFileFormat format = getMidiFileFormatFromStream(stream, | 
|  |                                                             MidiFileFormat.UNKNOWN_LENGTH, | 
|  |                                                             smfParser); | 
|  |  | 
|  |          | 
|  |         if ((format.getType() != 0) && (format.getType() != 1)) { | 
|  |             throw new InvalidMidiDataException("Invalid or unsupported file type: "  + format.getType()); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         Sequence sequence = new Sequence(format.getDivisionType(), format.getResolution()); | 
|  |  | 
|  |          | 
|  |         for (int i = 0; i < smfParser.tracks; i++) { | 
|  |             if (smfParser.nextTrack()) { | 
|  |                 smfParser.readTrack(sequence.createTrack()); | 
|  |             } else { | 
|  |                 break; | 
|  |             } | 
|  |         } | 
|  |         return sequence; | 
|  |     } | 
|  |  | 
|  |  | 
|  |  | 
|  |     public Sequence getSequence(URL url) throws InvalidMidiDataException, IOException { | 
|  |         InputStream is = url.openStream();   | 
|  |         is = new BufferedInputStream(is, bisBufferSize); | 
|  |         Sequence seq = null; | 
|  |         try { | 
|  |             seq = getSequence(is); | 
|  |         } finally { | 
|  |             is.close(); | 
|  |         } | 
|  |         return seq; | 
|  |     } | 
|  |  | 
|  |  | 
|  |     public Sequence getSequence(File file) throws InvalidMidiDataException, IOException { | 
|  |         InputStream is = new FileInputStream(file);  | 
|  |         is = new BufferedInputStream(is, bisBufferSize); | 
|  |         Sequence seq = null; | 
|  |         try { | 
|  |             seq = getSequence(is); | 
|  |         } finally { | 
|  |             is.close(); | 
|  |         } | 
|  |         return seq; | 
|  |     } | 
|  | } | 
|  |  | 
|  | //============================================================================================================= | 
|  |  | 
|  |  | 
|  |  | 
|  |  */ | 
|  | final class SMFParser { | 
|  |     private static final int MTrk_MAGIC = 0x4d54726b;   | 
|  |  | 
|  |      | 
|  |     private static final boolean STRICT_PARSER = false; | 
|  |  | 
|  |     private static final boolean DEBUG = false; | 
|  |  | 
|  |     int tracks;                        | 
|  |     DataInputStream stream;    | 
|  |  | 
|  |     private int trackLength = 0;   | 
|  |     private byte[] trackData = null; | 
|  |     private int pos = 0; | 
|  |  | 
|  |     SMFParser() { | 
|  |     } | 
|  |  | 
|  |     private int readUnsigned() throws IOException { | 
|  |         return trackData[pos++] & 0xFF; | 
|  |     } | 
|  |  | 
|  |     private void read(byte[] data) throws IOException { | 
|  |         System.arraycopy(trackData, pos, data, 0, data.length); | 
|  |         pos += data.length; | 
|  |     } | 
|  |  | 
|  |     private long readVarInt() throws IOException { | 
|  |         long value = 0;  | 
|  |         int currentByte = 0; | 
|  |         do { | 
|  |             currentByte = trackData[pos++] & 0xFF; | 
|  |             value = (value << 7) + (currentByte & 0x7F); | 
|  |         } while ((currentByte & 0x80) != 0); | 
|  |         return value; | 
|  |     } | 
|  |  | 
|  |     private int readIntFromStream() throws IOException { | 
|  |         try { | 
|  |             return stream.readInt(); | 
|  |         } catch (EOFException eof) { | 
|  |             throw new EOFException("invalid MIDI file"); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     boolean nextTrack() throws IOException, InvalidMidiDataException { | 
|  |         int magic; | 
|  |         trackLength = 0; | 
|  |         do { | 
|  |              | 
|  |             if (stream.skipBytes(trackLength) != trackLength) { | 
|  |                 if (!STRICT_PARSER) { | 
|  |                     return false; | 
|  |                 } | 
|  |                 throw new EOFException("invalid MIDI file"); | 
|  |             } | 
|  |             magic = readIntFromStream(); | 
|  |             trackLength = readIntFromStream(); | 
|  |         } while (magic != MTrk_MAGIC); | 
|  |         if (!STRICT_PARSER) { | 
|  |             if (trackLength < 0) { | 
|  |                 return false; | 
|  |             } | 
|  |         } | 
|  |          | 
|  |         try { | 
|  |             trackData = new byte[trackLength]; | 
|  |         } catch (final OutOfMemoryError oom) { | 
|  |             throw new IOException("Track length too big", oom); | 
|  |         } | 
|  |         try { | 
|  |              | 
|  |             stream.readFully(trackData); | 
|  |         } catch (EOFException eof) { | 
|  |             if (!STRICT_PARSER) { | 
|  |                 return false; | 
|  |             } | 
|  |             throw new EOFException("invalid MIDI file"); | 
|  |         } | 
|  |         pos = 0; | 
|  |         return true; | 
|  |     } | 
|  |  | 
|  |     private boolean trackFinished() { | 
|  |         return pos >= trackLength; | 
|  |     } | 
|  |  | 
|  |     void readTrack(Track track) throws IOException, InvalidMidiDataException { | 
|  |         try { | 
|  |              | 
|  |             long tick = 0; | 
|  |  | 
|  |             // reset current status byte to 0 (invalid value). | 
|  |             // this should cause us to throw an InvalidMidiDataException if we don't | 
|  |              | 
|  |             int status = 0; | 
|  |             boolean endOfTrackFound = false; | 
|  |  | 
|  |             while (!trackFinished() && !endOfTrackFound) { | 
|  |                 MidiMessage message; | 
|  |  | 
|  |                 int data1 = -1;          | 
|  |                 int data2 = 0; | 
|  |  | 
|  |                 // each event has a tick delay and then the event data. | 
|  |  | 
|  |                  | 
|  |                 tick += readVarInt(); | 
|  |  | 
|  |                  | 
|  |                 int byteValue = readUnsigned(); | 
|  |  | 
|  |                 if (byteValue >= 0x80) { | 
|  |                     status = byteValue; | 
|  |                 } else { | 
|  |                     data1 = byteValue; | 
|  |                 } | 
|  |  | 
|  |                 switch (status & 0xF0) { | 
|  |                 case 0x80: | 
|  |                 case 0x90: | 
|  |                 case 0xA0: | 
|  |                 case 0xB0: | 
|  |                 case 0xE0: | 
|  |                      | 
|  |                     if (data1 == -1) { | 
|  |                         data1 = readUnsigned(); | 
|  |                     } | 
|  |                     data2 = readUnsigned(); | 
|  |                     message = new FastShortMessage(status | (data1 << 8) | (data2 << 16)); | 
|  |                     break; | 
|  |                 case 0xC0: | 
|  |                 case 0xD0: | 
|  |                      | 
|  |                     if (data1 == -1) { | 
|  |                         data1 = readUnsigned(); | 
|  |                     } | 
|  |                     message = new FastShortMessage(status | (data1 << 8)); | 
|  |                     break; | 
|  |                 case 0xF0: | 
|  |                      | 
|  |                     switch(status) { | 
|  |                     case 0xF0: | 
|  |                     case 0xF7: | 
|  |                          | 
|  |                         int sysexLength = (int) readVarInt(); | 
|  |                         byte[] sysexData = new byte[sysexLength]; | 
|  |                         read(sysexData); | 
|  |  | 
|  |                         SysexMessage sysexMessage = new SysexMessage(); | 
|  |                         sysexMessage.setMessage(status, sysexData, sysexLength); | 
|  |                         message = sysexMessage; | 
|  |                         break; | 
|  |  | 
|  |                     case 0xFF: | 
|  |                          | 
|  |                         int metaType = readUnsigned(); | 
|  |                         int metaLength = (int) readVarInt(); | 
|  |                         final byte[] metaData; | 
|  |                         try { | 
|  |                             metaData = new byte[metaLength]; | 
|  |                         } catch (final OutOfMemoryError oom) { | 
|  |                             throw new IOException("Meta length too big", oom); | 
|  |                         } | 
|  |  | 
|  |                         read(metaData); | 
|  |  | 
|  |                         MetaMessage metaMessage = new MetaMessage(); | 
|  |                         metaMessage.setMessage(metaType, metaData, metaLength); | 
|  |                         message = metaMessage; | 
|  |                         if (metaType == 0x2F) { | 
|  |                              | 
|  |                             endOfTrackFound = true; | 
|  |                         } | 
|  |                         break; | 
|  |                     default: | 
|  |                         throw new InvalidMidiDataException("Invalid status byte: " + status); | 
|  |                     }  | 
|  |                     break; | 
|  |                 default: | 
|  |                     throw new InvalidMidiDataException("Invalid status byte: " + status); | 
|  |                 }  | 
|  |                 track.add(new MidiEvent(message, tick)); | 
|  |             } // while | 
|  |         } catch (ArrayIndexOutOfBoundsException e) { | 
|  |             if (DEBUG) e.printStackTrace(); | 
|  |              | 
|  |             throw new EOFException("invalid MIDI file"); | 
|  |         } | 
|  |     } | 
|  | } |