--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/media/sound/StandardMidiFileWriter.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,473 @@
+/*
+ * Copyright 1999-2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.media.sound;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.SequenceInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.BufferedOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.lang.IllegalArgumentException;
+import java.io.OutputStream;
+import java.util.Vector;
+
+import javax.sound.midi.MidiFileFormat;
+import javax.sound.midi.InvalidMidiDataException;
+import javax.sound.midi.MidiEvent;
+import javax.sound.midi.MetaMessage;
+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.MidiFileWriter;
+
+
+/**
+ * MIDI file writer.
+ *
+ * @author Kara Kytle
+ * @author Jan Borgersen
+ */
+public class StandardMidiFileWriter extends MidiFileWriter {
+
+ private static final int MThd_MAGIC = 0x4d546864; // 'MThd'
+ private static final int MTrk_MAGIC = 0x4d54726b; // 'MTrk'
+
+ private static final int ONE_BYTE = 1;
+ private static final int TWO_BYTE = 2;
+ private static final int SYSEX = 3;
+ private static final int META = 4;
+ private static final int ERROR = 5;
+ private static final int IGNORE = 6;
+
+ private static final int MIDI_TYPE_0 = 0;
+ private static final int MIDI_TYPE_1 = 1;
+
+ private static final int bufferSize = 16384; // buffersize for write
+ private DataOutputStream tddos; // data output stream for track writing
+
+
+
+ /**
+ * MIDI parser types
+ */
+ public static final int types[] = {
+ MIDI_TYPE_0,
+ MIDI_TYPE_1
+ };
+
+
+ /**
+ * new
+ */
+ public int[] getMidiFileTypes() {
+ int[] localArray = new int[types.length];
+ System.arraycopy(types, 0, localArray, 0, types.length);
+ return localArray;
+ }
+
+ /**
+ * Obtains the file types that this provider can write from the
+ * sequence specified.
+ * @param sequence the sequence for which midi file type support
+ * is queried
+ * @return array of file types. If no file types are supported,
+ * returns an array of length 0.
+ */
+ public int[] getMidiFileTypes(Sequence sequence){
+ int typesArray[];
+ Track tracks[] = sequence.getTracks();
+
+ if( tracks.length==1 ) {
+ typesArray = new int[2];
+ typesArray[0] = MIDI_TYPE_0;
+ typesArray[1] = MIDI_TYPE_1;
+ } else {
+ typesArray = new int[1];
+ typesArray[0] = MIDI_TYPE_1;
+ }
+
+ return typesArray;
+ }
+
+ public boolean isFileTypeSupported(int type) {
+ for(int i=0; i<types.length; i++) {
+ if( type == types[i] ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int write(Sequence in, int type, OutputStream out) throws IOException {
+ byte [] buffer = null;
+
+ int bytesRead = 0;
+ long bytesWritten = 0;
+
+ if( !isFileTypeSupported(type,in) ) {
+ throw new IllegalArgumentException("Could not write MIDI file");
+ }
+ // First get the fileStream from this sequence
+ InputStream fileStream = getFileStream(type,in);
+ if (fileStream == null) {
+ throw new IllegalArgumentException("Could not write MIDI file");
+ }
+ buffer = new byte[bufferSize];
+
+ while( (bytesRead = fileStream.read( buffer )) >= 0 ) {
+ out.write( buffer, 0, (int)bytesRead );
+ bytesWritten += bytesRead;
+ }
+ // Done....return bytesWritten
+ return (int) bytesWritten;
+ }
+
+ public int write(Sequence in, int type, File out) throws IOException {
+ FileOutputStream fos = new FileOutputStream(out); // throws IOException
+ int bytesWritten = write( in, type, fos );
+ fos.close();
+ return bytesWritten;
+ }
+
+ //=================================================================================
+
+
+ private InputStream getFileStream(int type, Sequence sequence) throws IOException {
+ Track tracks[] = sequence.getTracks();
+ int bytesBuilt = 0;
+ int headerLength = 14;
+ int length = 0;
+ int timeFormat;
+ float divtype;
+
+ PipedOutputStream hpos = null;
+ DataOutputStream hdos = null;
+ PipedInputStream headerStream = null;
+
+ InputStream trackStreams [] = null;
+ InputStream trackStream = null;
+ InputStream fStream = null;
+
+ // Determine the filetype to write
+ if( type==MIDI_TYPE_0 ) {
+ if (tracks.length != 1) {
+ return null;
+ }
+ } else if( type==MIDI_TYPE_1 ) {
+ if (tracks.length < 1) { // $$jb: 05.31.99: we _can_ write TYPE_1 if tracks.length==1
+ return null;
+ }
+ } else {
+ if(tracks.length==1) {
+ type = MIDI_TYPE_0;
+ } else if(tracks.length>1) {
+ type = MIDI_TYPE_1;
+ } else {
+ return null;
+ }
+ }
+
+ // Now build the file one track at a time
+ // Note that above we made sure that MIDI_TYPE_0 only happens
+ // if tracks.length==1
+
+ trackStreams = new InputStream[tracks.length];
+ int trackCount = 0;
+ for(int i=0; i<tracks.length; i++) {
+ try {
+ trackStreams[trackCount] = writeTrack( tracks[i], type );
+ trackCount++;
+ } catch (InvalidMidiDataException e) {
+ if(Printer.err) Printer.err("Exception in write: " + e.getMessage());
+ }
+ //bytesBuilt += trackStreams[i].getLength();
+ }
+
+ // Now seqence the track streams
+ if( trackCount == 1 ) {
+ trackStream = trackStreams[0];
+ } else if( trackCount > 1 ){
+ trackStream = trackStreams[0];
+ for(int i=1; i<tracks.length; i++) {
+ // fix for 5048381: NullPointerException when saving a MIDI sequence
+ // don't include failed track streams
+ if (trackStreams[i] != null) {
+ trackStream = new SequenceInputStream( trackStream, trackStreams[i]);
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("invalid MIDI data in sequence");
+ }
+
+ // Now build the header...
+ hpos = new PipedOutputStream();
+ hdos = new DataOutputStream(hpos);
+ headerStream = new PipedInputStream(hpos);
+
+ // Write the magic number
+ hdos.writeInt( MThd_MAGIC );
+
+ // Write the header length
+ hdos.writeInt( headerLength - 8 );
+
+ // Write the filetype
+ if(type==MIDI_TYPE_0) {
+ hdos.writeShort( 0 );
+ } else {
+ // MIDI_TYPE_1
+ hdos.writeShort( 1 );
+ }
+
+ // Write the number of tracks
+ hdos.writeShort( (short) trackCount );
+
+ // Determine and write the timing format
+ divtype = sequence.getDivisionType();
+ if( divtype == Sequence.PPQ ) {
+ timeFormat = sequence.getResolution();
+ } else if( divtype == Sequence.SMPTE_24) {
+ timeFormat = (24<<8) * -1;
+ timeFormat += (sequence.getResolution() & 0xFF);
+ } else if( divtype == Sequence.SMPTE_25) {
+ timeFormat = (25<<8) * -1;
+ timeFormat += (sequence.getResolution() & 0xFF);
+ } else if( divtype == Sequence.SMPTE_30DROP) {
+ timeFormat = (29<<8) * -1;
+ timeFormat += (sequence.getResolution() & 0xFF);
+ } else if( divtype == Sequence.SMPTE_30) {
+ timeFormat = (30<<8) * -1;
+ timeFormat += (sequence.getResolution() & 0xFF);
+ } else {
+ // $$jb: 04.08.99: What to really do here?
+ return null;
+ }
+ hdos.writeShort( timeFormat );
+
+ // now construct an InputStream to become the FileStream
+ fStream = new SequenceInputStream(headerStream, trackStream);
+ hdos.close();
+
+ length = bytesBuilt + headerLength;
+ return fStream;
+ }
+
+ /**
+ * Returns ONE_BYTE, TWO_BYTE, SYSEX, META,
+ * ERROR, or IGNORE (i.e. invalid for a MIDI file)
+ */
+ private int getType(int byteValue) {
+ if ((byteValue & 0xF0) == 0xF0) {
+ switch(byteValue) {
+ case 0xF0:
+ case 0xF7:
+ return SYSEX;
+ case 0xFF:
+ return META;
+ }
+ return IGNORE;
+ }
+
+ switch(byteValue & 0xF0) {
+ case 0x80:
+ case 0x90:
+ case 0xA0:
+ case 0xB0:
+ case 0xE0:
+ return TWO_BYTE;
+ case 0xC0:
+ case 0xD0:
+ return ONE_BYTE;
+ }
+ return ERROR;
+ }
+
+ private final static long mask = 0x7F;
+
+ private int writeVarInt(long value) throws IOException {
+ int len = 1;
+ int shift=63; // number of bitwise left-shifts of mask
+ // first screen out leading zeros
+ while ((shift > 0) && ((value & (mask << shift)) == 0)) shift-=7;
+ // then write actual values
+ while (shift > 0) {
+ tddos.writeByte((int) (((value & (mask << shift)) >> shift) | 0x80));
+ shift-=7;
+ len++;
+ }
+ tddos.writeByte((int) (value & mask));
+ return len;
+ }
+
+ private InputStream writeTrack( Track track, int type ) throws IOException, InvalidMidiDataException {
+ int bytesWritten = 0;
+ int lastBytesWritten = 0;
+ int size = track.size();
+ PipedOutputStream thpos = new PipedOutputStream();
+ DataOutputStream thdos = new DataOutputStream(thpos);
+ PipedInputStream thpis = new PipedInputStream(thpos);
+
+ ByteArrayOutputStream tdbos = new ByteArrayOutputStream();
+ tddos = new DataOutputStream(tdbos);
+ ByteArrayInputStream tdbis = null;
+
+ SequenceInputStream fStream = null;
+
+ long currentTick = 0;
+ long deltaTick = 0;
+ long eventTick = 0;
+ int runningStatus = -1;
+
+ // -----------------------------
+ // Write each event in the track
+ // -----------------------------
+ for(int i=0; i<size; i++) {
+ MidiEvent event = track.get(i);
+
+ int status;
+ int eventtype;
+ int metatype;
+ int data1, data2;
+ int length;
+ byte data[] = null;
+ ShortMessage shortMessage = null;
+ MetaMessage metaMessage = null;
+ SysexMessage sysexMessage = null;
+
+ // get the tick
+ // $$jb: this gets easier if we change all system-wide time to delta ticks
+ eventTick = event.getTick();
+ deltaTick = event.getTick() - currentTick;
+ currentTick = event.getTick();
+
+ // get the status byte
+ status = event.getMessage().getStatus();
+ eventtype = getType( status );
+
+ switch( eventtype ) {
+ case ONE_BYTE:
+ shortMessage = (ShortMessage) event.getMessage();
+ data1 = shortMessage.getData1();
+ bytesWritten += writeVarInt( deltaTick );
+
+ if(status!=runningStatus) {
+ runningStatus=status;
+ tddos.writeByte(status); bytesWritten += 1;
+ }
+ tddos.writeByte(data1); bytesWritten += 1;
+ break;
+
+ case TWO_BYTE:
+ shortMessage = (ShortMessage) event.getMessage();
+ data1 = shortMessage.getData1();
+ data2 = shortMessage.getData2();
+
+ bytesWritten += writeVarInt( deltaTick );
+ if(status!=runningStatus) {
+ runningStatus=status;
+ tddos.writeByte(status); bytesWritten += 1;
+ }
+ tddos.writeByte(data1); bytesWritten += 1;
+ tddos.writeByte(data2); bytesWritten += 1;
+ break;
+
+ case SYSEX:
+ sysexMessage = (SysexMessage) event.getMessage();
+ length = sysexMessage.getLength();
+ data = sysexMessage.getMessage();
+ bytesWritten += writeVarInt( deltaTick );
+
+ // $$jb: 04.08.99: always write status for sysex
+ runningStatus=status;
+ tddos.writeByte( data[0] ); bytesWritten += 1;
+
+ // $$jb: 10.18.99: we don't maintain length in
+ // the message data for SysEx (it is not transmitted
+ // over the line), so write the calculated length
+ // minus the status byte
+ bytesWritten += writeVarInt( (data.length-1) );
+
+ // $$jb: 10.18.99: now write the rest of the
+ // message
+ tddos.write(data, 1, (data.length-1));
+ bytesWritten += (data.length-1);
+ break;
+
+ case META:
+ metaMessage = (MetaMessage) event.getMessage();
+ length = metaMessage.getLength();
+ data = metaMessage.getMessage();
+ bytesWritten += writeVarInt( deltaTick );
+
+ // $$jb: 10.18.99: getMessage() returns the
+ // entire valid midi message for a file,
+ // including the status byte and the var-length-int
+ // length value, so we can just write the data
+ // here. note that we must _always_ write the
+ // status byte, regardless of runningStatus.
+ runningStatus=status;
+ tddos.write( data, 0, data.length );
+ bytesWritten += data.length;
+ break;
+
+ case IGNORE:
+ // ignore this event
+ break;
+
+ case ERROR:
+ // ignore this event
+ break;
+
+ default:
+ throw new InvalidMidiDataException("internal file writer error");
+ }
+ }
+ // ---------------------------------
+ // End write each event in the track
+ // ---------------------------------
+
+ // Build Track header now that we know length
+ thdos.writeInt(MTrk_MAGIC);
+ thdos.writeInt(bytesWritten);
+ bytesWritten += 8;
+
+ // Now sequence them
+ tdbis = new ByteArrayInputStream( tdbos.toByteArray() );
+ fStream = new SequenceInputStream(thpis,tdbis);
+ thdos.close();
+ tddos.close();
+
+ return fStream;
+ }
+}