org.tritonus.midi.file.StandardMidiFileWriter 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.DataOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.File;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.SysexMessage;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.Sequence;
import javax.sound.midi.Track;
import javax.sound.midi.spi.MidiFileWriter;
import org.tritonus.share.TDebug;
/** Writer for Standard Midi Files.
This writer can write type 0 and type 1 files. It cannot write type
2 files.
*/
public class StandardMidiFileWriter
extends MidiFileWriter
{
/** TODO:
*/
public static boolean USE_RUNNING_STATUS = true;
/** TODO:
*/
public static boolean CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX = true;
/** Return supported MIDI file types.
This writer supports Standard MIDI File (SMF) types 0 and 1.
So these numbers are returned here.
@return an array of supported SMF types.
*/
public int[] getMidiFileTypes()
{
return new int[]{0, 1};
}
/** Return the supported MIDI file types for a given Sequence.
This writer supports Standard MIDI File (SMF) types 0 and 1.
Depending on the Sequence, either 0 or 1 is returned.
@return and array of supported SMF types. It contains 0 if
the Sequence has one track, 1 otherwise.
*/
public int[] getMidiFileTypes(Sequence sequence)
{
Track[] tracks = sequence.getTracks();
if (tracks.length == 1)
{
return new int[]{0};
}
else
{
return new int[]{1};
}
}
/** Write a Sequence as Standard MIDI File (SMF) to an OutputStream.
A byte stream representing the passed Sequence is written
to the output stream in the given file type.
@return The number of bytes written to the output stream.
*/
public int write(Sequence sequence,
int nFileType,
OutputStream outputStream)
throws IOException
{
if (! isFileTypeSupported(nFileType, sequence))
{
throw new IllegalArgumentException("file type is not supported for this sequence");
}
Track[] aTracks = sequence.getTracks();
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
dataOutputStream.writeInt(MidiConstants.HEADER_MAGIC);
dataOutputStream.writeInt(6); // header length
dataOutputStream.writeShort(nFileType);
dataOutputStream.writeShort(aTracks.length);
float fDivisionType = sequence.getDivisionType();
int nResolution = sequence.getResolution();
int nDivision = 0;
if (fDivisionType == Sequence.PPQ)
{
nDivision = nResolution & 0x7fff;
}
else
{
// TODO:
}
dataOutputStream.writeShort(nDivision); // unsigned?
int nBytesWritten = 14;
for (int nTrack = 0; nTrack < aTracks.length; nTrack++)
{
nBytesWritten += writeTrack(aTracks[nTrack],
dataOutputStream);
}
return nBytesWritten;
}
/** Write a Sequence as Standard MIDI File (SMF) to a File.
A byte stream representing the passed Sequence is written
to the file in the given file type.
@return The number of bytes written to the file.
*/
public int write(Sequence sequence,
int nFileType,
File file)
throws IOException
{
OutputStream outputStream = new FileOutputStream(file);
int nBytes = write(sequence,
nFileType,
outputStream);
outputStream.close();
return nBytes;
}
/** Write a Track to a DataOutputStream.
@return The number of bytes written.
*/
private static int writeTrack(Track track,
DataOutputStream dataOutputStream)
throws IOException
{
/** The number of bytes written. This is used as return
value for this method.
*/
int nLength = 0;
if (dataOutputStream != null)
{
dataOutputStream.writeInt(MidiConstants.TRACK_MAGIC);
}
/*
* This is a recursive call!
* It is to find out the length of the track without
* actually writing. Having the second parameter as
* null tells writeTrack() and its subordinate
* methods to not write out data bytes.
*/
int nTrackLength = 0;
if (dataOutputStream != null)
{
nTrackLength = writeTrack(track, null);
}
if (dataOutputStream != null)
{
dataOutputStream.writeInt(nTrackLength);
}
MidiEvent previousEvent = null;
int[] anRunningStatusByte = new int[1];
anRunningStatusByte[0] = -1;
for (int nEvent = 0; nEvent < track.size(); nEvent++)
{
MidiEvent event = track.get(nEvent);
nLength += writeEvent(event,
previousEvent,
anRunningStatusByte,
dataOutputStream);
previousEvent = event;
}
return nLength;
}
/** TODO:
*/
private static int writeEvent(MidiEvent event,
MidiEvent previousEvent,
int[] anRunningStatusByte,
DataOutputStream dataOutputStream)
throws IOException
{
/** The number of bytes written. This is used as return
value for this method.
*/
int nLength = 0;
long lTickDelta = 0;
if (previousEvent != null)
{
lTickDelta = event.getTick() - previousEvent.getTick();
}
if (lTickDelta < 0)
{
TDebug.out("StandardMidiFileWriter.writeEvent(): warning: events not in order");
}
// add bytes according to coded length of delta
nLength += writeVariableLengthQuantity(lTickDelta, dataOutputStream);
MidiMessage message = event.getMessage();
// int nDataLength = message.getLength();
if (message instanceof ShortMessage)
{
nLength += writeShortMessage((ShortMessage) message,
anRunningStatusByte,
dataOutputStream);
}
else if (message instanceof SysexMessage)
{
nLength += writeSysexMessage((SysexMessage) message,
anRunningStatusByte,
dataOutputStream);
}
else if (message instanceof MetaMessage)
{
nLength += writeMetaMessage((MetaMessage) message,
anRunningStatusByte,
dataOutputStream);
}
else
{
TDebug.out("StandardMidiFileWriter.writeEvent(): warning: unknown message class");
}
return nLength;
}
/** TODO:
*/
private static int writeShortMessage(ShortMessage message,
int[] anRunningStatusByte,
DataOutputStream dataOutputStream)
throws IOException
{
/** The number of bytes written. This is used as return
value for this method.
*/
int nLength = 0;
int nDataLength = message.getLength();
if (USE_RUNNING_STATUS && anRunningStatusByte[0] == message.getStatus())
{
/*
* Write without status byte.
*/
if (dataOutputStream != null)
{
dataOutputStream.write(
message.getMessage(),
1, nDataLength - 1);
}
nLength += nDataLength - 1;
}
else
{
/*
* Write with status byte.
*/
if (dataOutputStream != null)
{
dataOutputStream.write(
message.getMessage(),
0, nDataLength);
}
nLength += nDataLength;
anRunningStatusByte[0] = message.getStatus();
}
return nLength;
}
/** TODO:
*/
private static int writeSysexMessage(SysexMessage message,
int[] anRunningStatusByte,
DataOutputStream dataOutputStream)
throws IOException
{
/** The number of bytes written. This is used as return
value for this method.
*/
int nLength = 0;
int nDataLength = message.getLength();
if (CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX)
{
anRunningStatusByte[0] = -1;
}
if (dataOutputStream != null)
{
dataOutputStream.write(message.getStatus());
}
nLength++;
nLength += writeVariableLengthQuantity(
nDataLength - 1,
dataOutputStream);
if (dataOutputStream != null)
{
dataOutputStream.write(
message.getData(),
0, nDataLength - 1);
}
nLength += nDataLength - 1;
return nLength;
}
/** TODO:
*/
private static int writeMetaMessage(MetaMessage message,
int[] anRunningStatusByte,
DataOutputStream dataOutputStream)
throws IOException
{
/** The number of bytes written. This is used as return
value for this method.
*/
int nLength = 0;
byte[] abData = message.getData();
int nDataLength = abData.length;
if (CANCEL_RUNNING_STATUS_ON_META_AND_SYSEX)
{
anRunningStatusByte[0] = -1;
}
if (dataOutputStream != null)
{
dataOutputStream.write(message.getStatus());
dataOutputStream.write(message.getType());
}
nLength += 2;
nLength += writeVariableLengthQuantity(
nDataLength,
dataOutputStream);
if (dataOutputStream != null)
{
dataOutputStream.write(abData);
}
nLength += nDataLength;
return nLength;
}
/** TODO:
outputStream == 0 signals to only calculate the number of
needed to represent the value.
*/
private static int writeVariableLengthQuantity(long lValue, OutputStream outputStream)
throws IOException
{
/** The number of bytes written. This is used as return
value for this method.
*/
int nLength = 0;
// IDEA: use a loop
boolean bWritingStarted = false;
int nByte = (int) ((lValue >> 21) & 0x7f);
if (nByte != 0)
{
if (outputStream != null)
{
outputStream.write(nByte | 0x80);
}
nLength++;
bWritingStarted = true;
}
nByte = (int) ((lValue >> 14) & 0x7f);
if (nByte != 0 || bWritingStarted)
{
if (outputStream != null)
{
outputStream.write(nByte | 0x80);
}
nLength++;
bWritingStarted = true;
}
nByte = (int) ((lValue >> 7) & 0x7f);
if (nByte != 0 || bWritingStarted)
{
if (outputStream != null)
{
outputStream.write(nByte | 0x80);
}
nLength++;
}
nByte = (int) (lValue & 0x7f);
if (outputStream != null)
{
outputStream.write(nByte);
}
nLength++;
return nLength;
}
}
/*** StandardMidiFileWriter.java ***/