/* * @(#)Handler.java 1.16 02/08/21 * * Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved. */ package com.sun.media.content.audio.midi; import java.io.*; import javax.media.*; import javax.media.protocol. *; import javax.sound.midi.*; import com.sun.media.*; import com.sun.media.protocol.*; import com.sun.media.parser.BasicPullParser; import com.sun.media.parser.BasicTrack; import com.sun.media.controls.GainControlAdapter; public class Handler extends BasicPlayer { private MidiController controller; protected javax.media.protocol.DataSource datasource = null; private boolean closed = false; private int META_EVENT_END_OF_MEDIA = 47; private Control [] controls = null; public Handler() { controller = new MidiController(); manageController(controller); } public void setSource(javax.media.protocol.DataSource source) throws IOException, IncompatibleSourceException { super.setSource(source); controller.setSource(source); // datasource = source; } // TODO: check with ivan protected boolean audioEnabled() { return true; } protected boolean videoEnabled() { return false; } // TODO: check with ivan. Use AudioTimeBase protected TimeBase getMasterTimeBase() { return controller.getMasterTimeBase(); } // TODO: check with ivan public void updateStats() { } // class MidiController extends BasicController implements MidiDevice.Listener { class MidiController extends BasicController implements MetaEventListener { // ControllerEventListener { private MidiParser midiParser; private javax.media.Track track = null; private Buffer buffer = new Buffer(); private PullSourceStream stream; private Sequencer sequencer = null; private Synthesizer synthesizer = null; protected MidiChannel channels[]; private Sequence sequence = null; private byte[] mididata = null; private MidiFileInputStream is = null; private Time duration = Duration.DURATION_UNKNOWN; private GCA gc; protected boolean isConfigurable() { return false; } public void setSource(javax.media.protocol.DataSource source) throws IOException, IncompatibleSourceException { // super.setSource(source); midiParser = new MidiParser(); midiParser.setSource(source); datasource = source; } // TODO: check with ivan protected TimeBase getMasterTimeBase() { return new SystemTimeBase(); } protected boolean doRealize() { if (datasource == null) return false; try { datasource.start(); } catch (IOException e) { return false; } stream = midiParser.getStream(); long contentLength = stream.getContentLength(); long minLocation = 0; long maxLocation; int bufferSize; minLocation = 0; if ( contentLength != SourceStream.LENGTH_UNKNOWN ) { // maxLocation = contentLength - 1; maxLocation = contentLength; bufferSize = (int) contentLength; } else { maxLocation = Long.MAX_VALUE; bufferSize = (int) maxLocation; } int numBuffers = 1; track = new BasicTrack(midiParser, // parser null, // format /*enabled=*/ true, Duration.DURATION_UNKNOWN, // duration new Time(0), // start time numBuffers, bufferSize, stream, minLocation, maxLocation ); return true; } protected boolean doPrefetch() { if (track == null) return false; if (sequencer == null) { try { sequencer = MidiSystem.getSequencer(); // System.out.println("sequencer is " + sequencer); if (sequencer instanceof Synthesizer) { synthesizer = (Synthesizer)sequencer; channels = synthesizer.getChannels(); } } catch (MidiUnavailableException e) { return false; } sequencer.addMetaEventListener( (MetaEventListener) this ); } if (buffer.getLength() == 0) { // Read is done only once. track.readFrame(buffer); if (buffer.isDiscard() || buffer.isEOM()) { buffer.setLength(0); return false; } mididata = (byte[]) buffer.getData(); // System.out.println("buffer length is " + buffer.getLength()); is = new MidiFileInputStream(mididata, buffer.getLength()); } synchronized(this) { if (is != null) { try { is.rewind(); // Exception will never be thrown } catch (Exception e) { } // fs = MidiSystem.getFileStream(is); // if (fs == null) { // return false; // } // TODO: Bug in JavaSound. getDuration from Sequence returns 0 // When it works, you have to set fs to null // call is.rewind and create fs again $$$$$ // if (sequence == null) { // sequence = MidiSystem.getSequence(fs); // System.out.println("sequence is " + sequence); // if (sequence != null) { // long durationNano = sequence.getDuration(); // System.out.println("durationNano is " + durationNano); // duration = new Time(durationNano); // } // } } else { return false; } } try { sequencer.open(); } catch (MidiUnavailableException e) { // Typically if audio device cannot be grabbed // or IOException if not data in the stream Log.error("Cannot open sequencer " + e + "\n"); return false; } catch (Exception e) { Log.error("Cannot open sequencer " + e + "\n"); return false; } try { sequencer.setSequence(new BufferedInputStream(is)); // TODO: avoid using BufferedInputStream long durationNano = sequencer.getMicrosecondLength() * 1000; // System.out.println("durationNano is " + durationNano); duration = new Time(durationNano); // sequencer.setSequence(is); } catch (InvalidMidiDataException e) { Log.error("Invalid Midi Data " + e + "\n"); sequencer.close(); return false; } catch (Exception e) { Log.error("Error setting sequence " + e + "\n"); sequencer.close(); return false; } return true; } protected void abortRealize() { } protected void abortPrefetch() { if ( (sequencer != null) && sequencer.isOpen() ) { sequencer.close(); } } protected void doStart() { if (sequencer == null) return; // sequencer should have been successfully in prefetch // for the control to come here. if (!sequencer.isOpen()) return; sequencer.start(); } protected void doStop() { if (sequencer == null) return; sequencer.stop(); sendEvent( new StopByRequestEvent(this, Started, Prefetched, getTargetState(), getMediaTime())); } protected void doDeallocate() { if (sequencer == null) return; // Do I need to check sequencer.isOpen() before calling close synchronized(this) { try { sequencer.close(); // if (fs != null) { // fs.close(); // fs = null; // } } catch (Exception e) { Log.error("Exception when deallocating: " + e + "\n"); } } } protected void doClose() { if (closed) return; doDeallocate(); // Disconnect the data source if (datasource != null) { datasource.disconnect(); } datasource = null; sequencer.removeMetaEventListener(this); closed = true; super.doClose(); } protected float doSetRate(float factor) { if (sequencer != null) { sequencer.setTempoFactor(factor); return sequencer.getTempoFactor(); } else { return 1.F; } } protected void doSetMediaTime(Time when) { // System.out.println("doSetMediaTime: " + when.getNanoseconds()); if ( (when != null) && (sequencer != null) ) { sequencer.setMicrosecondPosition(when.getNanoseconds()/1000); } } // public void update( MidiDevice.Event event ) { public void meta(MetaMessage me) { // System.out.println("meta: " + me + " : " + // me.getType()); if (me.getType() != META_EVENT_END_OF_MEDIA) return; if ( (sequencer != null) && (sequencer.isOpen()) ) { stopControllerOnly(); sequencer.stop(); // Is this necessary if (duration == Duration.DURATION_UNKNOWN) { duration = getMediaTime(); sendEvent(new DurationUpdateEvent(this, duration)); } // System.out.println("Got Sequencer.EOM"); sendEvent(new EndOfMediaEvent(this, Started, Prefetched, getTargetState(), getMediaTime())); } } // public void controlChange( ShortEvent event ) { // System.out.println(" ShortEvent received: " + event); // } public Time getDuration() { return duration; } public Control [] getControls() { if (controls == null) { controls = new Control[1]; gc = new GCA(); controls[0] = gc; } return controls; } public void gainChange(float g) { if ( (channels == null) || (gc == null) ) return; // No need to check if mute is on or off float level = gc.getLevel(); for (int i = 0; i < channels.length; i++) { channels[i].controlChange(7, (int)(level * 127.0)); } } public void muteChange(boolean muted) { if (channels == null) return; for (int i = 0; i < channels.length; i++) { channels[i].setMute(muted); } } class GCA extends GainControlAdapter { GCA() { super(1.0f); } public void setMute(boolean mute) { super.setMute(mute); muteChange(mute); } public float setLevel(float g) { float level = super.setLevel(g); gainChange(g); return level; } } } // class MidiController class MidiParser extends BasicPullParser { public ContentDescriptor [] getSupportedInputContentDescriptors() { return null; // method not used } // public void setSource(DataSource source) // throws IOException, IncompatibleSourceException { // super.setSource(source); // } public PullSourceStream getStream() { PullSourceStream stream = (PullSourceStream) streams[0]; return stream; } public javax.media.Track[] getTracks() throws IOException, BadHeaderException { // return new javax.media.Track[0]; return null; } public Time setPosition(Time where, int rounding) { return null; } public Time getMediaTime() { return null; } public Time getDuration() { return null; } public String getName() { return "Parser for MIDI file format"; } } // class MidiParser class MidiFileInputStream extends InputStream { private int length; private int index = 0; private byte[] data; private int markpos = 0; MidiFileInputStream(byte[] data, int length) { this.data = data; this.length = length; } public void rewind() { index = 0; markpos = 0; } // public boolean markSupported() { // System.out.println("markSupported: returns true"); // return true; // } // public synchronized void mark(int readlimit) { // System.out.println("mark: readlimit, index " + readlimit + // " : " + index); // markpos = index; // } // public synchronized void reset() throws IOException { // System.out.println("reset: " + markpos); // index = markpos; // } public int read() throws IOException { if (index >= length) return -1; else return (int) data[index++]; } // TODO: synchronize public int available() throws IOException { return (length - index); } public int read(byte b[]) throws IOException { // TODO: check bounds return read(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { // TODO: check bounds if (len > available()) len = available(); if (len == 0) return -1; System.arraycopy(data, index, b, off, len); index += len; return len; } } // class MidiFileInputStream }