/*
Copyright (c) 2006, Geomatics and Cartographic Research Centre, Carleton 
University All rights reserved.

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met:

 - Redistributions of source code must retain the above copyright notice, 
   this list of conditions and the following disclaimer.
 - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 - Neither the name of the Geomatics and Cartographic Research Centre, 
   Carleton University nor the names of its contributors may be used to 
   endorse or promote products derived from this software without specific 
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.

$Id$
*/
package ca.carleton.gcrc.atlas;

import java.applet.*;
import netscape.javascript.JSObject;

public class SoundApplet extends Applet {
	private static final long serialVersionUID = 1L;
	
	private TrackManager m_trackManager = null;
	private MidiManager m_midiManager = null;
	private boolean m_debugLogging = false; // by default, command-path debug logging is disabled.
	
	private void Log(String logMessage_) {
		if (true == m_debugLogging) {
			System.out.println(logMessage_);
		}
	} // Log()
	
	public void init() {
		Log("SoundApplet Inited");
		if (null == m_trackManager) {
			m_trackManager = new TrackManager(getDocumentBase());
		} else {
			Log("SoundApplet Init: track manager already exists - reusing.");
		}
		
		if (null == m_midiManager) {
			m_midiManager = new MidiManager(getDocumentBase());
		} else {
			Log("SoundApplet Init: MIDI manager already exists - reusing.");
		}
	}
	
	public void start() {
		Log("SoundApplet Started");
		m_trackManager.resume();
		m_midiManager.resume();
		
		// Let application know that the applet is in business
		JSObject win = null;
		try {
			win = JSObject.getWindow(this);
		} catch(Exception e) {
			Log("SoundApplet can not register to the application (JSObject not available): " + e.toString());
		}
		if (null != win) {
			try {
				Object[] args = new Object[0];
				win.call("SoundAppletRegister", args);
			} catch(Exception e) {
				Log("SoundApplet can not register to the application (function not found): " + e.toString());
			}
		} else {
			Log("SoundApplet not registering with application (win is null).");
		}

//		//String intRadioStream = "http://live.urn1350.net:8080/urn_high.ogg"; //OGG
//		//String intRadioStream = "http://64.236.34.97:80/stream/1006"; //MP3
//		//String intRadioStream = "http://205.188.234.68:8006"; //MP3 shoutcast
//		loadStreamSound(intRadioStream);
//		setGain(intRadioStream, (float) 0.6);
//		playSound(intRadioStream);
	}
	
	public void stop() {
		// Let application know that the applet is out of business
		JSObject win = null;
		try {
			win = JSObject.getWindow(this);
		} catch(Exception e) {
			Log("SoundApplet can not deregister to the application (JSObject not available): " + e.toString());
		}
		if (null != win) {
			try {
				Object[] args = new Object[0];
				win.call("SoundAppletDeregister", args);
			} catch(Exception e) {
				Log("SoundApplet can not deregister to the application (function not found): " + e.toString());
			}
		} else {
			Log("SoundApplet not deregistering with application (win is null).");
		}
		
		Log("SoundApplet Stopped");
		m_trackManager.pause();
		m_midiManager.pause();
	}
	
	public void destroy() {
		Log("SoundApplet Destroyed");
		m_trackManager.cleanUp();
		m_midiManager.cleanUp();
	}
	
	public void playAllSounds() {
		m_trackManager.playAllSounds();
		m_midiManager.playSequence();
	}
	
	/**
	 * Mute all currently loaded sounds.  New sounds loaded while this condition persists will be muted
	 * as well.  Gain setting changes will be accepted and stored but not acted upon until this condition
	 * is cleared.
	 */
	public void muteAllSounds() {
		Log("SoundApplet muting all sounds.");
		m_trackManager.setMutingForAllSounds(true);
		m_midiManager.setMutingForAllSounds(true);
	}
	
	/**
	 * Unmute all currently loaded sounds.
	 */
	public void unmuteAllSounds() {
		Log("SoundApplet unmuting all sounds.");
		m_trackManager.setMutingForAllSounds(false);
		m_midiManager.setMutingForAllSounds(false);
	}
	
	/*
	 *    - - - -   S A M P L E D   A U D I O   I N T E R F A C E S   - - - -
	 */
	
	//
	// *********************  Clip Interfaces     *********************************************
	//
	
	/**
	 * Load a single sound clip, specified by urlString_, from a URL identifying a short (maximum length
	 * varies according to client system - unfortunately) digitally-encoded sound.  This sounds will be
	 * loaded into buffer memory and retained there until each is explicitly unloaded.
	 * 
	 * See Track.java, ClipTrack.java, and StreamTrack.java for information on encodings.
	 * 
	 * @param urlString_ specification of URL for a sound clip.
	 */
	public void loadClipSound(String urlString_) {
		Log("SoundApplet loadClipSound " + urlString_);
		m_trackManager.loadClipSound(urlString_);
	}
	
	/**
	 * Load a set of sound clips, specified by urlStrings_ as a comma-separated list of URLs
	 * identifying a set of short (maximum length varies according to client system - unfortunately)
	 * digitally-encoded sounds.  These sounds will be loaded into buffer memory and retained
	 * there until each is explicitly unloaded.
	 * 
	 * See Track.java, ClipTrack.java, and StreamTrack.java for information on encodings.
	 * 
	 * @param urlStrings_ comma-separated set of URL specifications for sound clips.
	 */
	public void loadClipSounds(String urlStrings_) {
		Log("SoundApplet loadClipSounds " + urlStrings_);
		String[] urls = urlStrings_.split(",");
		m_trackManager.loadClipSounds(urls);
	}
	
	/**
	 * Fade out and unload all sounds except for those listed in urlStrings[].  The sounds listed in urlStrings[], if
	 * not already playing, are created as ClipTracks.  Note there is one gain setting and one loop flag for all of
	 * the tracks to be left behind and these flags are only used if the tracks are added by this call (i.e., the
	 * track was not already running).  Aditional interfaces may be needed later.
	 * 
	 * @param urlStrings_ set of urlStings to keep running or to start running if not already running as a comma-separated
	 *                     list. "" means leave no sounds running.
	 * @param duration_ duration over which tracks to be removed should be faded out and over which listed tracks
	 *                   should be faded in if not already running.
	 * @param gain_ gain setting for tracks desired but not yet running (0.0-1.0).
	 * @param loop_ should a new track to be loaded be looped?
	 */
	public void fadeOutAndUnloadSoundsExceptClips(String urlStrings_, long duration_, float gain_,  boolean loop_) {
		Log("SoundApplet fadeOutAndUnloadSoundsExceptClips " + urlStrings_ + " duration: " + duration_ + " gain: "
				+ gain_ + " looping: " + loop_);
		String[] urls;
		if (urlStrings_ == "") {
			urls = new String[1];
			urls[0] = "";
		} else {
			urls = urlStrings_.split(",");
		}
		m_trackManager.fadeOutAndUnloadSoundsExceptClips(urls, duration_, gain_, loop_);
	}
	
	//
	// *********************  Stream Interfaces     *********************************************
	//
	
	/**
	 * Load a single sound stream, specified by urlString_, from a URL containing a digitally-encoded
	 * sound.  This sound will be simulataneously transported and played using a small amount of temporary
	 * buffer memory.  The stream will use a small amount of memory for state information until explicitly
	 * unloaded.  This stream could represent a static digital sound file that is large enough to prevent
	 * loading as a clip (see above) or could represent a dynamic audio stream (such as internet radio).
	 * 
	 * See Track.java, ClipTrack.java, and StreamTrack.java for information on encodings.
	 * 
	 * @param urlString_ specification of URL for a sound clip.
	 */
	public void loadStreamSound(String urlString_) {
		Log("SoundApplet loadStreamSound " + urlString_);
		m_trackManager.loadStreamSound(urlString_);
	}
	
	/**
	 * Load a set of sound streams, specified by urlStrings_ as a comma-separated list of URLs
	 * identifying a set digitally-encoded sounds.  These sounds will be simulataneously transported
	 * and played using a small amount of temporary buffer memory.  Each stream will use a small
	 * amount of memory for state information until explicitly
	 * unloaded.  Each stream could represent a static digital sound file that is large enough to prevent
	 * loading as a clip (see above) or could represent a dynamic audio stream (such as internet radio).
	 * 
	 * See Track.java, ClipTrack.java, and StreamTrack.java for information on encodings.
	 * 
	 * @param urlStrings_ specification of URL for a sound clip.
	 */
	public void loadStreamSounds(String urlStrings_) {
		Log("SoundApplet loadStreamSounds " + urlStrings_);
		String[] urls = urlStrings_.split(",");
		m_trackManager.loadStreamSounds(urls);
	}
	
	/**
	 * Fade out and unload all sounds except for those listed in urlStrings[].  The sounds listed in urlStrings[], if
	 * not already playing, are created as StreamTracks.  Note there is one gain setting and one loop flag for all of
	 * the tracks to be left behind and these flags are only used if the tracks are added by this call (i.e., the
	 * track was not already running).  Aditional interfaces may be needed later.
	 * 
	 * @param urlStrings_ set of urlStings to keep running or to start running if not already running as a comma-separated
	 *                     list. "" means leave no streams running.
	 * @param duration_ duration over which tracks to be removed should be faded out and over which listed tracks
	 *                   should be faded in if not already running.
	 * @param gain_ gain setting for tracks desired but not yet running (0.0-1.0).
	 * @param loop_ should a new track to be loaded be looped?
	 */
	public void fadeOutAndUnloadSoundsExceptStreams(String urlStrings_, long duration_, float gain_,  boolean loop_) {
		Log("SoundApplet fadeOutAndUnloadSoundsExceptStreams " + urlStrings_ + " duration: " + duration_ + " gain: "
				+ gain_ + " looping: " + loop_);
		String[] urls;
		if (urlStrings_ == "") {
			urls = new String[1];
			urls[0] = "";
		} else {
			urls = urlStrings_.split(",");
		}
		m_trackManager.fadeOutAndUnloadSoundsExceptStreams(urls, duration_, gain_, loop_);
	}
	
	//
	// *****************  Generic Interfaces (all sampled audio track types)   ******************
	//
	
	/**
	 * Start playing the sound designated by urlString_.  Sound must have been previously loaded as a 
	 * clip or a stream.  If the sound file is of a finite length (i.e., it is not a continuous 
	 * stream such as an internet radio feed), it will play to the end and stop (won't loop).
	 * 
	 * @param urlString_ URL for sound to be played.
	 */
	public void playSound(String urlString_) {
		Log("SoundApplet playSound " + urlString_);
		m_trackManager.playSound(urlString_);
	}
	
	/**
	 * Start playing the sounds designated by urlStrings_.  Each sound must have been previously
	 * loaded as a clip or a stream.  Sounds of a finite length (i.e., not a continuous 
	 * stream such as an internet radio feed), will play to the end and stop (won't loop).
	 * 
	 * @param urlStrings_ comma-separated list of URLs for sounds to be played.
	 */
	public void playSounds(String urlStrings_) {
		Log("SoundApplet playSounds " + urlStrings_);
		String[] urls = urlStrings_.split(",");
		m_trackManager.playSounds(urls);
	}
	
	/**
	 * Start playing the sound designated by urlString_.  Sound must have been previously loaded as a 
	 * clip or a stream.  If the sound file is of a finite length (i.e., it is not a continuous 
	 * stream such as an internet radio feed), it will play to the end and then restart from the 
	 * beginning (loop!).
	 * 
	 * @param urlString_ URL for sound to be played.
	 */
	public void loopSound(String urlString_) {
		Log("SoundApplet loopSound " + urlString_);
		m_trackManager.loopSound(urlString_);
	}
	
	/**
	 * Start playing the sounds designated by urlStrings_.  Each sound must have been previously
	 * loaded as a clip or a stream.  Sounds of a finite length (i.e., not a continuous 
	 * stream such as an internet radio feed), will play to the end and then restart from the 
	 * beginning loop!).
	 * 
	 * @param urlStrings_ comma-separated list of URLs for sounds to be played.
	 */
	public void loopSounds(String urlStrings_) {
		Log("SoundApplet loopSounds " + urlStrings_);
		String[] urls = urlStrings_.split(",");
		m_trackManager.loopSounds(urls);
	}
		
	/**
	 * Stop playing the sound designated by urlString_.  The sound is not unloaded
	 * 
	 * @param urlString_ URL for sound to be played.
	 */
	public void stopSound(String urlString_) {
		Log("SoundApplet stopSound " + urlString_);
		m_trackManager.stopSound(urlString_);
	}
	
	/**
	 * Stop playing the sounds designated by urlStrings_.  The sounds are not unloaded.
	 * 
	 * @param urlStrings_ comma-separated list of URLs for sounds to be played.
	 */
	public void stopSounds(String urlStrings_) {
		Log("SoundApplet stopSounds " + urlStrings_);
		String[] urls = urlStrings_.split(",");
		m_trackManager.stopSounds(urls);
	}
	
	/**
	 * Unload the sound designated by urlString_ from memory.  Sound may currently be playing or stopped
	 * - it will be stopped if required.
	 * 
	 * @param urlString_ URL for sound to be played.
	 */
	public void unloadSound(String urlString_) {
		Log("SoundApplet unloadSound " + urlString_);
		m_trackManager.unloadSound(urlString_);
	}

	/**
	 * Unload the sounds designated by urlStrings_ from memory.  Sounds may currently be playing or stopped
	 * - it will be stopped if required.
	 * 
	 * @param urlStrings_ comma-separated list of URLs for sounds to be played.
	 */
	public void unloadSounds(String urlStrings_) {
		Log("SoundApplet unloadSounds " + urlStrings_);
		String[] urls = urlStrings_.split(",");
		m_trackManager.unloadSounds(urls);
	}

	/**
	 * Set the gain for the sound designated by urlString_.  Sound must have been previously loaded as a 
	 * clip or a stream but can be playing or stopped.  If stopped, the new gain setting will be stored
	 * and will apply when the sound is restarted.
	 * 
	 * @param urlString_ URL for sound to be played.
	 */
	public void setGain(String urlString_, float gain_) {
		Log("SoundApplet setGain " + urlString_ + " to " + gain_);
		m_trackManager.setGain(urlString_, gain_);
	}
	
	/**
	 * Set the gain for the sounds designated by urlStrings_ using the gain settings specified by gains_.
	 * Each sound must have been previously loaded as a clip or a stream but can be playing or stopped. 
	 * If stopped, the new gain setting will be stored and will apply when the sound is restarted.
	 * The URLs and gain settings are used as ordered pairs (i.e., gain1 applies to URL1 where URL1 is the
	 * first sound in the comma-separated list of sounds and gain1 is the first gain setting in the
	 * comma-separated list of settings, etc.).
	 * 
	 * @param urlStrings_ comma-separated list of URLs for sounds to be played.
	 * @param gains_ comma-separated list of gain settings (as numeric strings).
	 */
	public void setGains(String urlStrings_, String gains_) {
		String[] urls = urlStrings_.split(",");
		String[] gains = gains_.split(",");
		
		int arrayLength = gains.length;
		float[] floatGains = new float[arrayLength];
		for (int index=0; index < arrayLength; index++) {
			floatGains[index] = new Float(gains[index]);
			setGain(urls[index],floatGains[index]);
		}
	}

	/**
	 * Mute the specified sound.  Subsequent gain setting changes will be accepted,
	 * stored and ignored until this sound is unmuted.
	 * 
	 * @param urlString_ sound to be muted
	 */
	public void muteSound(String urlString_) {
		Log("SoundApplet muteSound " + urlString_);
		m_trackManager.muteSound(urlString_);
	}
	
	/**
	 * Unmute the specified sound.
	 * 
	 * @param urlString_ sound to be unmuted
	 */
	public void unmuteSound(String urlString_) {
		Log("SoundApplet unmuteSound " + urlString_);
		m_trackManager.unmuteSound(urlString_);
	}
	
	/**
	 * Fade the sound to the specified gain over the duration specified in milliseconds.  Fades
	 * from the current gain setting to the target gain specified (may fade up or down).
	 * 
	 * @param urlString_ sound to be faded
	 * @param gain_ target gain value (0.0 - 1.0)
	 * @param duration_ period over which to implement fade in milliseconds.
	 */
	public void fadeToGain(String urlString_, float gain_, long duration_) {
		Log("SoundApplet fadeToGain " + urlString_ + " gain: " + gain_ + " duration: " + duration_);
		m_trackManager.fadeToGain(urlString_, gain_, duration_);
	}
	
	/**
	 * Fade the specified sound out over the duration specified in milliseconds and then unload it.
	 * 
	 * @param urlString_ sound to be faded
	 * @param duration_ period over which to implement fade in milliseconds.
	 */
	public void fadeOutAndUnloadSound(String urlString_, long duration_) {
		Log("SoundApplet fadeOutAndUnloadSound " + urlString_ + " duration: " + duration_);
		m_trackManager.fadeOutAndUnloadSound(urlString_, duration_);
	}
	
	/**
	 * Fade the specified sounds out and then unload them.
	 * 
	 * @param urlStrings_ comma-separated list of clips to be faded
	 * @param duration_ period over which to implement fade in milliseconds.
	 */
	public void fadeOutAndUnloadSounds(String urlStrings_, long duration_) {
		Log("SoundApplet fadeOutAndUnloadSounds " + urlStrings_ + " duration: " + duration_);
		String[] urls = urlStrings_.split(",");
		m_trackManager.fadeOutAndUnloadSounds(urls, duration_);		
	}
	
	/**
	 * Manage the playing characteristics for a set of URLs specified in a single string.  This allows the
	 * SoundApplet to receive one call and instantiate all changes in one go.  The information for a single
	 * sound is seprated by "|" while individual parameters for a track (play flag, looping flag, pause flag,
	 * and gain setting are comma-separated for each URL.  i.e.
	 *  <song> = <url>,<play>,<loop>,<pause>,<gain>
	 *  <list> = <song>[|<song>[...]]
	 *  
	 * @param allInfo_ the string specifying the URLs and their associated attributes.
	 */
	public void manageMultipleTracks(String allInfo_) {
		Log("SoundApplet manageMultipleTracks ");
		
		// Split for each track
		String[] tracksInfo = allInfo_.split("\\|");
		for(int loop=0; loop<tracksInfo.length; ++loop) {
			Log("  " + tracksInfo[loop]);
			
			String[] attributes = tracksInfo[loop].split(",");
			if (5 == attributes.length) {
				boolean playing = Boolean.parseBoolean(attributes[1]);
				boolean looping = Boolean.parseBoolean(attributes[2]);
				boolean pause = Boolean.parseBoolean(attributes[3]);
				float gain = Float.parseFloat(attributes[4]);
				
				m_trackManager.manageTrack(attributes[0],playing,looping,pause,gain);
			} else {
				// Disregard incomplete information
				Log("SoundApplet manageMultipleTracks dropped: " + tracksInfo[loop]);
			}
		}
		
	} // end manageMultipleTracks()
	
	/*
	 * - - -   M I D I - S P E C I F I C   R O U T I N E S   - - - 
	 * 
	 * start of MIDI-specific routines.  For now, just pass URL through to Midi Manager since
	 * there is only one MIDI track loaded concurrently.  Probably have to revisit this to
	 * co-ordinate the playback of audio and MIDI.
	 */

	/**
	 * Load a MIDI sound, specified by urlString_, identifying a URL for a Standard Midi File.
	 * 
	 * @param urlString_ URL specifications for MIDI file.
	 */
	public void loadMidiSound(String urlString_) {
		Log("SoundApplet loadMidiSound " + urlString_);
		m_midiManager.loadMidiSequence(urlString_);
	}
	
	/**
	 * Unload the previously loaded MIDI file and clean-up.
	 * 
	 * See <link>loadMidiSound</link>
	 */
	public void unloadMidiSound() {
		Log("SoundApplet unloadMidiSound");
		m_midiManager.unloadMidiSequence();
	}
	
	/**
	 * Play the previously loaded MIDI file.
	 * 
	 * See <link>loadMidiSound</link>.
	 */
	public void playMidiSound() {
		Log("SoundApplet playMidiSound");
		m_midiManager.playSequence();
	}
	
	/**
	 * Play the loaded MIDI file.  Looping it indefinitely.
	 */
	public void loopMidiSound() {
		Log("SoundApplet loopMidiSound");
		m_midiManager.loopSequence();
	}
	
	/**
	 * Stop the previously loaded MIDI file.  The MIDI sequence is stopped but
	 * is not unloaded.  So this is indistinguishable from a "pause" operation.
	 */
	public void stopMidiSound() {
		Log("SoundApplet stopMidiSound");
		m_midiManager.stopSequence();
	}
	
	/**
	 * Mute the currently loaded Midi sequence.
	 */
	public void muteMidiSound() {
		Log("SoundApplet muteMidiSound");
		m_midiManager.setMutingForAllSounds(true);
	}
	
	/**
	 * Mute an individual track in the currently loaded Midi sequence.
	 * 
	 * @param track_ the track number to be muted.  Typically used for 
	 * type 1 (multitrack) Standard MIDI Files (in which case the track
	 * numbers are 1 based since Track 0 stores common parameters for
	 * the entire MIDI composition).  This could however also be used
	 * to mute the sole track in a type 0 SMF (track 0 contains the entire
	 * composition in this case) but <code>muteMidSound</code>
	 * is the preferred method for this operation.
	 */
	public void muteMidiTrack(int track_) {
		Log("SoundApplet muteMidiTrack " + track_);
		m_midiManager.setMutingForTrack(track_, true);		
	} // unmuteMidiTrack()

	/**
	 * Unmute the currently loaded Midi sequence.
	 */
	public void unmuteMidiSound() {
		Log("SoundApplet unmuteMidiSound");
		m_midiManager.setMutingForAllSounds(false);
	}
	
	/**
	 * Unmute an individual track in the currently loaded Midi sequence.  See
	 * <code>muteMidiTrack</code> for the semantics of the specified track number
	 * for both type 0 and type 1 MIDI files.
	 * 
	 * @param track_ track number to be muted.
	 */
	public void unmuteMidiTrack(int track_) {
		Log("SoundApplet unmuteMidiTrack " + track_);
		m_midiManager.setMutingForTrack(track_, false);		
	} // unmuteMidiTrack()
	
	/**
	 * Set the gain scale multiplier as a percentage for the Midi sequence.  This is used to 
	 * scale all master volume configurations in the current sequence.
	 * 
	 * Note that gain scaling is done on channels in the synthesizer while muting is done
	 * on tracks in the sequencer - muting individual instruments requires that they either be
	 * separated into tracks in the sequence and muted in the sequencer or separated into channels
	 * in the synthesizer and muted by setting their volume scale to zero.
	 * 
	 * @param gainScale_ integer percentage (0..100) gain scale multiplier value
	 */
	public void setMidiMasterGainScaleMultiplier(int gainScale_) {
		Log("SoundApplet setMidiMasterGainScaleMultiplier: " + gainScale_ + "%");
		m_midiManager.setMasterGainScaleMultiplier(gainScale_);
	}

	/**
	 * Set the gain scale multiplier as a percentage for an individual channel in the current
	 * Midi sequence.  This is used to scale the channel's volume configuration in the current
	 * sequence.  
	 * 
	 * Note that gain scaling is done on channels in the synthesizer while muting is done
	 * on tracks in the sequencer - muting individual instruments requires that they either be
	 * separated into tracks in the sequence and muted in the sequencer or separated into channels
	 * in the synthesizer and muted by setting their volume scale to zero.
	 * 
	 * @param channel_ integer channel number (0..15)
	 * @param gainScale_ integer percentage (0..100) gain scale multiplier value
	 */
	public void setMidiChannelGainScaleMultiplier(int channel_, int gainScale_) {
		Log("SoundApplet setMidiChannelGainScaleMultiplier: " + channel_ + " to: " + gainScale_ + "%");
		m_midiManager.setChannelGainScaleMultiplier(channel_, gainScale_);
	}
	
	public void setMidiNoteTranspose(int transpose_) {
		Log("SoundApplet setMidiNoteTranspose to: " + transpose_);
		m_midiManager.setNoteTranspose(transpose_);
	}
	
	/*
	 * D e b u g   d i s p l a y   r o u t i n e s
	 */
	
	/**
	 * Display status of sound applet on the java console.  Non-command-path display routine
	 * used to periodically dump the current state of the applet.
	 */
	public void displayAppletState() {
		System.out.println(">>>>>>>>>>> Start of Applet State >>>>>>>>>>>");
		if (null != m_trackManager) {
			m_trackManager.displayAppletState();
		}
		if (null != m_midiManager) {
			m_midiManager.displayAppletState();
		}
		System.out.println("<<<<<<<<<<<< End of Applet State <<<<<<<<<<<<");
	} // displayAppletState()
	
	/*
	 * R u n - t i m e   D e b u g   C o n f i g   R o u t i n e s
	 */
	
	/**
	 * Enable command-path debug logging within the sound applet.  Only the top-level
	 * SoundApplet API is affected by this (i.e., this class).
	 */
	public void enableDebugLogging() {
		m_debugLogging = true;
		System.out.println("SoundApplet: enable debug logging");
	} // enableDebugLogging()

	/**
	 * Disable command-path debug logging within the sound applet.  Only the top-level
	 * SoundApplet API is affected by this (i.e., this class).
	 */
	public void disableDebugLogging() {
		m_debugLogging = false;
		System.out.println("SoundApplet: disable debug logging");
	} // disableDebugLogging()
	
	/**
	 * Return the state of debug logging currently configured for the sound applet.
	 * @return true if debug logging is enabled and false otherwise.
	 */
	public boolean getDebugLoggingSetting() {
		return(m_debugLogging);
	} // getDebugLoggingSetting

} // end class SoundApplet
