mirror of
https://github.com/catfoolyou/Project164.git
synced 2025-06-05 17:30:57 -05:00
1604 lines
49 KiB
Java
1604 lines
49 KiB
Java
package paulscode.sound;
|
|
|
|
import java.io.IOException;
|
|
import java.net.URL;
|
|
import java.util.LinkedList;
|
|
import java.util.ListIterator;
|
|
|
|
import javax.sound.midi.InvalidMidiDataException;
|
|
import javax.sound.midi.MetaEventListener;
|
|
import javax.sound.midi.MetaMessage;
|
|
import javax.sound.midi.MidiDevice;
|
|
import javax.sound.midi.MidiSystem;
|
|
import javax.sound.midi.MidiUnavailableException;
|
|
import javax.sound.midi.Receiver;
|
|
import javax.sound.midi.Sequence;
|
|
import javax.sound.midi.Sequencer;
|
|
import javax.sound.midi.ShortMessage;
|
|
import javax.sound.midi.Synthesizer;
|
|
|
|
import net.lax1dude.eaglercraft.EaglerAdapter;
|
|
|
|
/**
|
|
* The MidiChannel class provides an interface for playing MIDI files, using
|
|
* the JavaSound API. For more information about the JavaSound API, visit
|
|
* http://java.sun.com/products/java-media/sound/
|
|
*<br><br>
|
|
*<b><i> SoundSystem License:</b></i><br><b><br>
|
|
* You are free to use this library for any purpose, commercial or otherwise.
|
|
* You may modify this library or source code, and distribute it any way you
|
|
* like, provided the following conditions are met:
|
|
*<br>
|
|
* 1) You may not falsely claim to be the author of this library or any
|
|
* unmodified portion of it.
|
|
*<br>
|
|
* 2) You may not copyright this library or a modified version of it and then
|
|
* sue me for copyright infringement.
|
|
*<br>
|
|
* 3) If you modify the source code, you must clearly document the changes
|
|
* made before redistributing the modified source code, so other users know
|
|
* it is not the original code.
|
|
*<br>
|
|
* 4) You are not required to give me credit for this library in any derived
|
|
* work, but if you do, you must also mention my website:
|
|
* http://www.paulscode.com
|
|
*<br>
|
|
* 5) I the author will not be responsible for any damages (physical,
|
|
* financial, or otherwise) caused by the use if this library or any part
|
|
* of it.
|
|
*<br>
|
|
* 6) I the author do not guarantee, warrant, or make any representations,
|
|
* either expressed or implied, regarding the use of this library or any
|
|
* part of it.
|
|
* <br><br>
|
|
* Author: Paul Lamb
|
|
* <br>
|
|
* http://www.paulscode.com
|
|
* </b>
|
|
*/
|
|
public class MidiChannel implements MetaEventListener
|
|
{
|
|
/**
|
|
* Processes status messages, warnings, and error messages.
|
|
*/
|
|
private SoundSystemLogger logger;
|
|
|
|
/**
|
|
* Filename/URL to the file:
|
|
*/
|
|
private FilenameURL filenameURL;
|
|
|
|
/**
|
|
* Unique source identifier for this MIDI source.
|
|
*/
|
|
private String sourcename;
|
|
|
|
/**
|
|
* Global identifier for the MIDI "change volume" event.
|
|
*/
|
|
private static final int CHANGE_VOLUME = 7;
|
|
|
|
/**
|
|
* Global identifier for the MIDI "end of track" event.
|
|
*/
|
|
private static final int END_OF_TRACK = 47;
|
|
|
|
/**
|
|
* Used to return a current value from one of the synchronized
|
|
* boolean-interface methods.
|
|
*/
|
|
private static final boolean GET = false;
|
|
|
|
/**
|
|
* Used to set the value in one of the synchronized boolean-interface methods.
|
|
*/
|
|
private static final boolean SET = true;
|
|
|
|
/**
|
|
* Used when a parameter for one of the synchronized boolean-interface methods
|
|
* is not aplicable.
|
|
*/
|
|
private static final boolean XXX = false;
|
|
|
|
/**
|
|
* Runs the assigned sequence, passing information on to the synthesizer for
|
|
* playback.
|
|
*/
|
|
private Sequencer sequencer = null;
|
|
|
|
/**
|
|
* Converts MIDI events into audio.
|
|
*/
|
|
private Synthesizer synthesizer = null;
|
|
|
|
/**
|
|
* Converts MIDI events into audio if there is no default Synthesizer.
|
|
*/
|
|
private MidiDevice synthDevice = null;
|
|
|
|
/**
|
|
* Sequence of MIDI events defining sound.
|
|
*/
|
|
private Sequence sequence = null;
|
|
|
|
/**
|
|
* Should playback loop or play only once.
|
|
*/
|
|
private boolean toLoop = true;
|
|
|
|
/**
|
|
* Playback volume, float value (0.0f - 1.0f).
|
|
*/
|
|
private float gain = 1.0f;
|
|
|
|
/**
|
|
* True while sequencer is busy being set up.
|
|
*/
|
|
private boolean loading = true;
|
|
|
|
/**
|
|
* The list of MIDI files to play when the current sequence finishes.
|
|
*/
|
|
private LinkedList<FilenameURL> sequenceQueue = null;
|
|
|
|
/**
|
|
* Ensures that only one thread accesses the sequenceQueue at a time.
|
|
*/
|
|
private final Object sequenceQueueLock = new Object();
|
|
|
|
/**
|
|
* Specifies the gain factor used for the fade-out effect, or -1 when
|
|
* playback is not currently fading out.
|
|
*/
|
|
protected float fadeOutGain = -1.0f;
|
|
|
|
/**
|
|
* Specifies the gain factor used for the fade-in effect, or 1 when
|
|
* playback is not currently fading in.
|
|
*/
|
|
protected float fadeInGain = 1.0f;
|
|
|
|
/**
|
|
* Specifies the number of miliseconds it should take to fade out.
|
|
*/
|
|
protected long fadeOutMilis = 0;
|
|
|
|
/**
|
|
* Specifies the number of miliseconds it should take to fade in.
|
|
*/
|
|
protected long fadeInMilis = 0;
|
|
|
|
/**
|
|
* System time in miliseconds when the last fade in/out volume check occurred.
|
|
*/
|
|
protected long lastFadeCheck = 0;
|
|
|
|
/**
|
|
* Used for fading in and out effects.
|
|
*/
|
|
private FadeThread fadeThread = null;
|
|
|
|
/**
|
|
* Constructor: Defines the basic source information.
|
|
* @param toLoop Should playback loop or play only once?
|
|
* @param sourcename Unique identifier for this source.
|
|
* @param filename Name of the MIDI file to play.
|
|
*/
|
|
public MidiChannel( boolean toLoop, String sourcename, String filename )
|
|
{
|
|
// let others know we are busy loading:
|
|
loading( SET, true );
|
|
|
|
// grab a handle to the message logger:
|
|
logger = SoundSystemConfig.getLogger();
|
|
|
|
// save information about the source:
|
|
filenameURL( SET, new FilenameURL( filename ) );
|
|
sourcename( SET, sourcename );
|
|
setLooping( toLoop );
|
|
|
|
// initialize the MIDI channel:
|
|
init();
|
|
|
|
// finished loading:
|
|
loading( SET, false );
|
|
}
|
|
|
|
/**
|
|
* Constructor: Defines the basic source information. The fourth parameter,
|
|
* 'identifier' should look like a filename, and it must have the correct
|
|
* extension (.mid or .midi).
|
|
* @param toLoop Should playback loop or play only once?
|
|
* @param sourcename Unique identifier for this source.
|
|
* @param midiFile URL to the MIDI file to play.
|
|
* @param identifier Filename/identifier for the MIDI file.
|
|
*/
|
|
public MidiChannel( boolean toLoop, String sourcename, URL midiFile,
|
|
String identifier )
|
|
{
|
|
// let others know we are busy loading
|
|
loading( SET, true );
|
|
|
|
// grab a handle to the message logger:
|
|
logger = SoundSystemConfig.getLogger();
|
|
|
|
// save information about the source:
|
|
filenameURL( SET, new FilenameURL( midiFile, identifier ) );
|
|
sourcename( SET, sourcename );
|
|
setLooping( toLoop );
|
|
|
|
// initialize the MIDI channel:
|
|
init();
|
|
|
|
// finished loading:
|
|
loading( SET, false );
|
|
}
|
|
|
|
/**
|
|
* Constructor: Defines the basic source information.
|
|
* @param toLoop Should playback loop or play only once?
|
|
* @param sourcename Unique identifier for this source.
|
|
* @param midiFilenameURL Filename/URL to the MIDI file to play.
|
|
*/
|
|
public MidiChannel( boolean toLoop, String sourcename,
|
|
FilenameURL midiFilenameURL )
|
|
{
|
|
// let others know we are busy loading
|
|
loading( SET, true );
|
|
|
|
// grab a handle to the message logger:
|
|
logger = SoundSystemConfig.getLogger();
|
|
|
|
// save information about the source:
|
|
filenameURL( SET, midiFilenameURL );
|
|
sourcename( SET, sourcename );
|
|
setLooping( toLoop );
|
|
|
|
// initialize the MIDI channel:
|
|
init();
|
|
|
|
// finished loading:
|
|
loading( SET, false );
|
|
}
|
|
|
|
/**
|
|
* Initializes the sequencer, loads the sequence, and sets up the synthesizer.
|
|
*/
|
|
private void init()
|
|
{
|
|
// Load a sequencer:
|
|
getSequencer();
|
|
|
|
// Load the sequence to play:
|
|
setSequence( filenameURL( GET, null).getURL() );
|
|
|
|
// Load a synthesizer to play the sequence on:
|
|
getSynthesizer();
|
|
|
|
// Ensure the initial volume is correct:
|
|
// (TODO: doesn't always work??)
|
|
resetGain();
|
|
}
|
|
|
|
/**
|
|
* Shuts the channel down and removes references to all instantiated objects.
|
|
*/
|
|
public void cleanup()
|
|
{
|
|
loading( SET, true );
|
|
setLooping( true );
|
|
|
|
if( sequencer != null )
|
|
{
|
|
try
|
|
{
|
|
sequencer.stop();
|
|
sequencer.close();
|
|
sequencer.removeMetaEventListener( this );
|
|
}
|
|
catch( Exception e )
|
|
{}
|
|
}
|
|
|
|
logger = null;
|
|
sequencer = null;
|
|
synthesizer = null;
|
|
sequence = null;
|
|
|
|
synchronized( sequenceQueueLock )
|
|
{
|
|
if( sequenceQueue != null )
|
|
sequenceQueue.clear();
|
|
sequenceQueue = null;
|
|
}
|
|
|
|
// End the fade effects thread if it exists:
|
|
if( fadeThread != null )
|
|
{
|
|
boolean killException = false;
|
|
try
|
|
{
|
|
fadeThread.kill(); // end the fade effects thread.
|
|
fadeThread.interrupt(); // wake the thread up so it can end.
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
killException = true;
|
|
}
|
|
|
|
if( !killException )
|
|
{
|
|
// wait up to 5 seconds for fade effects thread to end:
|
|
for( int i = 0; i < 50; i++ )
|
|
{
|
|
if( !fadeThread.alive() )
|
|
break;
|
|
try{Thread.sleep( 100 );}catch(InterruptedException e){}
|
|
}
|
|
}
|
|
|
|
// Let user know if there was a problem ending the fade thread
|
|
if( killException || fadeThread.alive() )
|
|
{
|
|
errorMessage( "MIDI fade effects thread did not die!" );
|
|
message( "Ignoring errors... continuing clean-up." );
|
|
}
|
|
}
|
|
|
|
fadeThread = null;
|
|
|
|
loading( SET, false );
|
|
}
|
|
|
|
/**
|
|
* Queues up the next MIDI sequence to play when the previous sequence ends.
|
|
* @param filenameURL MIDI sequence to play next.
|
|
*/
|
|
public void queueSound( FilenameURL filenameURL )
|
|
{
|
|
if( filenameURL == null )
|
|
{
|
|
errorMessage( "Filename/URL not specified in method 'queueSound'" );
|
|
return;
|
|
}
|
|
|
|
synchronized( sequenceQueueLock )
|
|
{
|
|
if( sequenceQueue == null )
|
|
sequenceQueue = new LinkedList<FilenameURL>();
|
|
sequenceQueue.add( filenameURL );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes the first occurrence of the specified filename/identifier from the
|
|
* list of MIDI sequences to play when the previous sequence ends.
|
|
* @param filename Filename or identifier of a MIDI sequence to remove from the
|
|
* queue.
|
|
*/
|
|
public void dequeueSound( String filename )
|
|
{
|
|
if( filename == null || filename.equals( "" ) )
|
|
{
|
|
errorMessage( "Filename not specified in method 'dequeueSound'" );
|
|
return;
|
|
}
|
|
|
|
synchronized( sequenceQueueLock )
|
|
{
|
|
if( sequenceQueue != null )
|
|
{
|
|
ListIterator<FilenameURL> i = sequenceQueue.listIterator();
|
|
while( i.hasNext() )
|
|
{
|
|
if( i.next().getFilename().equals( filename ) )
|
|
{
|
|
i.remove();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fades out the volume of whatever sequence is currently playing, then
|
|
* begins playing the specified MIDI file at the previously assigned
|
|
* volume level. If the filenameURL parameter is null or empty, playback will
|
|
* simply fade out and stop. The miliseconds parameter must be non-negative or
|
|
* zero. This method will remove anything that is currently in the list of
|
|
* queued MIDI sequences that would have played next when current playback
|
|
* finished.
|
|
* @param filenameURL MIDI file to play next, or null for none.
|
|
* @param milis Number of miliseconds the fadeout should take.
|
|
*/
|
|
public void fadeOut( FilenameURL filenameURL, long milis )
|
|
{
|
|
if( milis < 0 )
|
|
{
|
|
errorMessage( "Miliseconds may not be negative in method " +
|
|
"'fadeOut'." );
|
|
return;
|
|
}
|
|
|
|
fadeOutMilis = milis;
|
|
fadeInMilis = 0;
|
|
fadeOutGain = 1.0f;
|
|
lastFadeCheck = EaglerAdapter.steadyTimeMillis();
|
|
|
|
synchronized( sequenceQueueLock )
|
|
{
|
|
if( sequenceQueue != null )
|
|
sequenceQueue.clear();
|
|
|
|
if( filenameURL != null )
|
|
{
|
|
if( sequenceQueue == null )
|
|
sequenceQueue = new LinkedList<FilenameURL>();
|
|
sequenceQueue.add( filenameURL );
|
|
}
|
|
}
|
|
if( fadeThread == null )
|
|
{
|
|
fadeThread = new FadeThread();
|
|
fadeThread.start();
|
|
}
|
|
fadeThread.interrupt();
|
|
}
|
|
|
|
/**
|
|
* Fades out the volume of whatever sequence is currently playing, then
|
|
* fades the volume back in playing the specified MIDI file. Final volume
|
|
* after fade-in completes will be equal to the previously assigned volume
|
|
* level. The filenameURL parameter may not be null or empty. The miliseconds
|
|
* parameters must be non-negative or zero. This method will remove anything
|
|
* that is currently in the list of queued MIDI sequences that would have
|
|
* played next when current playback finished.
|
|
* @param filenameURL MIDI file to play next, or null for none.
|
|
* @param milisOut Number of miliseconds the fadeout should take.
|
|
* @param milisIn Number of miliseconds the fadein should take.
|
|
*/
|
|
public void fadeOutIn( FilenameURL filenameURL, long milisOut,
|
|
long milisIn )
|
|
{
|
|
if( filenameURL == null )
|
|
{
|
|
errorMessage( "Filename/URL not specified in method 'fadeOutIn'." );
|
|
return;
|
|
}
|
|
if( milisOut < 0 || milisIn < 0 )
|
|
{
|
|
errorMessage( "Miliseconds may not be negative in method " +
|
|
"'fadeOutIn'." );
|
|
return;
|
|
}
|
|
|
|
fadeOutMilis = milisOut;
|
|
fadeInMilis = milisIn;
|
|
fadeOutGain = 1.0f;
|
|
lastFadeCheck = EaglerAdapter.steadyTimeMillis();
|
|
|
|
synchronized( sequenceQueueLock )
|
|
{
|
|
if( sequenceQueue == null )
|
|
sequenceQueue = new LinkedList<FilenameURL>();
|
|
sequenceQueue.clear();
|
|
sequenceQueue.add( filenameURL );
|
|
}
|
|
if( fadeThread == null )
|
|
{
|
|
fadeThread = new FadeThread();
|
|
fadeThread.start();
|
|
}
|
|
fadeThread.interrupt();
|
|
}
|
|
|
|
/**
|
|
* Resets this source's volume if it is fading out or in. Returns true if this
|
|
* source is currently in the process of fading out. When fade-out completes,
|
|
* this method transitions the source to the next sound in the sound sequence
|
|
* queue if there is one. This method has no effect on non-streaming sources.
|
|
* @return True if this source is in the process of fading out.
|
|
*/
|
|
private synchronized boolean checkFadeOut()
|
|
{
|
|
if( fadeOutGain == -1.0f && fadeInGain == 1.0f )
|
|
return false;
|
|
|
|
long currentTime = EaglerAdapter.steadyTimeMillis();
|
|
long milisPast = currentTime - lastFadeCheck;
|
|
lastFadeCheck = currentTime;
|
|
|
|
if( fadeOutGain >= 0.0f )
|
|
{
|
|
if( fadeOutMilis == 0 )
|
|
{
|
|
fadeOutGain = 0.0f;
|
|
fadeInGain = 0.0f;
|
|
if( !incrementSequence() )
|
|
stop();
|
|
rewind();
|
|
resetGain();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
float fadeOutReduction = ((float)milisPast) / ((float)fadeOutMilis);
|
|
|
|
fadeOutGain -= fadeOutReduction;
|
|
if( fadeOutGain <= 0.0f )
|
|
{
|
|
fadeOutGain = -1.0f;
|
|
fadeInGain = 0.0f;
|
|
if( !incrementSequence() )
|
|
stop();
|
|
rewind();
|
|
resetGain();
|
|
return false;
|
|
}
|
|
}
|
|
resetGain();
|
|
return true;
|
|
}
|
|
|
|
if( fadeInGain < 1.0f )
|
|
{
|
|
fadeOutGain = -1.0f;
|
|
if( fadeInMilis == 0 )
|
|
{
|
|
fadeOutGain = -1.0f;
|
|
fadeInGain = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
float fadeInIncrease = ((float)milisPast) / ((float)fadeInMilis);
|
|
fadeInGain += fadeInIncrease;
|
|
if( fadeInGain >= 1.0f )
|
|
{
|
|
fadeOutGain = -1.0f;
|
|
fadeInGain = 1.0f;
|
|
}
|
|
}
|
|
resetGain();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Removes the next sequence from the queue and assigns it to the sequencer.
|
|
* @return True if there was something in the queue.
|
|
*/
|
|
private boolean incrementSequence()
|
|
{
|
|
synchronized( sequenceQueueLock )
|
|
{
|
|
// Is there a queue, and if so, is there anything in it:
|
|
if( sequenceQueue != null && sequenceQueue.size() > 0 )
|
|
{
|
|
// grab the next filename/URL from the queue:
|
|
filenameURL( SET, sequenceQueue.remove( 0 ) );
|
|
|
|
// Let everyone know we are busy loading:
|
|
loading( SET, true );
|
|
|
|
// Check if we have a sequencer:
|
|
if( sequencer == null )
|
|
{
|
|
// nope, try and get one now:
|
|
getSequencer();
|
|
}
|
|
else
|
|
{
|
|
// We have a sequencer. Stop it now:
|
|
sequencer.stop();
|
|
// rewind to the beginning:
|
|
sequencer.setMicrosecondPosition( 0 );
|
|
// Stop listening for a moment:
|
|
sequencer.removeMetaEventListener( this );
|
|
// wait a bit for the sequencer to shut down and rewind:
|
|
try{ Thread.sleep( 100 ); }catch( InterruptedException e ){}
|
|
}
|
|
// We need to have a sequencer at this point:
|
|
if( sequencer == null )
|
|
{
|
|
errorMessage( "Unable to set the sequence in method " +
|
|
"'incrementSequence', because there wasn't " +
|
|
"a sequencer to use." );
|
|
|
|
// Finished loading:
|
|
loading( SET, false );
|
|
|
|
// failure:
|
|
return false;
|
|
}
|
|
// set the new sequence to be played:
|
|
setSequence( filenameURL( GET, null ).getURL() );
|
|
// start playing again:
|
|
sequencer.start();
|
|
// make sure we play at the correct volume:
|
|
// (TODO: This doesn't always work??)
|
|
resetGain();
|
|
// start listening for end of track event again:
|
|
sequencer.addMetaEventListener( this );
|
|
|
|
// Finished loading:
|
|
loading( SET, false );
|
|
|
|
// We successfully moved to the next sequence:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Nothing left to load
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Plays the MIDI file from the beginning, or from where it left off if it was
|
|
* paused.
|
|
*/
|
|
public void play()
|
|
{
|
|
if( !loading() )
|
|
{
|
|
// Make sure there is a sequencer:
|
|
if( sequencer == null )
|
|
return;
|
|
|
|
try
|
|
{
|
|
// start playing:
|
|
sequencer.start();
|
|
// event will be sent when end of track is reached:
|
|
sequencer.addMetaEventListener( this );
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
errorMessage( "Exception in method 'play'" );
|
|
printStackTrace( e );
|
|
SoundSystemException sse = new SoundSystemException(
|
|
e.getMessage() );
|
|
SoundSystem.setException( sse );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops playback and rewinds to the beginning.
|
|
*/
|
|
public void stop()
|
|
{
|
|
if( !loading() )
|
|
{
|
|
// Make sure there is a sequencer:
|
|
if( sequencer == null )
|
|
return;
|
|
|
|
try
|
|
{
|
|
// stop playback:
|
|
sequencer.stop();
|
|
// rewind to the beginning:
|
|
sequencer.setMicrosecondPosition( 0 );
|
|
// No need to listen any more:
|
|
sequencer.removeMetaEventListener( this );
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
errorMessage( "Exception in method 'stop'" );
|
|
printStackTrace( e );
|
|
SoundSystemException sse = new SoundSystemException(
|
|
e.getMessage() );
|
|
SoundSystem.setException( sse );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Temporarily stops playback without rewinding.
|
|
*/
|
|
public void pause()
|
|
{
|
|
if( !loading() )
|
|
{
|
|
// Make sure there is a sequencer:
|
|
if( sequencer == null )
|
|
return;
|
|
|
|
try
|
|
{
|
|
//stop playback. Will resume from this location next play.
|
|
sequencer.stop();
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
errorMessage( "Exception in method 'pause'" );
|
|
printStackTrace( e );
|
|
SoundSystemException sse = new SoundSystemException(
|
|
e.getMessage() );
|
|
SoundSystem.setException( sse );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns playback to the beginning.
|
|
*/
|
|
public void rewind()
|
|
{
|
|
if( !loading() )
|
|
{
|
|
// Make sure there is a sequencer:
|
|
if( sequencer == null )
|
|
return;
|
|
|
|
try
|
|
{
|
|
// rewind to the beginning:
|
|
sequencer.setMicrosecondPosition( 0 );
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
errorMessage( "Exception in method 'rewind'" );
|
|
printStackTrace( e );
|
|
SoundSystemException sse = new SoundSystemException(
|
|
e.getMessage() );
|
|
SoundSystem.setException( sse );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Changes the volume of MIDI playback.
|
|
* @param value Float value (0.0f - 1.0f).
|
|
*/
|
|
public void setVolume( float value )
|
|
{
|
|
gain = value;
|
|
resetGain();
|
|
}
|
|
|
|
/**
|
|
* Returns the current volume for the MIDI source.
|
|
* @return Float value (0.0f - 1.0f).
|
|
*/
|
|
public float getVolume()
|
|
{
|
|
return gain;
|
|
}
|
|
|
|
/**
|
|
* Changes the basic information about the MIDI source. This method removes
|
|
* any queued filenames/URLs from the list of MIDI sequences that would have
|
|
* played after the current sequence ended.
|
|
* @param toLoop Should playback loop or play only once?
|
|
* @param sourcename Unique identifier for this source.
|
|
* @param filename Name of the MIDI file to play.
|
|
*/
|
|
public void switchSource( boolean toLoop, String sourcename,
|
|
String filename )
|
|
{
|
|
// Let everyone know we are busy loading:
|
|
loading( SET, true );
|
|
|
|
// save information about the source:
|
|
filenameURL( SET, new FilenameURL( filename ) );
|
|
sourcename( SET, sourcename );
|
|
setLooping( toLoop );
|
|
|
|
reset();
|
|
|
|
// Finished loading:
|
|
loading( SET, false );
|
|
}
|
|
|
|
/**
|
|
* Changes the basic information about the MIDI source. This method removes
|
|
* any queued filenames/URLs from the list of MIDI sequences that would have
|
|
* played after the current sequence ended. The fourth parameter,
|
|
* 'identifier' should look like a filename, and it must have the correct
|
|
* extension (.mid or .midi).
|
|
* @param toLoop Should playback loop or play only once?
|
|
* @param sourcename Unique identifier for this source.
|
|
* @param midiFile URL to the MIDI file to play.
|
|
* @param identifier Filename/identifier for the MIDI file.
|
|
*/
|
|
public void switchSource( boolean toLoop, String sourcename, URL midiFile,
|
|
String identifier )
|
|
{
|
|
// Let everyone know we are busy loading:
|
|
loading( SET, true );
|
|
|
|
// save information about the source:
|
|
filenameURL( SET, new FilenameURL( midiFile, identifier ) );
|
|
sourcename( SET, sourcename );
|
|
setLooping( toLoop );
|
|
|
|
reset();
|
|
|
|
// Finished loading:
|
|
loading( SET, false );
|
|
}
|
|
|
|
/**
|
|
* Changes the basic information about the MIDI source. This method removes
|
|
* any queued filenames/URLs from the list of MIDI sequences that would have
|
|
* played after the current sequence ended.
|
|
* @param toLoop Should playback loop or play only once?
|
|
* @param sourcename Unique identifier for this source.
|
|
* @param filenameURL Filename/URL of the MIDI file to play.
|
|
*/
|
|
public void switchSource( boolean toLoop, String sourcename,
|
|
FilenameURL filenameURL )
|
|
{
|
|
// Let everyone know we are busy loading:
|
|
loading( SET, true );
|
|
|
|
// save information about the source:
|
|
filenameURL( SET, filenameURL );
|
|
sourcename( SET, sourcename );
|
|
setLooping( toLoop );
|
|
|
|
reset();
|
|
|
|
// Finished loading:
|
|
loading( SET, false );
|
|
}
|
|
|
|
/**
|
|
* Stops and rewinds the sequencer, and resets the sequence.
|
|
*/
|
|
private void reset()
|
|
{
|
|
synchronized( sequenceQueueLock )
|
|
{
|
|
if( sequenceQueue != null )
|
|
sequenceQueue.clear();
|
|
}
|
|
|
|
// Check if we have a sequencer:
|
|
if( sequencer == null )
|
|
{
|
|
// nope, try and get one now:
|
|
getSequencer();
|
|
}
|
|
else
|
|
{
|
|
// We have a sequencer. Stop it now:
|
|
sequencer.stop();
|
|
// rewind to the beginning:
|
|
sequencer.setMicrosecondPosition( 0 );
|
|
// Stop listening for a moment:
|
|
sequencer.removeMetaEventListener( this );
|
|
// wait a bit for the sequencer to shut down and rewind:
|
|
try{ Thread.sleep( 100 ); }catch( InterruptedException e ){}
|
|
}
|
|
// We need to have a sequencer at this point:
|
|
if( sequencer == null )
|
|
{
|
|
errorMessage( "Unable to set the sequence in method " +
|
|
"'reset', because there wasn't " +
|
|
"a sequencer to use." );
|
|
return;
|
|
}
|
|
|
|
// set the new sequence to be played:
|
|
setSequence( filenameURL( GET, null ).getURL() );
|
|
// start playing again:
|
|
sequencer.start();
|
|
// make sure we play at the correct volume:
|
|
// (TODO: This doesn't always work??)
|
|
resetGain();
|
|
// start listening for end of track event again:
|
|
sequencer.addMetaEventListener( this );
|
|
}
|
|
|
|
/**
|
|
* Sets the value of boolean 'toLoop'.
|
|
* @param value True or False.
|
|
*/
|
|
public void setLooping( boolean value )
|
|
{
|
|
toLoop( SET, value );
|
|
}
|
|
|
|
/**
|
|
* Returns the value of boolean 'toLoop'.
|
|
* @return True while looping.
|
|
*/
|
|
public boolean getLooping()
|
|
{
|
|
return toLoop( GET, XXX );
|
|
}
|
|
|
|
/**
|
|
* Sets or returns the value of boolean 'toLoop'.
|
|
* @param action GET or SET.
|
|
* @param value New value if action == SET, or XXX if action == GET.
|
|
* @return True while looping.
|
|
*/
|
|
private synchronized boolean toLoop( boolean action, boolean value )
|
|
{
|
|
if( action == SET )
|
|
toLoop = value;
|
|
return toLoop;
|
|
}
|
|
|
|
/**
|
|
* Check if a MIDI file is in the process of loading.
|
|
*/
|
|
public boolean loading()
|
|
{
|
|
return( loading( GET, XXX ) );
|
|
}
|
|
|
|
/**
|
|
* Sets or returns the value of boolean 'loading'.
|
|
* @param action GET or SET.
|
|
* @param value New value if action == SET, or XXX if action == GET.
|
|
* @return True while a MIDI file is in the process of loading.
|
|
*/
|
|
private synchronized boolean loading( boolean action, boolean value )
|
|
{
|
|
if( action == SET )
|
|
loading = value;
|
|
return loading;
|
|
}
|
|
|
|
/**
|
|
* Defines the unique identifier for this source
|
|
* @param value New source name.
|
|
*/
|
|
public void setSourcename( String value )
|
|
{
|
|
sourcename( SET, value );
|
|
}
|
|
|
|
/**
|
|
* Returns the unique identifier for this source.
|
|
* @return The source's name.
|
|
*/
|
|
public String getSourcename()
|
|
{
|
|
return sourcename( GET, null );
|
|
}
|
|
|
|
/**
|
|
* Sets or returns the value of String 'sourcename'.
|
|
* @param action GET or SET.
|
|
* @param value New value if action == SET, or null if action == GET.
|
|
* @return The source's name.
|
|
*/
|
|
private synchronized String sourcename( boolean action, String value )
|
|
{
|
|
if( action == SET )
|
|
sourcename = value;
|
|
return sourcename;
|
|
}
|
|
|
|
/**
|
|
* Defines which MIDI file to play.
|
|
* @param value Path to the MIDI file.
|
|
*/
|
|
public void setFilenameURL( FilenameURL value )
|
|
{
|
|
filenameURL( SET, value );
|
|
}
|
|
|
|
/**
|
|
* Returns the filename/identifier of the MIDI file being played.
|
|
* @return Filename of identifier of the MIDI file.
|
|
*/
|
|
public String getFilename()
|
|
{
|
|
return filenameURL( GET, null ).getFilename();
|
|
}
|
|
|
|
/**
|
|
* Returns the MIDI file being played.
|
|
* @return Filename/URL of the MIDI file.
|
|
*/
|
|
public FilenameURL getFilenameURL()
|
|
{
|
|
return filenameURL( GET, null );
|
|
}
|
|
|
|
/**
|
|
* Sets or returns the value of filenameURL.
|
|
* @param action GET or SET.
|
|
* @param value New value if action == SET, or null if action == GET.
|
|
* @return Path to the MIDI file.
|
|
*/
|
|
private synchronized FilenameURL filenameURL( boolean action,
|
|
FilenameURL value )
|
|
{
|
|
if( action == SET )
|
|
filenameURL = value;
|
|
return filenameURL;
|
|
}
|
|
|
|
/**
|
|
* Called when MIDI events occur.
|
|
* @param message Meta mssage describing the MIDI event.
|
|
*/
|
|
public void meta( MetaMessage message )
|
|
{
|
|
if( message.getType() == END_OF_TRACK )
|
|
{
|
|
// Generate an EOS event:
|
|
SoundSystemConfig.notifyEOS( sourcename, sequenceQueue.size() );
|
|
|
|
// check if we should loop or not:
|
|
if( toLoop )
|
|
{
|
|
// looping
|
|
// Check if playback is in the process of fading out.
|
|
if( !checkFadeOut() )
|
|
{
|
|
// Not fading out, progress to the next MIDI sequence if
|
|
// any are queued.
|
|
if( !incrementSequence() )
|
|
{
|
|
try
|
|
{
|
|
// Rewind to the beginning.
|
|
sequencer.setMicrosecondPosition( 0 );
|
|
sequencer.start();
|
|
// Make sure playback volume is correct.
|
|
resetGain();
|
|
}
|
|
catch( Exception e ){}
|
|
}
|
|
}
|
|
else if( sequencer != null )
|
|
{
|
|
try
|
|
{
|
|
// Rewind to the beginning.
|
|
sequencer.setMicrosecondPosition( 0 );
|
|
sequencer.start();
|
|
// Make sure playback volume is correct.
|
|
resetGain();
|
|
}
|
|
catch( Exception e ){}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//non-looping
|
|
if( !checkFadeOut() )
|
|
{
|
|
if( !incrementSequence() )
|
|
{
|
|
try
|
|
{
|
|
// stop playback:
|
|
sequencer.stop();
|
|
// rewind to the beginning:
|
|
sequencer.setMicrosecondPosition( 0 );
|
|
// stop looping:
|
|
sequencer.removeMetaEventListener( this );
|
|
}
|
|
catch( Exception e ){}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
// stop playback:
|
|
sequencer.stop();
|
|
// rewind to the beginning:
|
|
sequencer.setMicrosecondPosition( 0 );
|
|
// stop looping:
|
|
sequencer.removeMetaEventListener( this );
|
|
}
|
|
catch( Exception e ){}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets playback volume to the correct level.
|
|
*/
|
|
public void resetGain()
|
|
{
|
|
// make sure the value for gain is valid (between 0 and 1)
|
|
if( gain < 0.0f )
|
|
gain = 0.0f;
|
|
if( gain > 1.0f )
|
|
gain = 1.0f;
|
|
|
|
int midiVolume = (int) ( gain * SoundSystemConfig.getMasterGain()
|
|
* (float) Math.abs( fadeOutGain ) * fadeInGain
|
|
* 127.0f );
|
|
if( synthesizer != null )
|
|
{
|
|
javax.sound.midi.MidiChannel[] channels = synthesizer.getChannels();
|
|
for( int c = 0; channels != null && c < channels.length; c++ )
|
|
{
|
|
channels[c].controlChange( CHANGE_VOLUME, midiVolume );
|
|
}
|
|
}
|
|
else if( synthDevice != null )
|
|
{
|
|
try
|
|
{
|
|
ShortMessage volumeMessage = new ShortMessage();
|
|
for( int i = 0; i < 16; i++ )
|
|
{
|
|
volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, i,
|
|
CHANGE_VOLUME, midiVolume );
|
|
synthDevice.getReceiver().send( volumeMessage, -1 );
|
|
}
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
errorMessage( "Error resetting gain on MIDI device" );
|
|
printStackTrace( e );
|
|
}
|
|
}
|
|
else if( sequencer != null && sequencer instanceof Synthesizer )
|
|
{
|
|
synthesizer = (Synthesizer) sequencer;
|
|
javax.sound.midi.MidiChannel[] channels = synthesizer.getChannels();
|
|
for( int c = 0; channels != null && c < channels.length; c++ )
|
|
{
|
|
channels[c].controlChange( CHANGE_VOLUME, midiVolume );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
Receiver receiver = MidiSystem.getReceiver();
|
|
ShortMessage volumeMessage= new ShortMessage();
|
|
for( int c = 0; c < 16; c++ )
|
|
{
|
|
volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, c,
|
|
CHANGE_VOLUME, midiVolume );
|
|
receiver.send( volumeMessage, -1 );
|
|
}
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
errorMessage( "Error resetting gain on default receiver" );
|
|
printStackTrace( e );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempts to load the default sequencer. If it fails, then other common
|
|
* sequencers are tried. If none can be loaded, then variable 'sequencer'
|
|
* remains null.
|
|
*/
|
|
private void getSequencer()
|
|
{
|
|
try
|
|
{
|
|
sequencer = MidiSystem.getSequencer();
|
|
if( sequencer != null )
|
|
{
|
|
try
|
|
{
|
|
sequencer.getTransmitter();
|
|
}
|
|
catch( MidiUnavailableException mue )
|
|
{
|
|
message( "Unable to get a transmitter from the " +
|
|
"default MIDI sequencer" );
|
|
}
|
|
sequencer.open();
|
|
}
|
|
}
|
|
catch( MidiUnavailableException mue )
|
|
{
|
|
message( "Unable to open the default MIDI sequencer" );
|
|
sequencer = null;
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
if( e instanceof InterruptedException )
|
|
{
|
|
message( "Caught InterruptedException while attempting to " +
|
|
"open the default MIDI sequencer. Trying again." );
|
|
sequencer = null;
|
|
}
|
|
try
|
|
{
|
|
sequencer = MidiSystem.getSequencer();
|
|
if( sequencer != null )
|
|
{
|
|
try
|
|
{
|
|
sequencer.getTransmitter();
|
|
}
|
|
catch( MidiUnavailableException mue )
|
|
{
|
|
message( "Unable to get a transmitter from the " +
|
|
"default MIDI sequencer" );
|
|
}
|
|
sequencer.open();
|
|
}
|
|
}
|
|
catch( MidiUnavailableException mue )
|
|
{
|
|
message( "Unable to open the default MIDI sequencer" );
|
|
sequencer = null;
|
|
}
|
|
catch( Exception e2 )
|
|
{
|
|
message( "Unknown error opening the default MIDI sequencer" );
|
|
sequencer = null;
|
|
}
|
|
}
|
|
|
|
if( sequencer == null )
|
|
sequencer = openSequencer( "Real Time Sequencer" );
|
|
if( sequencer == null )
|
|
sequencer = openSequencer( "Java Sound Sequencer");
|
|
if( sequencer == null )
|
|
{
|
|
errorMessage( "Failed to find an available MIDI sequencer" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the MIDI sequence form the specified URL, and sets the sequence. If
|
|
* variable 'sequencer' is null or an error occurs, then variable 'sequence'
|
|
* remains null.
|
|
* @param midiSource URL to a MIDI file.
|
|
*/
|
|
private void setSequence( URL midiSource )
|
|
{
|
|
if( sequencer == null )
|
|
{
|
|
errorMessage( "Unable to update the sequence in method " +
|
|
"'setSequence', because variable 'sequencer' " +
|
|
"is null" );
|
|
return;
|
|
}
|
|
|
|
if( midiSource == null )
|
|
{
|
|
errorMessage( "Unable to load Midi file in method 'setSequence'." );
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
sequence = MidiSystem.getSequence( midiSource );
|
|
}
|
|
catch( IOException ioe )
|
|
{
|
|
errorMessage( "Input failed while reading from MIDI file in " +
|
|
"method 'setSequence'." );
|
|
printStackTrace( ioe );
|
|
return;
|
|
}
|
|
catch( InvalidMidiDataException imde )
|
|
{
|
|
errorMessage( "Invalid MIDI data encountered, or not a MIDI " +
|
|
"file in method 'setSequence' (1)." );
|
|
printStackTrace( imde );
|
|
return;
|
|
}
|
|
if( sequence == null )
|
|
{
|
|
errorMessage( "MidiSystem 'getSequence' method returned null " +
|
|
"in method 'setSequence'." );
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
sequencer.setSequence( sequence );
|
|
}
|
|
catch( InvalidMidiDataException imde )
|
|
{
|
|
errorMessage( "Invalid MIDI data encountered, or not a MIDI " +
|
|
"file in method 'setSequence' (2)." );
|
|
printStackTrace( imde );
|
|
return;
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
errorMessage( "Problem setting sequence from MIDI file in " +
|
|
"method 'setSequence'." );
|
|
printStackTrace( e );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* First attempts to load the specified "override MIDI synthesizer" if one was
|
|
* defined. If none was defined or unable to use it, then attempts to load the
|
|
* default synthesizer. If that fails, then other common synthesizers are
|
|
* attempted. If none can be loaded, then MIDI is not possible on this system.
|
|
*/
|
|
private void getSynthesizer()
|
|
{
|
|
if( sequencer == null )
|
|
{
|
|
errorMessage( "Unable to load a Synthesizer in method " +
|
|
"'getSynthesizer', because variable 'sequencer' " +
|
|
"is null" );
|
|
return;
|
|
}
|
|
|
|
// Check if an alternate MIDI synthesizer was specified to use
|
|
String overrideMIDISynthesizer =
|
|
SoundSystemConfig.getOverrideMIDISynthesizer();
|
|
if( overrideMIDISynthesizer != null
|
|
&& !overrideMIDISynthesizer.equals( "" ) )
|
|
{
|
|
// Try and open the specified device:
|
|
synthDevice = openMidiDevice( overrideMIDISynthesizer );
|
|
// See if we got it:
|
|
if( synthDevice != null )
|
|
{
|
|
// Got it, try and link it to the sequencer:
|
|
try
|
|
{
|
|
sequencer.getTransmitter().setReceiver(
|
|
synthDevice.getReceiver() );
|
|
// Success!
|
|
return;
|
|
}
|
|
catch( MidiUnavailableException mue )
|
|
{
|
|
// Problem linking the two, let the user know
|
|
errorMessage( "Unable to link sequencer transmitter " +
|
|
"with receiver for MIDI device '" +
|
|
overrideMIDISynthesizer + "'" );
|
|
}
|
|
}
|
|
}
|
|
|
|
// No alternate MIDI synthesizer was specified, or unable to use it.
|
|
|
|
// If the squencer were also a synthesizer, that would make things easy:
|
|
if( sequencer instanceof Synthesizer )
|
|
{
|
|
synthesizer = (Synthesizer) sequencer;
|
|
}
|
|
else
|
|
{
|
|
// Try getting the default synthesizer first:
|
|
try
|
|
{
|
|
synthesizer = MidiSystem.getSynthesizer();
|
|
synthesizer.open();
|
|
}
|
|
catch( MidiUnavailableException mue )
|
|
{
|
|
message( "Unable to open the default synthesizer" );
|
|
synthesizer = null;
|
|
}
|
|
|
|
// See if we were sucessful:
|
|
if( synthesizer == null )
|
|
{
|
|
// Try for the common MIDI synthesizers:
|
|
synthDevice = openMidiDevice( "Java Sound Synthesizer" );
|
|
if( synthDevice == null )
|
|
synthDevice = openMidiDevice( "Microsoft GS Wavetable" );
|
|
if( synthDevice == null )
|
|
synthDevice = openMidiDevice( "Gervill" );
|
|
if( synthDevice == null )
|
|
{
|
|
// Still nothing, MIDI is not going to work
|
|
errorMessage( "Failed to find an available MIDI " +
|
|
"synthesizer" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Are we using the default synthesizer or something else?
|
|
if( synthesizer == null )
|
|
{
|
|
// Link the sequencer and synthesizer:
|
|
try
|
|
{
|
|
sequencer.getTransmitter().setReceiver(
|
|
synthDevice.getReceiver() );
|
|
}
|
|
catch( MidiUnavailableException mue )
|
|
{
|
|
errorMessage( "Unable to link sequencer transmitter " +
|
|
"with MIDI device receiver" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Bug-fix for multiple-receivers playing simultaneously
|
|
if( synthesizer.getDefaultSoundbank() == null )
|
|
{
|
|
// Link the sequencer to the default receiver:
|
|
try
|
|
{
|
|
sequencer.getTransmitter().setReceiver(
|
|
MidiSystem.getReceiver() );
|
|
}
|
|
catch( MidiUnavailableException mue )
|
|
{
|
|
errorMessage( "Unable to link sequencer transmitter " +
|
|
"with default receiver" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Link the sequencer to the default synthesizer:
|
|
try
|
|
{
|
|
sequencer.getTransmitter().setReceiver(
|
|
synthesizer.getReceiver() );
|
|
}
|
|
catch( MidiUnavailableException mue )
|
|
{
|
|
errorMessage( "Unable to link sequencer transmitter " +
|
|
"with synthesizer receiver" );
|
|
}
|
|
}
|
|
// End bug-fix
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempts to open the Sequencer with a name containing the specified string.
|
|
* @param containsString Part or all of a Sequencer's name.
|
|
* @return Handle to the Sequencer, or null if not found or error.
|
|
*/
|
|
private Sequencer openSequencer( String containsString )
|
|
{
|
|
Sequencer s = null;
|
|
s = (Sequencer) openMidiDevice( containsString );
|
|
if( s == null )
|
|
return null;
|
|
try
|
|
{
|
|
s.getTransmitter();
|
|
}
|
|
catch( MidiUnavailableException mue )
|
|
{
|
|
message( " Unable to get a transmitter from this sequencer" );
|
|
s = null;
|
|
return null;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* Attempts to open the MIDI device with a name containing the specified
|
|
* string.
|
|
* @param containsString Part or all of a MIDI device's name.
|
|
* @return Handle to the MIDI device, or null if not found or error.
|
|
*/
|
|
private MidiDevice openMidiDevice( String containsString )
|
|
{
|
|
message( "Searching for MIDI device with name containing '" +
|
|
containsString + "'" );
|
|
MidiDevice device = null;
|
|
MidiDevice.Info[] midiDevices = MidiSystem.getMidiDeviceInfo();
|
|
for( int i = 0; i < midiDevices.length; i++ )
|
|
{
|
|
device = null;
|
|
try
|
|
{
|
|
device = MidiSystem.getMidiDevice( midiDevices[i] );
|
|
}
|
|
catch( MidiUnavailableException e )
|
|
{
|
|
message( " Problem in method 'getMidiDevice': " +
|
|
"MIDIUnavailableException was thrown" );
|
|
device = null;
|
|
}
|
|
if( device != null && midiDevices[i].getName().contains(
|
|
containsString ) )
|
|
{
|
|
message( " Found MIDI device named '" +
|
|
midiDevices[i].getName() + "'" );
|
|
if( device instanceof Synthesizer )
|
|
message( " *this is a Synthesizer instance" );
|
|
if( device instanceof Sequencer )
|
|
message( " *this is a Sequencer instance" );
|
|
try
|
|
{
|
|
device.open();
|
|
}
|
|
catch( MidiUnavailableException mue )
|
|
{
|
|
message( " Unable to open this MIDI device" );
|
|
device = null;
|
|
}
|
|
return device;
|
|
}
|
|
}
|
|
message( " MIDI device not found" );
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Prints a message.
|
|
* @param message Message to print.
|
|
*/
|
|
protected void message( String message )
|
|
{
|
|
logger.message( message, 0 );
|
|
}
|
|
|
|
/**
|
|
* Prints an important message.
|
|
* @param message Message to print.
|
|
*/
|
|
protected void importantMessage( String message )
|
|
{
|
|
logger.importantMessage( message, 0 );
|
|
}
|
|
|
|
/**
|
|
* Prints the specified message if error is true.
|
|
* @param error True or False.
|
|
* @param message Message to print if error is true.
|
|
* @return True if error is true.
|
|
*/
|
|
protected boolean errorCheck( boolean error, String message )
|
|
{
|
|
return logger.errorCheck( error, "MidiChannel", message, 0 );
|
|
}
|
|
|
|
/**
|
|
* Prints an error message.
|
|
* @param message Message to print.
|
|
*/
|
|
protected void errorMessage( String message )
|
|
{
|
|
logger.errorMessage( "MidiChannel", message, 0 );
|
|
}
|
|
|
|
/**
|
|
* Prints an exception's error message followed by the stack trace.
|
|
* @param e Exception containing the information to print.
|
|
*/
|
|
protected void printStackTrace( Exception e )
|
|
{
|
|
logger.printStackTrace( e, 1 );
|
|
}
|
|
|
|
/**
|
|
* The FadeThread class handles sequence changing, timing, and volume change
|
|
* messages in the background.
|
|
*/
|
|
private class FadeThread extends SimpleThread
|
|
{
|
|
@Override
|
|
/**
|
|
* Runs in the background, timing fade in and fade out, changing the sequence,
|
|
* and issuing the appropriate volume change messages.
|
|
*/
|
|
public void run()
|
|
{
|
|
while( !dying() )
|
|
{
|
|
// if not currently fading in or out, put the thread to sleep
|
|
if( fadeOutGain == -1.0f && fadeInGain == 1.0f )
|
|
snooze( 3600000 );
|
|
checkFadeOut();
|
|
// only update every 50 miliseconds (no need to peg the cpu)
|
|
snooze( 50 );
|
|
}
|
|
// Important!
|
|
cleanup();
|
|
}
|
|
}
|
|
|
|
}
|
|
|