/*
* This file is modified by Ivan Maidanski <ivmai@ivmaisoft.com>
* Project name: JCGO-SUNAWT (http://www.ivmaisoft.com/jcgo/)
*/
/*
* @(#)MixerSequencer.java 1.53 03/01/23
*
* Copyright 2003 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.sun.media.sound;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;
import javax.sound.midi.*;
/******************************************************************************************
IMPLEMENTATION TODO:
******************************************************************************************/
/**
* Sequencer using the Headspace Mixer. This Sequencer implementation
* extends MixerSynth, reflecting the current structure in the engine.
* In the future, we want to modify this to allow selection of the
* Receiver....
*
* @version 1.53, 03/01/23
* @author Kara Kytle
*/
class MixerSequencer extends AbstractPlayer implements Sequencer{
private static final int MIDI_TYPE_0 = 0;
private static final int MIDI_TYPE_1 = 1;
private static final int MIDI_TYPE_2 = 2;
/**
* All MixerSequencers share this info object.
*/
static final MixerSequencerInfo info = new MixerSequencerInfo();
private static Sequencer.SyncMode[] masterSyncModes = { Sequencer.SyncMode.INTERNAL_CLOCK };
private static Sequencer.SyncMode[] slaveSyncModes = { Sequencer.SyncMode.NO_SYNC };
private static Sequencer.SyncMode masterSyncMode = Sequencer.SyncMode.INTERNAL_CLOCK;
private static Sequencer.SyncMode slaveSyncMode = Sequencer.SyncMode.NO_SYNC;
/**
* Current time-stamp in microseconds
*/
private long timeStamp = -1;
/**
* Sequence on which this sequencer is operating.
*/
private Sequence sequence = null;
/**
* Local copy of sequence data
*/
private byte midiData[] = null;
/**
* True whenever the song has been started, until the
* song end callback.
*/
private boolean runningInEngine = false;
/**
* Marked to false each time we close the native sequencer object,
* in implClose. it then gets started with GM_BeginSong. Subsequently,
* it gets started with GM_ResumeSong.
*/
private boolean newSequenceStarted = false;
// some of the methods to the engine do not function under certain state
// conditions. we cache the requested values in these variables and
// make the calls when they actually work.
/**
* If setTickPosition gets called while the sequencer is stopped, we
* record the value here and set it when the sequencer is next
* started.
* -1 means not set.
*/
private long tick = -1;
/**
* If setTempoInBPM gets called before playback of a particular sequence
* starts the first time, the value gets overridden by the value in the
* sequence when it starts. We record the value here and set it when
* the sequencer is next started.
* -1 means not set.
*/
private float tempoInBPM = -1;
/**
* Same for setTempoInMPQ...
* -1 means not set.
*/
private float tempoInMPQ = -1;
//$$fb 2002-07-19: fix for 4716740: default sequencer does not set the tempo factor
/**
* cache value for tempo factor until dequence is set
* -1 means not set.
*/
private float tempoFactor = -1;
/**
* True if the sequence is running.
*/
private boolean running = false;
/**
* True if we are recording
*/
private boolean recording = false;
/**
* True if we have finished recording and need to
* reset the sequence
*/
private boolean sequenceChanged = false;
/**
* Used for record timing
*/
private long startTime = 0;
private long startMillisecondTime = 0;
private long lastTempoChangeTime = 0;
private long lastTempoChangeTick = 0;
private long recordTempoInMPQ = 500000; // 120 bpm in mpq
private long startTick = 0;
private float divisionType = 0.0f;
private int resolution = 0;
/**
* Receiver instance for this sequencer
*/
private SequencerReceiver sequencerReceiver = null;
/**
* Vector of tracks to which we're recording
*/
private Vector recordingTracks = new Vector();
/**
* Meta event listeners
*/
private Vector metaEventListeners = new Vector();
/**
* Control change listeners
*/
private Vector controllerEventListeners = new Vector();
// CONSTRUCTOR
protected MixerSequencer() throws MidiUnavailableException {
super(info);
if(Printer.trace) Printer.trace(">> MixerSequencer CONSTRUCTOR");
if(Printer.trace) Printer.trace("<< MixerSequencer CONSTRUCTOR completed");
}
// SEQUENCER METHODS
public synchronized void setSequence(Sequence sequence) throws InvalidMidiDataException {
if(Printer.trace) Printer.trace(">> MixerSequencer: setSequence(" + sequence +")");
if ( !isOpen() ) {
throw new IllegalStateException("Cannot set sequence until sequencer has been opened");
}
// check for file type support
int midiFileTypes[] = MidiSystem.getMidiFileTypes( sequence );
if( midiFileTypes.length == 0 ) {
throw new InvalidMidiDataException("Unsupported sequence: " + sequence);
}
// write the file data
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
MidiSystem.write(sequence, midiFileTypes[0], baos);
baos.close();
} catch( IOException e ) {
throw new InvalidMidiDataException("Unable to get file stream from sequence: " + sequence);
}
byte[] fileByteArray = baos.toByteArray();
ByteArrayInputStream fileStream = new ByteArrayInputStream(fileByteArray);
// set and store the sequence
try {
setSequence(fileStream);
this.sequence = sequence;
} catch(IOException e) {
throw new InvalidMidiDataException("Failed to load sequence: " + sequence);
}
if(Printer.trace) Printer.trace("<< MixerSequencer: setSequence(" + sequence +") completed");
}
public synchronized void setSequence(InputStream stream) throws IOException, InvalidMidiDataException {
if (stream == null)
throw new NullPointerException("Stream is null");
if(Printer.trace) Printer.trace(">> MixerSequencer: setSequence(" + stream +")");
// first make sure the mixer is loaded.
if ( !isOpen() ) {
throw new IllegalStateException("Cannot set sequence until sequencer has been opened");
}
// now make sure that stream is a supported file type.
// if not, and if a sequence is currently playing, we
// can avoid interrupting it.
MidiFileFormat fileFormat = MidiSystem.getMidiFileFormat(stream); // can throw IOException, InvalidMidiDataException
int type = fileFormat.getType();
// we don't support MIDI type 2 files
if (type == MIDI_TYPE_2) {
throw new InvalidMidiDataException("Unsupported file type: " + type + ". Only type 0 and type 1 MIDI files are supported.");
}
// okay, we support it. if we're currently playing,
// stop and destroy the current native sequencer object
if (id != 0) {
stop();
// this is a bit excessive!! it will close and reopen the external synth
// if it's being used.
implClose();
try { implOpen(); } catch (MidiUnavailableException mue) { }
}
// get the midi data into a byte array
midiData = getBytesFromFileStream(stream,fileFormat);
if ( (midiData == null) || (midiData.length == 0) ) {
throw new IOException("Failed to read data from stream.");
}
// create a MIDI sequencer if it's a MIDI type 0 or type 1 file
if ( (type == MIDI_TYPE_0) || (type == MIDI_TYPE_1) ) {
id = nOpenMidiSequencer(midiData, midiData.length);
// if this didn't work, try the RMF sequencer
if( id == 0 ) {
id = nOpenRmfSequencer(midiData,midiData.length);
}
}
// failure!
if (id == 0) {
throw new InvalidMidiDataException("Failed to load sequence");
}
int i;
// update the channels with the new id
for (i = 0; i < channels.length; i++) {
channels[i].setId(id);
}
// connect to the internal synth (with the new id value)
connectToInternalSynth();
// $$fb set tempo factor, if it is cached
if (tempoFactor != -1) {
setTempoFactor(tempoFactor);
}
// if we have any MIDI OUT receivers that are AbstractMidiDevices,
// add them now!
// $$kk: 09.27.99 need to implement additional receivers here
/*
Receiver[] currentOutReceivers = getOutReceivers();
for (i = 0; i < currentOutReceivers.length; i++) {
if (currentOutReceivers[i] instanceof AbstractMidiDevice) {
addOutReceiver(currentOutReceivers[i]);
}
}
*/
// $$jb: if we have any ControllerEventListeners, add them now
// because they were probably added before we had a native
// sequencer object to add the listeners to.
for (i = 0; i < controllerEventListeners.size(); i++) {
ControllerVectorElement cve = (ControllerVectorElement)controllerEventListeners.elementAt(i);
for (int z = 0; z < cve.controllers.length; z++) {
nAddControllerEventCallback(id, cve.controllers[z]);
}
}
// $$jb: 06.02.99: do we want an event for sequence loaded?
if(Printer.trace) Printer.trace("<< MixerSequencer: setSequence(" + stream +") completed");
}
public Sequence getSequence() {
// $$kk: 04.14.99: right now this may be null if the sequencer
// was opened with a FileStream; should decide how to handle
// this!!
// $$jb: 06.02.99: we can get it from MidiSystem.getSequence.
// this call is expensive, so it makes sense to call it here,
// rather than building the local sequence object every time
// a new sequence is set.
if( (sequence == null) && (midiData != null) && (midiData.length > 0) ) {
ByteArrayInputStream bais = new ByteArrayInputStream( midiData );
try {
sequence = MidiSystem.getSequence(bais);
} catch (InvalidMidiDataException imde) {
} catch (IOException ioe) {
}
}
// if we failed to get the sequence, this will be null
return sequence;
}
public synchronized void start() {
if(Printer.trace) Printer.trace(">> MixerSequencer: start()");
// sequencer not open: throw an exception
if ( !isOpen() ) {
throw new IllegalStateException("Sequencer not open");
}
// sequence not set: fail quietly
if (id == 0) {
return;
}
// already running: return quietly
if (running == true) {
return;
}
// $$jb:11.03.99: If we have recorded, we need to re-set the
// sequence before we play again.
if (sequenceChanged == true) {
try {
setSequence( sequence );
} catch(InvalidMidiDataException e) {
// should never happen
}
sequenceChanged = false;
}
// mark the sequencer running
running = true;
if (!newSequenceStarted) {
nStartSequencer(id);
newSequenceStarted = true;
// set our position to the currently requested tick position.
if (tick != -1) {
setTickPosition(tick);
}
// set our tempo to the currently requested value
// (we only need to do this the first time the sequence is started.)
if (tempoInBPM != -1) {
setTempoInBPM(tempoInBPM);
}
if (tempoInMPQ != -1) {
setTempoInMPQ(tempoInMPQ);
}
} else {
if (tick != -1) {
// if a tick position is set, set our tick to that position.
// this will cause playback to resume at this position.
setTickPosition(tick);
} else {
// if the song has ended, reset the position to the beginning.
if (!runningInEngine) {
setMicrosecondPosition(0);
}
}
// otherwise just resume.
nResumeSequencer(id);
}
runningInEngine = true;
// this isn't a real callback!
callbackSongStart();
if(Printer.trace) Printer.trace("<< MixerSequencer: start() completed");
}
public synchronized void stop() {
if(Printer.trace) Printer.trace(">> MixerSequencer: stop()");
if ( !isOpen() ) {
throw new IllegalStateException("Sequencer not open");
}
stopRecording();
if (id == 0) {
// sequence not set; fail quietly
return;
}
// not running; just return
if (running == false) {
return;
}
// stop playback
implStop();
// this isn't a real callback!
callbackSongStop();
if(Printer.trace) Printer.trace("<< MixerSequencer: stop() completed");
}
public boolean isRunning() {
return running;
}
public void startRecording() {
if ( !isOpen() ) {
throw new IllegalStateException("Sequencer not open");
}
recording = true;
divisionType = sequence.getDivisionType();
resolution = sequence.getResolution();
startTime = System.currentTimeMillis();
startMillisecondTime = getMicrosecondPosition() / 1000;
startTick = 0;
lastTempoChangeTime = 0;
lastTempoChangeTick = startTick;
recordTempoInMPQ = (long) getTempoInMPQ();
start();
startMillisecondTime = getMicrosecondPosition() / 1000;
}
public void stopRecording() {
if ( !isOpen() ) {
throw new IllegalStateException("Sequencer not open");
}
// if we were recording, we need to re-set the sequence next time we play
if( recording == true ) {
sequenceChanged = true;
}
recording = false;
}
public boolean isRecording() {
return recording;
}
public void recordEnable(Track track, int channel) {
if ( ! findTrack(track) ) {
throw new IllegalArgumentException("Track does not exist in the current sequence");
}
synchronized(recordingTracks) {
RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
if (rc != null) {
rc.channel = channel;
} else {
recordingTracks.addElement(new RecordingTrack(track, channel));
}
}
}
public void recordDisable(Track track) {
synchronized(recordingTracks) {
RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
if (rc != null) {
recordingTracks.removeElement(rc);
}
}
}
private boolean findTrack(Track track) {
boolean found = false;
if (sequence != null) {
Track[] tracks = sequence.getTracks();
for (int i = 0; i < tracks.length; i++) {
if (track == tracks[i]) {
found = true;
break;
}
}
}
return found;
}
public float getTempoInBPM() {
if(Printer.trace) Printer.trace(">> MixerSequencer: getTempoInBPM() ");
//$$fb 2002-07-19: fix for 4716740: default sequencer does not set the tempo factor
if ((id == 0) || !newSequenceStarted) {
// if the sequence has not been started and a tempo
// value has been set, return that value.
if (tempoInBPM != -1) {
return tempoInBPM;
}
else if (tempoInMPQ != -1) {
return 60000000 / tempoInMPQ;
}
// if the sequence is not set, return 0.
if (id == 0) {
return 0;
}
}
return (float)nGetTempoInBPM(id);
}
public void setTempoInBPM(float bpm) {
if(Printer.trace) Printer.trace(">> MixerSequencer: setTempoInBPM() ");
//$$fb 2002-07-19: fix for 4716740: default sequencer does not set the tempo factor
if ((id == 0) || !newSequenceStarted) {
// if this sequence has not been started, this tempo value will be
// overwritten by the sequence's tempo when it does start. so we
// cache the value.
tempoInBPM = bpm;
// if previously tempoInMPQ was cached, reset it
tempoInMPQ = -1;
// it will be set when the sequence is started
return;
}
// set the tempo in BPM
nSetTempoInBPM(id, (int)bpm);
// reset the tempoInBPM and tempoInMPQ values so we won't use them again
this.tempoInBPM = -1;
this.tempoInMPQ = -1;
}
public float getTempoInMPQ() {
if(Printer.trace) Printer.trace(">> MixerSequencer: getTempoInMPQ() ");
//$$fb 2002-07-19: fix for 4716740: default sequencer does not set the tempo factor
if ((id == 0) || !newSequenceStarted) {
// if the sequence has not been started and a tempo
// value has been set, return that value.
if (tempoInMPQ != -1) {
return tempoInMPQ;
}
else if (tempoInBPM != -1) {
return 60000000 / tempoInBPM;
}
// if the sequence is not set, return 0.
if (id == 0) {
return 0;
}
}
return (float)nGetTempoInMPQ(id);
}
public void setTempoInMPQ(float mpq) {
if(Printer.trace) Printer.trace(">> MixerSequencer: setTempoInMPQ() ");
//$$fb 2002-07-19: fix for 4716740: default sequencer does not set the tempo factor
if ((id == 0) || !newSequenceStarted) {
// if this sequence has not been started, this tempo value will be
// overwritten by the sequence's tempo when it does start. so we
// cache the value.
tempoInMPQ = mpq;
// if previously tempoInBPM was cached, reset it
tempoInBPM = -1;
// it will be set when the sequence is started
return;
}
// set the tempo in MPQ
nSetTempoInMPQ(id, (int)mpq);
// reset the tempoInBPM and tempoInMPQ values so we won't use them again
tempoInBPM = -1;
tempoInMPQ = -1;
}
public void setTempoFactor(float factor) {
if(Printer.trace) Printer.trace(">> MixerSequencer: setTempoFactor() ");
if (id == 0) {
//$$fb 2002-07-19: fix for 4716740: default sequencer does not set the tempo factor
if (factor > 0) {
tempoFactor = factor;
}
return;
}
nSetMasterTempo(id, factor);
// don't need cache anymore
tempoFactor = -1;
}
public float getTempoFactor() {
if(Printer.trace) Printer.trace(">> MixerSequencer: getTempoFactor() ");
if (id == 0) {
//$$fb 2002-07-19: fix for 4716740: default sequencer does not set the tempo factor
if (tempoFactor != -1) {
return tempoFactor;
}
return 0;
}
return nGetMasterTempo(id);
}
public long getTickLength() {
if(Printer.trace) Printer.trace(">> MixerSequencer: getTickLength() ");
if (id == 0) {
return 0;
}
//$$fb 2002-10-30: fix for 4427890: Sequencer.getTickLength() and Sequence.getTickLength() report the wrong length
// see below (getTickPosition) for reason
return nGetSequenceTickLength(id)/64;
}
public synchronized long getTickPosition() {
if(Printer.trace) Printer.trace(">> MixerSequencer: getTickPosition() ");
if (tick != -1)
return tick;
if (id == 0) {
return 0;
}
// $$jb: 11.09.99: The engine is keeping track of ticks
// at 64x the normal tick speed. This is probably better
// fixed in the engine code, but I'm patching it here for
// the time being. (bug #4288671)
return (nGetSequencerTickPosition(id)/64);
}
public synchronized void setTickPosition(long tick) {
if(Printer.trace) Printer.trace(">> MixerSequencer: setTickPosition() ");
if (id == 0) {
if (isOpen())
this.tick = tick;
return;
}
// $$kk: 06.08.99: record the requested tick position if not currently running.
if (running == false) {
// Cache tick value (normal tick speed)
this.tick = tick;
} else {
// $$kk: 06.08.99: note that this will *always* call GM_ResumeSong
// at the chosen tick position, even if the song was paused! i think
// maybe this should change. in the meantime, though, i am just going
// to handle this in this class by not calling this method unless we
// are not paused.
// $$jb: 11.09.99: The engine is keeping track of ticks
// at 64x the normal tick speed. (bug #4288671)
nSetSequencerTickPosition(id, tick*64);
// reset the tick position so we won't use it again
this.tick = -1;
}
}
public long getMicrosecondLength() {
if(Printer.trace) Printer.trace(">> MixerSequencer: getMicrosecondLength() ");
if (id == 0) {
return 0;
}
return nGetSequenceMicrosecondLength(id);
}
public long getMicrosecondPosition() {
if(Printer.trace) Printer.trace(">> MixerSequencer: getMicrosecondPosition() ");
if (id == 0) {
return 0;
}
return (nGetSequencerMicrosecondPosition(id));
}
public void setMicrosecondPosition(long microseconds) {
if(Printer.trace) Printer.trace(">> MixerSequencer: setMicrosecondPosition() ");
if (id == 0) {
return;
}
// $$kk: 07.19.99: each time we call GM_SetSongMicrosecondPosition (called from
// nSetSequencerMicrosecondPosition), it calls PV_ComfigureMusic,
// which parses the midi file data and resets the tempo to what's specified there.
// i think that is the appropriate behaviour since tempo changes can occur at any
// time in a midi sequence; here we're just getting the initial one....
nSetSequencerMicrosecondPosition(id, microseconds);
// $$kk: 06.08.99
// reset the tick position so we won't use it again
// note that GM_SetSongTickPosition will resume a song regardless of
// whether it was originally paused, but GM_SetSongMicrosecondPosition
// only resumes it if it was not playing before. a lot of this ugly
// code will go away if i change GM_SetSongTickPosition to handle the
// paused case....
this.tick = -1;
}
public void setMasterSyncMode(Sequencer.SyncMode sync) {
// $$kk: 04.07.99: we do not support this now.
}
public Sequencer.SyncMode getMasterSyncMode() {
return masterSyncMode;
}
public Sequencer.SyncMode[] getMasterSyncModes() {
Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[masterSyncModes.length];
System.arraycopy(masterSyncModes, 0, returnedModes, 0, masterSyncModes.length);
return returnedModes;
}
public void setSlaveSyncMode(Sequencer.SyncMode sync) {
// $$kk: 04.07.99: we do not support this now.
}
public Sequencer.SyncMode getSlaveSyncMode() {
return slaveSyncMode;
}
public Sequencer.SyncMode[] getSlaveSyncModes() {
Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[slaveSyncModes.length];
System.arraycopy(slaveSyncModes, 0, returnedModes, 0, slaveSyncModes.length);
return returnedModes;
}
protected int getTrackCount() {
Sequence seq = getSequence();
if (seq != null) {
// $$fb wish there was a nicer way to get the number of tracks...
return sequence.getTracks().length;
}
return 0;
}
public void setTrackMute(int track, boolean mute) {
// fix for 4713900: default Sequencer allows to set Mute for invalid track
if (id == 0 || track < 0 || track >= getTrackCount()) return;
nSetTrackMute(id, track, mute);
}
public boolean getTrackMute(int track) {
// fix for 4713900: default Sequencer allows to set Mute for invalid track
if (id == 0 || track < 0 || track >= getTrackCount()) return false;
return nGetTrackMute(id, track);
}
public void setTrackSolo(int track, boolean solo) {
// fix for 4713900: default Sequencer allows to set Mute for invalid track
if (id == 0 || track < 0 || track >= getTrackCount()) return;
nSetTrackSolo(id, track, solo);
}
public boolean getTrackSolo(int track) {
// fix for 4713900: default Sequencer allows to set Mute for invalid track
if (id == 0 || track < 0 || track >= getTrackCount()) return false;
return nGetTrackSolo(id, track);
}
public boolean addMetaEventListener(MetaEventListener listener) {
synchronized( metaEventListeners ) {
if (! metaEventListeners.contains(listener)) {
metaEventListeners.addElement(listener);
}
return true;
}
}
public void removeMetaEventListener(MetaEventListener listener) {
synchronized( metaEventListeners ) {
metaEventListeners.removeElement(listener);
}
}
public int[] addControllerEventListener(ControllerEventListener listener, int[] controllers) {
synchronized( controllerEventListeners ) {
// first find the listener. if we have one, add the controllers
// if not, create a new element for it.
ControllerVectorElement cve = null;
boolean flag = false;
for( int i=0; i < controllerEventListeners.size(); i++ ) {
cve = (ControllerVectorElement) controllerEventListeners.elementAt(i);
if( cve.listener.equals( listener ) ) {
cve.addControllers( controllers );
flag = true;
break;
}
}
if( !flag ) {
cve = new ControllerVectorElement( listener, controllers );
controllerEventListeners.addElement( cve );
}
// now make sure the engine knows to notify us on these controllers
// this won't get called if the sequencer hasn't been opened yet,
// but if we're adding a listener while the song is playing, it will.
if( id != 0 ) {
for(int i=0; i<controllers.length; i++) {
nAddControllerEventCallback( id, controllers[i] );
}
}
// and return all the controllers this listener is interested in
return cve.getControllers();
}
}
public int[] removeControllerEventListener(ControllerEventListener listener, int[] controllers) {
synchronized(controllerEventListeners) {
ControllerVectorElement cve = null;
boolean flag = false;
for( int i=0; i < controllerEventListeners.size(); i++ ) {
cve = (ControllerVectorElement) controllerEventListeners.elementAt(i);
if( cve.listener.equals( listener ) ) {
cve.removeControllers( controllers );
flag = true;
break;
}
}
if( !flag ) {
return new int[0];
}
if( controllers == null ) {
controllerEventListeners.removeElement( cve );
return new int[0];
}
return cve.getControllers();
}
}
// RECEIVER METHODS
/**
* Return the time-stamp in microseconds
* MixerSynth always returns -1.
*/
public long getTimeStamp() {
return getMicrosecondPosition();
}
/**
* Set the programmed tick.
* MixerSynth ignores this.
*/
public void setTimeStamp(long timeStamp) {
//setMicrosecondPosition(timeStamp);
}
// OVERRIDES OF ABSTRACT PLAYER METHODS
/**
* return ourselves, since we are a Receiver
*/
public synchronized Receiver getReceiver() throws MidiUnavailableException {
if( sequencerReceiver == null ) {
sequencerReceiver = new SequencerReceiver();
}
return sequencerReceiver;
}
/**
* wait for the callbackSongDone callback
*/
public synchronized void implClose() {
if(Printer.trace) Printer.trace(">> MixerSequencer: implClose() ");
implStop();
super.implClose();
//programmedTick = 0;
sequence = null;
running = false;
newSequenceStarted = false;
while (runningInEngine) {
synchronized(this) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
if(Printer.trace) Printer.trace("<< MixerSequencer: implClose() completed");
}
// HELPER METHODS
/*
* we actually do very little here 'cause we
* create the native stuff in setSequence.
*/
public void implOpen() throws MidiUnavailableException {
openInternalSynth();
}
protected void implStop() {
if(Printer.trace) Printer.trace(">> MixerSequencer: implStop()");
nPauseSequencer(id);
running = false;
//recording = false;
}
/**
* Send midi player events.
*/
protected void sendMetaEvents(MetaMessage message) {
// $$kk: 04.22.99: i'm using the same method we use
// for sampled audio channel events 'cause there's no relevant
// typing....
if (Printer.debug) Printer.debug("sending a meta event");
eventDispatcher.sendAudioEvents(message, metaEventListeners);
}
/**
* Send midi player events.
*/
protected void sendControllerEvents(ShortMessage message,int controller) {
// $$kk: 04.22.99: i'm using the same method we use
// for sampled audio channel events 'cause there's no relevant
// typing....
if (Printer.debug) Printer.debug("sending a controller event");
Vector sendToListeners = new Vector();
for( int i=0; i<controllerEventListeners.size(); i++ ) {
ControllerVectorElement cve = (ControllerVectorElement) controllerEventListeners.elementAt(i);
for(int j=0; j<cve.controllers.length; j++) {
if( cve.controllers[j] == controller ) {
sendToListeners.addElement( cve.listener );
break;
}
}
}
eventDispatcher.sendAudioEvents(message, sendToListeners);
}
/**
* Calculate the current tick position for recording
*/
private long calculateTickPosition( long elapsedTime ) {
if(divisionType == Sequence.PPQ ) {
long tick;
long ppqelapsed = (elapsedTime - lastTempoChangeTime);
// $$jb: 11.03.99: We need to *really* keep track of tempo changes
// for PPQ.
if(ppqelapsed >= 0) {
tick = lastTempoChangeTick + (ppqelapsed * resolution * 1000 / recordTempoInMPQ);
} else {
tick = (elapsedTime * resolution * 1000 / recordTempoInMPQ);
}
return tick;
} else {
// SMPTE is easy
return (long)( elapsedTime * divisionType * resolution / 1000 );
}
}
/**
* Return the entire contents of the FileStream as a byte array.
*/
protected byte[] getBytesFromFileStream(InputStream stream, MidiFileFormat format) throws IOException {
// if null, throw an IOException
if (stream == null) {
throw new IOException("Stream is null");
}
// load data from the stream into a byte array
byte[] data;
long length = format.getByteLength();
// if we know the length, just read into an array of that size.
if (length != MidiFileFormat.UNKNOWN_LENGTH) {
data = new byte[(int)length];
if (stream.read(data) != length) {
throw new IOException("Read failure: expected " + length + " bytes");
}
} else {
// otherwise, read until we hit the end of the stream
int MAX_READ_LIMIT = 2048;
ByteArrayOutputStream ba = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(ba);
int readCount = MAX_READ_LIMIT;
byte tmp[] = new byte[readCount];
int num = 0;
int readByte = 0;
while (true) {
readByte = stream.read(tmp, 0, readCount); // can throw IOException
if (readByte == -1)
if (num == 0) {
throw new IOException("No data found in stream");
} else {
break;
}
dos.write(tmp, 0, readByte);
num += readByte;
Thread.currentThread().yield();
} // while
data = ba.toByteArray();
}
return data;
}
// CALLBACKS
// called by the engine when the song ends.
private synchronized void callbackSongEnd() {
if(Printer.trace) Printer.trace(">> MixerSequencer: callbackSongEnd()");
// this will set running to false
implStop();
// the engine no longer thinks this song is playing
runningInEngine = false;
// $$kk: 07.19.99: this will now get set in implStop()
//paused = true;
// this will knock us out of the wait() in implClose()
synchronized(this) {
notifyAll();
}
// $$kk: 04.22.99: we should have something equivalent to this for midi!
// setActive(false);
// send events
// $$kk: 04.22.99: need to make the tick position work here.
// however, right now we have state management problems (sequencer may be closed)
// and we don't (necessarily) actually have a real Sequence object....
// $$jb: 06.04.99: no longer sending midi events
// sendEvents(new MidiDeviceEvent(this, Sequencer.EOM, (long)0));
MetaMessage message = new MetaMessage();
try{
message.setMessage(47, new byte[0], 0);
if (Printer.debug) Printer.debug("new meta message: " + message);
} catch(InvalidMidiDataException e1) {
if (Printer.debug) Printer.debug("invalid midi data: " + e1);
}
sendMetaEvents(message);
if(Printer.trace) Printer.trace("<< MixerSequencer: callbackSongEnd completed()");
}
// called by us when we start or resume the sequence.
// there's no real callback for this
private void callbackSongStart() {
if(Printer.trace) Printer.trace("MixerSequencer: callbackSongStart()");
}
// called by us when we pause the sequence.
// there's no real callback for this
private void callbackSongStop() {
if(Printer.trace) Printer.trace("MixerSequencer: callbackSongStop()");
}
private void callbackMetaEvent( int type, int msgLength, int channel, byte data[]) {
if (Printer.trace) Printer.trace(">> MixerSequencer: callbackMetaEvent()");
if (Printer.debug) Printer.debug(" type = " + type +
", msgLength = " + msgLength + ", channel = " + channel );
MetaMessage newMessage = new MetaMessage();
try {
newMessage.setMessage(type, data, msgLength);
if (Printer.debug) Printer.debug("new meta message: " + newMessage);
sendMetaEvents(newMessage);
} catch (InvalidMidiDataException e1) {
if (Printer.debug) Printer.debug("invalid midi data: " + e1);
}
if(Printer.trace) Printer.trace("<< MixerSequencer: callbackMetaEvent() completed");
}
private void callbackControllerEvent( int channel, int track, int controller, int value ) {
if(Printer.trace) Printer.trace(">> MixerSequencer: callbackControllerEvent()");
ShortMessage newMessage = new ShortMessage();
try {
// 176 = 0xB0 -- the status byte for controller and mode
// events is 0xBc, where c is the channel the event occurs on
newMessage.setMessage( (176+channel), controller, value);
sendControllerEvents(newMessage, controller);
} catch(InvalidMidiDataException e1) {
if(Printer.debug)Printer.debug("invalid midi data: " + e1);
}
if(Printer.trace) Printer.trace("<< MixerSequencer: callbackControllerEvent() completed");
}
// INNER CLASSES
class SequencerReceiver extends PlayerReceiver {
public void send(MidiMessage message, long timeStamp) {
// $$jb: 11.03.99: do we really want to do this here?
super.send(message, timeStamp);
// RECORD!!
if (recording) {
long tickPos;
Vector v = null;
// convert timeStamp to ticks
if( timeStamp < 0 ) {
if( running ) {
tickPos = calculateTickPosition( getMicrosecondPosition()/1000 );
} else {
tickPos = calculateTickPosition( startMillisecondTime + (System.currentTimeMillis() - startTime) );
}
} else {
tickPos = calculateTickPosition( timeStamp );
}
MidiEvent me = new MidiEvent(message, tickPos);
if( message instanceof ShortMessage ) {
v = RecordingTrack.get( recordingTracks, ((ShortMessage)message).getChannel() );
} else {
// $$jb: where to record meta, sysex events?
v = RecordingTrack.get( recordingTracks, 0 );
}
for(int i=0; i<v.size(); i++) {
((Track)v.elementAt(i)).add( me );
}
}
}
}
private static class MixerSequencerInfo extends MidiDevice.Info {
private static final String name = "Java Sound Sequencer";
private static final String vendor = "Sun Microsystems";
private static final String description = "Software sequencer / synthesizer module";
private static final String version = "Version 1.0";
private MixerSequencerInfo() {
//super(name, vendor, description, version, MixerSequencer.class);
super(name, vendor, description, version);
}
} // class Info
private class ControllerVectorElement {
// $$jb: using an array for controllers b/c its
// easier to deal with than turning all the
// ints into objects to use a Vector
int [] controllers;
ControllerEventListener listener;
private ControllerVectorElement( ControllerEventListener listener, int[] controllers ) {
this.listener = listener;
this.controllers = controllers;
}
private void addControllers( int[] c ) {
if( c==null) {
return;
}
int temp[] = new int[ controllers.length + c.length ];
int elements;
// first add what we have
for( int i=0; i<controllers.length; i++) {
temp[i] = controllers[i];
}
elements = controllers.length;
// now add the new controllers only if we don't already have them
for( int i=0; i<c.length; i++ ) {
boolean flag = false;
for( int j=0; j<controllers.length; j++ ) {
if( c[i] == controllers[j] ) {
flag = true;
break;
}
}
if( !flag ) {
temp[elements++] = c[i];
}
}
// now keep only the elements we need
int newc[] = new int[ elements ];
for( int i=0; i<elements; i++ ){
newc[i] = temp[i];
}
controllers = newc;
}
private void removeControllers( int[] c ) {
if( c==null ) {
controllers = new int[0];
} else {
int temp[] = new int[ controllers.length ];
int elements = 0;
for(int i=0; i<controllers.length; i++){
boolean flag = false;
for(int j=0; j<c.length; j++) {
if( controllers[i] == c[j] ) {
flag = true;
break;
}
}
if( !flag ){
temp[elements++] = controllers[i];
}
}
// now keep only the elements remaining
int newc[] = new int[ elements ];
for( int i=0; i<elements; i++ ) {
newc[i] = temp[i];
}
controllers = newc;
}
}
private int[] getControllers() {
// return a copy of our array of controllers,
// so others can't mess with it
int c[] = new int[controllers.length];
for(int i=0; i<controllers.length; i++){
c[i] = controllers[i];
}
return c;
}
} // class ControllerVectorElement
static class RecordingTrack {
Track track;
int channel;
RecordingTrack(Track track, int channel) {
this.track = track;
this.channel = channel;
}
static RecordingTrack get(Vector vector, Track track) {
synchronized(vector) {
int size = vector.size();
RecordingTrack current;
for (int i = 0; i < size; i++ ) {
current = (RecordingTrack)vector.elementAt(i);
if (current.track == track) {
return current;
}
}
}
return null;
}
static Vector get(Vector vector, int channel) {
Vector newV = new Vector();
synchronized(vector) {
int size = vector.size();
RecordingTrack current;
for (int i = 0; i < size; i++ ) {
current = (RecordingTrack)vector.elementAt(i);
if( (current.channel == channel) || (current.channel == -1) ) {
newV.addElement( current.track );
}
}
}
return newV;
}
}
// NATIVE METHODS
// GM_LoadSong
protected native long nOpenMidiSequencer(byte[] midiData, int length);
// GM_LoadSong
protected native long nOpenRmfSequencer(byte[] rmfData, int length);
// GM_BeginSong
protected native void nStartSequencer(long id);
// GM_PauseSong
protected native void nPauseSequencer(long id);
// GM_ResumeSong
protected native void nResumeSequencer(long id);
// GM_AddControllerEventCallback
protected native void nAddControllerEventCallback(long id, int controller);
// GM_SongTicks
protected native long nGetSequencerTickPosition(long id);
// GM_SetSongTickPosition
protected native long nSetSequencerTickPosition(long id, long tick);
// GM_SongMicroseconds
protected native long nGetSequencerMicrosecondPosition(long id);
// GM_SetSongMicrosecondPosition
protected native long nSetSequencerMicrosecondPosition(long id, long microseconds);
// GM_GetSongTempoInBeatsPerMinute
protected native int nGetTempoInBPM(long id);
// GM_SetSongTempoInBeatsPerMinute
protected native int nSetTempoInBPM(long id, int bpm);
// GM_GetSongTempo
protected native int nGetTempoInMPQ(long id);
// GM_SetSongTempo
protected native int nSetTempoInMPQ(long id, int mpq);
// GM_GetMasterSongTempo
protected native float nGetMasterTempo(long id);
// GM_SetMasterSongTempo
protected native float nSetMasterTempo(long id, float factor);
// GM_MuteTrack or GM_UnmuteTrack
protected native void nSetTrackMute(long id, int track, boolean mute);
// GM_GetTrackMuteStatus
protected native boolean nGetTrackMute(long id, int track);
// GM_SoloTrack or GM_UnsoloTrack
protected native void nSetTrackSolo(long id, int track, boolean mute);
// GM_GetTrackSoloStatus
protected native boolean nGetTrackSolo(long id, int track);
// WE'RE CHEATING WITH THESE:
// GM_GetSongTickLength
protected native long nGetSequenceTickLength(long id);
// GM_GetSongMicrosecondLength
protected native long nGetSequenceMicrosecondLength(long id);
}