org.tritonus.midi.file.StandardMidiFileReader Maven / Gradle / Ivy
The newest version!
/*
* StandardMidiFileReader.java
*
* This file is part of Tritonus: http://www.tritonus.org/
*/
/*
* Copyright (c) 1999, 2000 by Matthias Pfisterer
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
|<--- this code is formatted to fit into 80 columns --->|
*/
package org.tritonus.midi.file;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.File;
import java.net.URL;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiFileFormat;
import javax.sound.midi.MidiEvent;
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;
import javax.sound.midi.spi.MidiFileReader;
import org.tritonus.share.TDebug;
import org.tritonus.share.midi.TMidiFileFormat;
/** TODO:
*/
public class StandardMidiFileReader
extends MidiFileReader
{
/** TODO:
*/
public static boolean CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX = true;
private static final int STATUS_NONE = 0;
private static final int STATUS_ONE_BYTE = 1;
private static final int STATUS_TWO_BYTES = 2;
private static final int STATUS_SYSEX = 3;
private static final int STATUS_META = 4;
/** TODO:
*/
public MidiFileFormat getMidiFileFormat(InputStream inputStream)
throws InvalidMidiDataException, IOException
{
DataInputStream dataInputStream = new DataInputStream(inputStream);
int nHeaderMagic = dataInputStream.readInt();
if (nHeaderMagic != MidiConstants.HEADER_MAGIC)
{
throw new InvalidMidiDataException("not a MIDI file: wrong header magic");
}
int nHeaderLength = dataInputStream.readInt();
if (nHeaderLength < 6)
{
throw new InvalidMidiDataException("corrupt MIDI file: wrong header length");
}
int nType = dataInputStream.readShort();
if (nType < 0 || nType > 2)
{
throw new InvalidMidiDataException("corrupt MIDI file: illegal type");
}
if (nType == 2)
{
throw new InvalidMidiDataException("this implementation doesn't support type 2 MIDI files");
}
int nNumTracks = dataInputStream.readShort();
if (nNumTracks <= 0)
{
throw new InvalidMidiDataException("corrupt MIDI file: number of tracks must be positive");
}
if (nType == 0 && nNumTracks != 1)
{
throw new InvalidMidiDataException("corrupt MIDI file: type 0 files must contain exactely one track");
}
int nDivision = dataInputStream.readUnsignedShort();
float fDivisionType = -1.0F;
int nResolution = -1;
if ((nDivision & 0x8000) != 0) //frame division
{
// TODO:
int nFrameType = -((nDivision >>> 8) & 0xFF);
switch (nFrameType)
{
case 24:
fDivisionType = Sequence.SMPTE_24;
break;
case 25:
fDivisionType = Sequence.SMPTE_25;
break;
case 29:
fDivisionType = Sequence.SMPTE_30DROP;
break;
case 30:
fDivisionType = Sequence.SMPTE_30;
break;
default:
throw new InvalidMidiDataException("corrupt MIDI file: illegal frame division type");
}
nResolution = nDivision & 0xff;
}
else // BPM division
{
fDivisionType = Sequence.PPQ;
nResolution = nDivision & 0x7fff;
}
// skip additional bytes in the header
dataInputStream.skip(nHeaderLength - 6);
MidiFileFormat midiFileFormat = new TMidiFileFormat(
nType,
fDivisionType,
nResolution,
MidiFileFormat.UNKNOWN_LENGTH,
MidiFileFormat.UNKNOWN_LENGTH,
nNumTracks);
return midiFileFormat;
}
/** TODO:
*/
public MidiFileFormat getMidiFileFormat(URL url)
throws InvalidMidiDataException, IOException
{
InputStream inputStream = url.openStream();
try
{
return getMidiFileFormat(inputStream);
}
finally
{
inputStream.close();
}
}
/** TODO:
*/
public MidiFileFormat getMidiFileFormat(File file)
throws InvalidMidiDataException, IOException
{
InputStream inputStream = new FileInputStream(file);
//inputStream = new BufferedInputStream(inputStream, 1024);
try
{
return getMidiFileFormat(inputStream);
}
finally
{
inputStream.close();
}
}
/** TODO:
*/
public Sequence getSequence(URL url)
throws InvalidMidiDataException, IOException
{
InputStream inputStream = url.openStream();
try
{
return getSequence(inputStream);
}
catch (InvalidMidiDataException e)
{
if (TDebug.TraceAllExceptions)
{
TDebug.out(e);
}
inputStream.close();
throw e;
}
catch (IOException e)
{
if (TDebug.TraceAllExceptions)
{
TDebug.out(e);
}
inputStream.close();
throw e;
}
}
/** TODO:
*/
public Sequence getSequence(File file)
throws InvalidMidiDataException, IOException
{
InputStream inputStream = new FileInputStream(file);
// inputStream = new BufferedInputStream(inputStream, 1024);
try
{
return getSequence(inputStream);
}
catch (InvalidMidiDataException e)
{
if (TDebug.TraceAllExceptions)
{
TDebug.out(e);
}
inputStream.close();
throw e;
}
catch (IOException e)
{
if (TDebug.TraceAllExceptions)
{
TDebug.out(e);
}
inputStream.close();
throw e;
}
}
/** TODO:
*/
public Sequence getSequence(InputStream inputStream)
throws InvalidMidiDataException, IOException
{
MidiFileFormat midiFileFormat = getMidiFileFormat(inputStream);
Sequence sequence = new Sequence(
midiFileFormat.getDivisionType(),
midiFileFormat.getResolution());
DataInputStream dataInputStream = new DataInputStream(inputStream);
int nNumTracks = ((TMidiFileFormat) midiFileFormat).getTrackCount();
for (int nTrack = 0; nTrack < nNumTracks; nTrack++)
{
Track track = sequence.createTrack();
readTrack(dataInputStream, track);
}
return sequence;
}
/** TODO:
*/
private void readTrack(DataInputStream dataInputStream, Track track)
throws InvalidMidiDataException, IOException
{
// search for a "MTrk" chunk
while (true)
{
int nMagic = dataInputStream.readInt();
if (nMagic == MidiConstants.TRACK_MAGIC)
{
break;
}
int nChunkLength = dataInputStream.readInt();
if (nChunkLength % 2 != 0)
{
nChunkLength++;
}
dataInputStream.skip(nChunkLength);
}
int nTrackChunkLength = dataInputStream.readInt();
long lTicks = 0;
long[] alRemainingBytes = new long[1];
alRemainingBytes[0] = nTrackChunkLength;
int[] anRunningStatusByte = new int[1];
// indicates no running status in effect
anRunningStatusByte[0] = -1;
while (alRemainingBytes[0] > 0)
{
long lDeltaTicks = readVariableLengthQuantity(dataInputStream, alRemainingBytes);
// TDebug.out("delta ticks: " + lDeltaTicks);
lTicks += lDeltaTicks;
MidiEvent event = readEvent(dataInputStream, alRemainingBytes, anRunningStatusByte, lTicks);
track.add(event);
}
}
/** TODO:
*/
private static MidiEvent readEvent(DataInputStream dataInputStream, long[] alRemainingBytes, int[] anRunningStatusByte, long lTicks)
throws InvalidMidiDataException, IOException
{
int nStatusByte = readUnsignedByte(dataInputStream, alRemainingBytes);
// TDebug.out("status byte: " + nStatusByte);
MidiMessage message = null;
boolean bRunningStatusApplies = false;
int nSavedByte = 0;
if (nStatusByte < 0x80)
{
if (anRunningStatusByte[0] != -1)
{
bRunningStatusApplies = true;
nSavedByte = nStatusByte;
nStatusByte = anRunningStatusByte[0];
}
else
{
throw new InvalidMidiDataException("corrupt MIDI file: status byte missing");
}
}
switch (getType(nStatusByte))
{
case STATUS_ONE_BYTE:
int nByte = 0;
if (bRunningStatusApplies)
{
nByte = nSavedByte;
}
else
{
nByte = readUnsignedByte(dataInputStream, alRemainingBytes);
anRunningStatusByte[0] = nStatusByte;
}
ShortMessage shortMessage1 = new ShortMessage();
shortMessage1.setMessage(nStatusByte, nByte, 0);
message = shortMessage1;
break;
case STATUS_TWO_BYTES:
int nByte1 = 0;
if (bRunningStatusApplies)
{
nByte1 = nSavedByte;
}
else
{
nByte1 = readUnsignedByte(dataInputStream, alRemainingBytes);
anRunningStatusByte[0] = nStatusByte;
}
int nByte2 = readUnsignedByte(dataInputStream, alRemainingBytes);
ShortMessage shortMessage2 = new ShortMessage();
shortMessage2.setMessage(nStatusByte, nByte1, nByte2);
message = shortMessage2;
break;
case STATUS_SYSEX:
if (CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX)
{
anRunningStatusByte[0] = -1;
}
int nSysexDataLength = (int) readVariableLengthQuantity(dataInputStream, alRemainingBytes);
byte[] abSysexData = new byte[nSysexDataLength];
for (int i = 0; i < nSysexDataLength; i++)
{
int nDataByte = readUnsignedByte(dataInputStream, alRemainingBytes);
abSysexData[i] = (byte) nDataByte;
}
SysexMessage sysexMessage = new SysexMessage();
sysexMessage.setMessage(nStatusByte, abSysexData, nSysexDataLength);
message = sysexMessage;
break;
case STATUS_META:
if (CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX)
{
anRunningStatusByte[0] = -1;
}
int nTypeByte = readUnsignedByte(dataInputStream, alRemainingBytes);
int nMetaDataLength = (int) readVariableLengthQuantity(dataInputStream, alRemainingBytes);
byte[] abMetaData = new byte[nMetaDataLength];
for (int i = 0; i < nMetaDataLength; i++)
{
int nDataByte = readUnsignedByte(dataInputStream, alRemainingBytes);
abMetaData[i] = (byte) nDataByte;
}
MetaMessage metaMessage = new MetaMessage();
metaMessage.setMessage(nTypeByte, abMetaData, nMetaDataLength);
message = metaMessage;
break;
default:
}
MidiEvent event = new MidiEvent(message, lTicks);
return event;
}
// TODO: use table
/** TODO:
*/
private static int getType(int nStatusByte)
{
if (nStatusByte < 0xf0) // channel voice or mode command
{
int nCommand = nStatusByte & 0xf0;
switch (nCommand)
{
case 0x80: // note off
case 0x90: // note on
case 0xa0: // polyphonic key pressure
case 0xb0: // control change
case 0xe0: // pitch wheel change
return STATUS_TWO_BYTES;
case 0xc0: // program change
case 0xd0: // channel pressure
return STATUS_ONE_BYTE;
default:
return STATUS_NONE;
}
}
else if (nStatusByte == 0xf0 || nStatusByte == 0xf7)
{
return STATUS_SYSEX;
}
else if (nStatusByte == 0xff)
{
return STATUS_META;
}
else
{
return STATUS_NONE;
}
}
/** TODO:
*/
public static long readVariableLengthQuantity(DataInputStream dataInputStream, long[] alRemainingBytes)
throws InvalidMidiDataException, IOException
{
long lValue = 0;
int nByteCount = 0;
while (nByteCount < 4)
{
int nByte = readUnsignedByte(dataInputStream, alRemainingBytes);
nByteCount++;
lValue <<= 7;
lValue |= (nByte & 0x7f);
if (nByte < 128) // MSB is 0: last byte
{
return lValue;
}
}
throw new InvalidMidiDataException("not a MIDI file: unterminated variable-length quantity");
}
/** TODO:
*/
public static int readUnsignedByte(DataInputStream dataInputStream, long[] alRemainingBytes)
throws IOException
{
int nByte = dataInputStream.readUnsignedByte();
// already done in DataInputStream.readUnsignedByte();
// if (nByte < 0)
// {
// throw new EOFException();
// }
alRemainingBytes[0]--;
return nByte;
}
}
/*** StandardMidiFileReader.java ***/