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

Save This Page
Home » openjdk-7 » com.sun.media » sound » [javadoc | source]