Back to index...
/*
 * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
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;
/**
 * MIDI file reader.
 *
 * @author Kara Kytle
 * @author Jan Borgersen
 * @author Florian Bomers
 */
public final class StandardMidiFileReader extends MidiFileReader {
    private static final int MThd_MAGIC = 0x4d546864;  // 'MThd'
    private static final int bisBufferSize = 1024; // buffer size in buffered input streams
    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()
    // returns format having invalid length
    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) ) {
                // not MIDI
                throw new InvalidMidiDataException("not a valid MIDI file");
            }
            // read header length
            int bytesRemaining = dis.readInt() - 6;
            type = dis.readShort();
            numtracks = dis.readShort();
            int timing = dis.readShort();
            // decipher the timing code
            if (timing > 0) {
                // tempo based timing.  value is ticks per beat.
                divisionType = Sequence.PPQ;
                resolution = timing;
            } else {
                // SMPTE based timing.  first decipher the frame code.
                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);
                }
                // now determine the timing resolution in ticks per frame.
                resolution = timing & 0xFF;
            }
            if (smfParser != null) {
                // remainder of this chunk
                dis.skip(bytesRemaining);
                smfParser.tracks = numtracks;
            }
        } finally {
            // if only reading the file format, reset the stream
            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(); // throws IOException
        BufferedInputStream bis = new BufferedInputStream( urlStream, bisBufferSize );
        MidiFileFormat fileFormat = null;
        try {
            fileFormat = getMidiFileFormat( bis ); // throws InvalidMidiDataException
        } finally {
            bis.close();
        }
        return fileFormat;
    }
    public MidiFileFormat getMidiFileFormat(File file) throws InvalidMidiDataException, IOException {
        FileInputStream fis = new FileInputStream(file); // throws IOException
        BufferedInputStream bis = new BufferedInputStream(fis, bisBufferSize);
        // $$fb 2002-04-17: part of fix for 4635286: MidiSystem.getMidiFileFormat() returns format having invalid length
        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);
        // must be MIDI Type 0 or Type 1
        if ((format.getType() != 0) && (format.getType() != 1)) {
            throw new InvalidMidiDataException("Invalid or unsupported file type: "  + format.getType());
        }
        // construct the sequence object
        Sequence sequence = new Sequence(format.getDivisionType(), format.getResolution());
        // for each track, go to the beginning and read the track events
        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();  // throws IOException
        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); // throws IOException
        is = new BufferedInputStream(is, bisBufferSize);
        Sequence seq = null;
        try {
            seq = getSequence(is);
        } finally {
            is.close();
        }
        return seq;
    }
}
//=============================================================================================================
/**
 * State variables during parsing of a MIDI file.
 */
final class SMFParser {
    private static final int MTrk_MAGIC = 0x4d54726b;  // 'MTrk'
    // set to true to not allow corrupt MIDI files tombe loaded
    private static final boolean STRICT_PARSER = false;
    private static final boolean DEBUG = false;
    int tracks;                       // number of tracks
    DataInputStream stream;   // the stream to read from
    private int trackLength = 0;  // remaining length in track
    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; // the variable-lengh int value
        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 {
            // $$fb 2003-08-20: fix for 4910986: MIDI file parser breaks up on http connection
            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;
            }
        }
        // now read track in a byte array
        try {
            trackData = new byte[trackLength];
        } catch (final OutOfMemoryError oom) {
            throw new IOException("Track length too big", oom);
        }
        try {
            // $$fb 2003-08-20: fix for 4910986: MIDI file parser breaks up on http connection
            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 {
            // reset current tick to 0
            long tick = 0;
            // reset current status byte to 0 (invalid value).
            // this should cause us to throw an InvalidMidiDataException if we don't
            // get a valid status byte from the beginning of the track.
            int status = 0;
            boolean endOfTrackFound = false;
            while (!trackFinished() && !endOfTrackFound) {
                MidiMessage message;
                int data1 = -1;         // initialize to invalid value
                int data2 = 0;
                // each event has a tick delay and then the event data.
                // first read the delay (a variable-length int) and update our tick value
                tick += readVarInt();
                // check for new status
                int byteValue = readUnsigned();
                if (byteValue >= 0x80) {
                    status = byteValue;
                } else {
                    data1 = byteValue;
                }
                switch (status & 0xF0) {
                case 0x80:
                case 0x90:
                case 0xA0:
                case 0xB0:
                case 0xE0:
                    // two data bytes
                    if (data1 == -1) {
                        data1 = readUnsigned();
                    }
                    data2 = readUnsigned();
                    message = new FastShortMessage(status | (data1 << 8) | (data2 << 16));
                    break;
                case 0xC0:
                case 0xD0:
                    // one data byte
                    if (data1 == -1) {
                        data1 = readUnsigned();
                    }
                    message = new FastShortMessage(status | (data1 << 8));
                    break;
                case 0xF0:
                    // sys-ex or meta
                    switch(status) {
                    case 0xF0:
                    case 0xF7:
                        // sys ex
                        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:
                        // meta
                        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) {
                            // end of track means it!
                            endOfTrackFound = true;
                        }
                        break;
                    default:
                        throw new InvalidMidiDataException("Invalid status byte: " + status);
                    } // switch sys-ex or meta
                    break;
                default:
                    throw new InvalidMidiDataException("Invalid status byte: " + status);
                } // switch
                track.add(new MidiEvent(message, tick));
            } // while
        } catch (ArrayIndexOutOfBoundsException e) {
            if (DEBUG) e.printStackTrace();
            // fix for 4834374
            throw new EOFException("invalid MIDI file");
        }
    }
}
Back to index...