| /* MidiFileReader.java -- Read MIDI files. |
| Copyright (C) 2006, 2012 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath 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 for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| package gnu.javax.sound.midi.file; |
| |
| import java.io.DataInputStream; |
| 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.ShortMessage; |
| import javax.sound.midi.SysexMessage; |
| import javax.sound.midi.Track; |
| |
| /** |
| * A MIDI file reader. |
| * |
| * This code reads MIDI file types 0 and 1. |
| * |
| * There are many decent documents on the web describing the MIDI file |
| * format. I didn't bother looking for the official document. If it |
| * exists, I'm not even sure if it is freely available. We should |
| * update this comment if we find out anything helpful here. |
| * |
| * @author Anthony Green (green@redhat.com) |
| * |
| */ |
| public class MidiFileReader extends javax.sound.midi.spi.MidiFileReader |
| { |
| /* Get the MidiFileFormat for the given input stream. |
| * @see javax.sound.midi.spi.MidiFileReader#getMidiFileFormat(java.io.InputStream) |
| */ |
| public MidiFileFormat getMidiFileFormat(InputStream in) |
| throws InvalidMidiDataException, IOException |
| { |
| DataInputStream din; |
| if (in instanceof DataInputStream) |
| din = (DataInputStream) in; |
| else |
| din = new DataInputStream(in); |
| |
| int type, ntracks, division, resolution, bytes; |
| float divisionType; |
| |
| if (din.readInt() != 0x4d546864) // "MThd" |
| throw new InvalidMidiDataException("Invalid MIDI chunk header."); |
| |
| bytes = din.readInt(); |
| if (bytes < 6) |
| throw new |
| InvalidMidiDataException("Invalid MIDI chunk header length: " + bytes); |
| |
| type = din.readShort(); |
| if (type < 0 || type > 2) |
| throw new |
| InvalidMidiDataException("Invalid MIDI file type value: " + type); |
| |
| ntracks = din.readShort(); |
| if (ntracks <= 0) |
| throw new |
| InvalidMidiDataException("Invalid number of MIDI tracks: " + ntracks); |
| |
| division = din.readShort(); |
| if ((division & 0x8000) != 0) |
| { |
| division = -((division >>> 8) & 0xFF); |
| switch (division) |
| { |
| 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("Invalid MIDI frame division type: " |
| + division); |
| } |
| resolution = division & 0xff; |
| } |
| else |
| { |
| divisionType = Sequence.PPQ; |
| resolution = division & 0x7fff; |
| } |
| |
| // If we haven't read every byte in the header now, just skip the rest. |
| din.skip(bytes - 6); |
| |
| return new ExtendedMidiFileFormat(type, divisionType, resolution, |
| MidiFileFormat.UNKNOWN_LENGTH, |
| MidiFileFormat.UNKNOWN_LENGTH, ntracks); |
| } |
| |
| /* Get the MidiFileFormat from the given URL. |
| * @see javax.sound.midi.spi.MidiFileReader#getMidiFileFormat(java.net.URL) |
| */ |
| public MidiFileFormat getMidiFileFormat(URL url) |
| throws InvalidMidiDataException, IOException |
| { |
| InputStream is = url.openStream(); |
| try |
| { |
| return getMidiFileFormat(is); |
| } |
| finally |
| { |
| is.close(); |
| } |
| } |
| |
| /* Get the MidiFileFormat from the given file. |
| * @see javax.sound.midi.spi.MidiFileReader#getMidiFileFormat(java.io.File) |
| */ |
| public MidiFileFormat getMidiFileFormat(File file) |
| throws InvalidMidiDataException, IOException |
| { |
| InputStream is = new FileInputStream(file); |
| try |
| { |
| return getMidiFileFormat(is); |
| } |
| finally |
| { |
| is.close(); |
| } |
| } |
| |
| /* Get the MIDI Sequence found in this input stream. |
| * @see javax.sound.midi.spi.MidiFileReader#getSequence(java.io.InputStream) |
| */ |
| public Sequence getSequence(InputStream is) throws InvalidMidiDataException, |
| IOException |
| { |
| MidiDataInputStream din = new MidiDataInputStream(is); |
| ExtendedMidiFileFormat mff = (ExtendedMidiFileFormat) getMidiFileFormat(din); |
| |
| Sequence seq = new Sequence(mff.getDivisionType(), mff.getResolution()); |
| |
| int ntracks = mff.getNumberTracks(); |
| |
| while (ntracks-- > 0) |
| { |
| Track track = seq.createTrack(); |
| int Mtrk = din.readInt(); |
| if (Mtrk != 0x4d54726b) |
| throw new InvalidMidiDataException("Invalid MIDI track header."); |
| din.readInt(); // length |
| |
| int runningStatus = -1; |
| int click = 0; |
| |
| // Set this to true when we've hit an End of Track meta event. |
| boolean done = false; |
| |
| // Read all events. |
| while (! done) |
| { |
| MidiMessage mm; |
| int dtime = din.readVariableLengthInt(); |
| click += dtime; |
| |
| int sbyte = din.readUnsignedByte(); |
| |
| if (sbyte < 0xf0) |
| { |
| ShortMessage sm; |
| switch (sbyte & 0xf0) |
| { |
| case ShortMessage.NOTE_OFF: |
| case ShortMessage.NOTE_ON: |
| case ShortMessage.POLY_PRESSURE: |
| case ShortMessage.CONTROL_CHANGE: |
| case ShortMessage.PITCH_BEND: |
| case ShortMessage.SONG_POSITION_POINTER: |
| sm = new ShortMessage(); |
| sm.setMessage(sbyte, din.readByte(), din.readByte()); |
| runningStatus = sbyte; |
| break; |
| |
| case ShortMessage.PROGRAM_CHANGE: |
| case ShortMessage.CHANNEL_PRESSURE: |
| case ShortMessage.SONG_SELECT: |
| case 0xF5: // FIXME: unofficial bus select. Not in spec?? |
| sm = new ShortMessage(); |
| sm.setMessage(sbyte, din.readByte(), 0); |
| runningStatus = sbyte; |
| break; |
| |
| case ShortMessage.TUNE_REQUEST: |
| case ShortMessage.END_OF_EXCLUSIVE: |
| case ShortMessage.TIMING_CLOCK: |
| case ShortMessage.START: |
| case ShortMessage.CONTINUE: |
| case ShortMessage.STOP: |
| case ShortMessage.ACTIVE_SENSING: |
| case ShortMessage.SYSTEM_RESET: |
| sm = new ShortMessage(); |
| sm.setMessage(sbyte, 0, 0); |
| runningStatus = sbyte; |
| break; |
| |
| default: |
| if (runningStatus != - 1) |
| { |
| switch (runningStatus & 0xf0) |
| { |
| case ShortMessage.NOTE_OFF: |
| case ShortMessage.NOTE_ON: |
| case ShortMessage.POLY_PRESSURE: |
| case ShortMessage.CONTROL_CHANGE: |
| case ShortMessage.PITCH_BEND: |
| case ShortMessage.SONG_POSITION_POINTER: |
| sm = new ShortMessage(); |
| sm.setMessage(runningStatus, sbyte, din.readByte()); |
| break; |
| |
| case ShortMessage.PROGRAM_CHANGE: |
| case ShortMessage.CHANNEL_PRESSURE: |
| case ShortMessage.SONG_SELECT: |
| case 0xF5: // FIXME: unofficial bus select. Not in |
| // spec?? |
| sm = new ShortMessage(); |
| sm.setMessage(runningStatus, sbyte, 0); |
| continue; |
| |
| case ShortMessage.TUNE_REQUEST: |
| case ShortMessage.END_OF_EXCLUSIVE: |
| case ShortMessage.TIMING_CLOCK: |
| case ShortMessage.START: |
| case ShortMessage.CONTINUE: |
| case ShortMessage.STOP: |
| case ShortMessage.ACTIVE_SENSING: |
| case ShortMessage.SYSTEM_RESET: |
| sm = new ShortMessage(); |
| sm.setMessage(runningStatus, 0, 0); |
| continue; |
| |
| default: |
| throw new |
| InvalidMidiDataException("Invalid Short MIDI Event: " |
| + sbyte); |
| } |
| } |
| else |
| throw new |
| InvalidMidiDataException("Invalid Short MIDI Event: " |
| + sbyte); |
| } |
| mm = sm; |
| } |
| else if (sbyte == 0xf0 || sbyte == 0xf7) |
| { |
| // System Exclusive event |
| int slen = din.readVariableLengthInt(); |
| byte sysex[] = new byte[slen]; |
| din.readFully(sysex); |
| SysexMessage sm = new SysexMessage(); |
| sm.setMessage(sbyte, sysex, slen); |
| mm = sm; |
| runningStatus = - 1; |
| } |
| else if (sbyte == 0xff) |
| { |
| // Meta Message |
| byte mtype = din.readByte(); |
| int mlen = din.readVariableLengthInt(); |
| byte meta[] = new byte[mlen]; |
| din.readFully(meta); |
| MetaMessage metam = new MetaMessage(); |
| metam.setMessage(mtype, meta, mlen); |
| mm = metam; |
| |
| if (mtype == 0x2f) // End of Track |
| done = true; |
| |
| runningStatus = - 1; |
| } |
| else |
| { |
| throw new InvalidMidiDataException("Invalid status byte: " |
| + sbyte); |
| } |
| |
| track.add(new MidiEvent(mm, click)); |
| } |
| } |
| |
| return seq; |
| } |
| |
| /* Get the MIDI Sequence found at the given URL. |
| * @see javax.sound.midi.spi.MidiFileReader#getSequence(java.net.URL) |
| */ |
| public Sequence getSequence(URL url) throws InvalidMidiDataException, |
| IOException |
| { |
| InputStream is = url.openStream(); |
| try |
| { |
| return getSequence(is); |
| } |
| finally |
| { |
| is.close(); |
| } |
| } |
| |
| /* Get the MIDI Sequence found in the given file. |
| * @see javax.sound.midi.spi.MidiFileReader#getSequence(java.io.File) |
| */ |
| public Sequence getSequence(File file) throws InvalidMidiDataException, |
| IOException |
| { |
| InputStream is = new FileInputStream(file); |
| try |
| { |
| return getSequence(is); |
| } |
| finally |
| { |
| is.close(); |
| } |
| } |
| } |