package paulscode.sound;

import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;
import java.util.Set;
import javax.sound.sampled.AudioFormat;

/**
 * The SoundSystem class is the core class for the SoundSystem library. It is
 * capable of interfacing with external sound library and codec library
 * pluggins. This core class is stripped down to give it a smaller memory
 * footprint and to make it more customizable. This library was created to
 * provide a simple, common interface to a variety of 3rd-party sound and codec
 * libraries, and to simplify switching between them on the fly. If no external
 * pluggins are loaded, this core class by itself is only capable of playing
 * MIDI files. Specific implementations (such as SoundSystemJPCT) will extend
 * this core class. They will automatically link with popular external pluggins
 * and provide extra methods for ease of use. There should be only one instance
 * of this class in any program! The SoundSystem can be constructed by defining
 * which sound library to use, or by allowing SoundSystem to perform its own
 * library compatibility checking. See {@link paulscode.sound.SoundSystemConfig
 * SoundSystemConfig} for information about changing default settings and
 * linking with external pluggins. <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 SoundSystem {
	/**
	 * 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;

	/**
	 * Processes status messages, warnings, and error messages.
	 */
	protected SoundSystemLogger logger;

	/**
	 * Handle to the active sound library.
	 */
	protected Library soundLibrary;

	/**
	 * List of queued commands to perform.
	 */
	protected List<CommandObject> commandQueue;

	/**
	 * Used internally by SoundSystem to keep track of play/pause/stop/rewind
	 * commands. This prevents source management (culling and activating) from being
	 * adversely affected by the quickPlay, quickStream, and backgroundMusic
	 * methods.
	 */
	private List<CommandObject> sourcePlayList;

	/**
	 * Processes queued commands in the background.
	 */
	protected CommandThread commandThread;

	/**
	 * Generates random numbers.
	 */
	public Random randomNumberGenerator;

	/**
	 * Name of this class.
	 */
	protected String className = "SoundSystem";

	/**
	 * Indicates the currently loaded sound-library, or null if none.
	 */
	private static Class currentLibrary = null;

	/**
	 * Becomes true when the sound library has been initialized.
	 */
	private static boolean initialized = false;

	/**
	 * Indicates the last exception that was thrown.
	 */
	private static SoundSystemException lastException = null;

	/**
	 * Constructor: Create the sound system using the default library. If the
	 * default library is not compatible, another library type will be loaded
	 * instead, in the order of library preference. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for information
	 * about sound library types.
	 */
	public SoundSystem() {
		// create the message logger:
		logger = SoundSystemConfig.getLogger();
		// if the user didn't create one, then do it now:
		if (logger == null) {
			logger = new SoundSystemLogger();
			SoundSystemConfig.setLogger(logger);
		}

		linkDefaultLibrariesAndCodecs();

		LinkedList<Class> libraries = SoundSystemConfig.getLibraries();

		if (libraries != null) {
			ListIterator<Class> i = libraries.listIterator();
			Class c;
			while (i.hasNext()) {
				c = i.next();
				try {
					init(c);
					return;
				} catch (SoundSystemException sse) {
					logger.printExceptionMessage(sse, 1);
				}
			}
		}
		try {
			init(Library.class);
			return;
		} catch (SoundSystemException sse) {
			logger.printExceptionMessage(sse, 1);
		}
	}

	/**
	 * Constructor: Create the sound system using the specified library.
	 * 
	 * @param libraryClass Library to use. See
	 *                     {@link paulscode.sound.SoundSystemConfig
	 *                     SoundSystemConfig} for information about chosing a sound
	 *                     library.
	 */
	public SoundSystem(Class libraryClass) throws SoundSystemException {
		// create the message logger:
		logger = SoundSystemConfig.getLogger();
		// if the user didn't create one, then do it now:
		if (logger == null) {
			logger = new SoundSystemLogger();
			SoundSystemConfig.setLogger(logger);
		}
		linkDefaultLibrariesAndCodecs();

		init(libraryClass);
	}

	/**
	 * Links with any default libraries or codecs should be made in this method.
	 * This method is empty in the core SoundSystem class, and should be overriden
	 * by classes which extend SoundSystem. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for information
	 * about linking with sound libraries and codecs.
	 */
	protected void linkDefaultLibrariesAndCodecs() {
	}

	/**
	 * Loads the message logger, initializes the specified sound library, and starts
	 * the command thread. Also instantiates the random number generator and the
	 * command queue.
	 * 
	 * @param libraryClass Library to initialize. See
	 *                     {@link paulscode.sound.SoundSystemConfig
	 *                     SoundSystemConfig} for information about chosing a sound
	 *                     library.
	 */
	protected void init(Class libraryClass) throws SoundSystemException {
		message("", 0);
		message("Starting up " + className + " version " + SoundSystem.class.getPackage().getImplementationVersion()
				+ "...", 0);

		// create the random number generator:
		randomNumberGenerator = new Random();
		// create the command queue:
		commandQueue = new LinkedList<CommandObject>();
		// create the working source playlist:
		sourcePlayList = new LinkedList<CommandObject>();

		// Instantiate and start the Command Processer thread:
		commandThread = new CommandThread(this); // Gets a SoundSystem handle
		commandThread.start();

		snooze(200);

		newLibrary(libraryClass);
		message("", 0);
	}

	/**
	 * Ends the command thread, shuts down the sound system, and removes references
	 * to all instantiated objects.
	 */
	public void cleanup() {
		boolean killException = false;
		message("", 0);
		message(className + " shutting down...", 0);

		// End the command thread:
		try {
			commandThread.kill(); // end the command processor loop.
			commandThread.interrupt(); // wake the thread up so it can end.
		} catch (Exception e) {
			killException = true;
		}

		if (!killException) {
			// wait up to 5 seconds for command thread to end:
			for (int i = 0; i < 50; i++) {
				if (!commandThread.alive())
					break;
				snooze(100);
			}
		}

		// Let user know if there was a problem ending the command thread
		if (killException || commandThread.alive()) {
			errorMessage("Command thread did not die!", 0);
			message("Ignoring errors... continuing clean-up.", 0);
		}

		initialized(SET, false);
		currentLibrary(SET, null);
		try {
			// Stop all sources and shut down the sound library:
			if (soundLibrary != null)
				soundLibrary.cleanup();
		} catch (Exception e) {
			errorMessage("Problem during Library.cleanup()!", 0);
			message("Ignoring errors... continuing clean-up.", 0);
		}

		try {
			// remove any queued commands:
			if (commandQueue != null)
				commandQueue.clear();
		} catch (Exception e) {
			errorMessage("Unable to clear the command queue!", 0);
			message("Ignoring errors... continuing clean-up.", 0);
		}

		try {
			// empty the source management list:
			if (sourcePlayList != null)
				sourcePlayList.clear();
		} catch (Exception e) {
			errorMessage("Unable to clear the source management list!", 0);
			message("Ignoring errors... continuing clean-up.", 0);
		}

		// Remove references to all instantiated objects:
		randomNumberGenerator = null;
		soundLibrary = null;
		commandQueue = null;
		sourcePlayList = null;
		commandThread = null;

		importantMessage("Author: Paul Lamb, www.paulscode.com", 1);
		message("", 0);
	}

	/**
	 * Wakes up the Command Thread to process commands. This method should be used
	 * if there is a need to call methods 'ManageSources' and 'CommandQueue'
	 * externally. In most cases, this method will not be needed, since SoundSystem
	 * automatically wakes the Command Thread every time commands are placed into
	 * either the ManageSources queue or CommandQueue to be processed.
	 */
	public void interruptCommandThread() {
		if (commandThread == null) {
			errorMessage("Command Thread null in method " + "'interruptCommandThread'", 0);
			return;
		}
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * Pre-loads a sound into memory. The file may either be located within the JAR
	 * or at an online location. If the file is online, filename must begin with
	 * "http://", since that is how SoundSystem recognizes URL's. If the file is
	 * located within the compiled JAR, the package in which sound files are located
	 * may be set by calling SoundSystemConfig.setSoundFilesPackage().
	 * 
	 * @param filename Filename of the sound file to load.
	 */
	public void loadSound(String filename) {
		// Queue a command to load the sound file:
		CommandQueue(new CommandObject(CommandObject.LOAD_SOUND, new FilenameURL(filename)));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * Pre-loads a sound specified by the given URL into memory. The second
	 * parameter 'identifier' should look like a filename, and it must have the
	 * correct extension so SoundSystem knows what codec to use for the file
	 * referenced by the URL instance.
	 * 
	 * @param url        URL handle to the sound file to load.
	 * @param identifier Filename/identifier of the file referenced by the URL.
	 */
	public void loadSound(URL url, String identifier) {
		// Queue a command to load the sound file from a URL:
		CommandQueue(new CommandObject(CommandObject.LOAD_SOUND, new FilenameURL(url, identifier)));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * Saves raw PCM audio data in the specified audio format, under the specified
	 * identifier. This identifier can be later used in place of 'filename'
	 * parameters to reference the sample data.
	 * 
	 * @param data       The sample data
	 * @param format     Format the sample data is stored in
	 * @param identifier What to call the sample.
	 */
	public void loadSound(byte[] data, AudioFormat format, String identifier) {
		// Queue a command to load the sound file from a URL:
		CommandQueue(new CommandObject(CommandObject.LOAD_DATA, identifier, new SoundBuffer(data, format)));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * Removes a pre-loaded sound from memory. This is a good method to use for
	 * freeing up memory after a large sound file is no longer needed. NOTE: the
	 * source will remain in memory after calling this method as long as the sound
	 * is attached to an existing source. When calling this method, calls should
	 * also be made to method removeSource( String ) for all sources which this
	 * sound is bound to.
	 * 
	 * @param filename Filename/identifier of the sound file to unload.
	 */
	public void unloadSound(String filename) {
		// Queue a command to unload the sound file:
		CommandQueue(new CommandObject(CommandObject.UNLOAD_SOUND, filename));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * If the specified source is a streaming source or MIDI source, this method
	 * queues up the next sound to play when the previous playback ends. The file
	 * may either be located within the JAR or at an online location. If the file is
	 * online, filename must begin with "http://", since that is how SoundSystem
	 * recognizes URL paths. If the file is located within the compiled JAR, the
	 * package in which sound files are located may be set by calling
	 * SoundSystemConfig.setSoundFilesPackage(). This method has no effect on
	 * non-streaming sources.
	 * 
	 * @param sourcename Source identifier.
	 * @param filename   Name of the sound file to play next.
	 */
	public void queueSound(String sourcename, String filename) {
		// Queue a command to queue the sound:
		CommandQueue(new CommandObject(CommandObject.QUEUE_SOUND, sourcename, new FilenameURL(filename)));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * If the specified source is a streaming source or MIDI source, this method
	 * queues up the next sound to play when the previous playback ends. The third
	 * parameter 'identifier' should look like a filename, and it must have the
	 * correct extension so SoundSystem knows what codec to use for the file
	 * referenced by the URL instance. This method has no effect on non-streaming
	 * sources.
	 * 
	 * @param sourcename Source identifier.
	 * @param url        URL handle to the sound file to load.
	 * @param identifier Filename/identifier of the file referenced by the URL.
	 */
	public void queueSound(String sourcename, URL url, String identifier) {
		// Queue a command to queue the sound:
		CommandQueue(new CommandObject(CommandObject.QUEUE_SOUND, sourcename, new FilenameURL(url, identifier)));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * Removes the first occurrence of the specified filename/identifier from the
	 * specified source's list of sounds to play when previous playback ends. This
	 * method has no effect on non-streaming sources.
	 * 
	 * @param sourcename Source identifier.
	 * @param filename   Filename/identifier of the sound file to play next.
	 */
	public void dequeueSound(String sourcename, String filename) {
		// Queue a command to dequeue the sound:
		CommandQueue(new CommandObject(CommandObject.DEQUEUE_SOUND, sourcename, filename));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * Fades out the volume of whatever the specified source is currently playing,
	 * then begins playing the specified file at the source's previously assigned
	 * volume level. The file may either be located within the JAR or at an online
	 * location. If the file is online, filename must begin with "http://", since
	 * that is how SoundSystem recognizes URL paths. If the file is located within
	 * the compiled JAR, the package in which sound files are located may be set by
	 * calling SoundSystemConfig.setSoundFilesPackage(). If the filename parameter
	 * is null or empty, the specified source 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 specified source's list of queued sounds
	 * that would have played next when the current sound finished playing. This
	 * method may only be used for streaming and MIDI sources.
	 * 
	 * @param sourcename Name of the source to fade out.
	 * @param filename   Name of a sound file to play next, or null for none.
	 * @param milis      Number of miliseconds the fadeout should take.
	 */
	public void fadeOut(String sourcename, String filename, long milis) {
		FilenameURL fu = null;
		if (filename != null)
			fu = new FilenameURL(filename);
		// Queue a command to fade out:
		CommandQueue(new CommandObject(CommandObject.FADE_OUT, sourcename, fu, milis));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * Fades out the volume of whatever the specified source is currently playing,
	 * then begins playing the specified file at the source's previously assigned
	 * volume level. If the url parameter is null or empty, the specified source
	 * will simply fade out and stop. The third parameter 'identifier' should look
	 * like a filename, and it must have the correct extension so SoundSystem knows
	 * what codec to use for the file referenced by the URL instance. The
	 * miliseconds parameter must be non-negative or zero. This method will remove
	 * anything that is currently in the specified source's list of queued sounds
	 * that would have played next when the current sound finished playing. This
	 * method may only be used for streaming and MIDI sources.
	 * 
	 * @param sourcename Name of the source to fade out.
	 * @param url        URL handle to the sound file to play next, or null for
	 *                   none.
	 * @param identifier Filename/identifier of the file referenced by the URL.
	 * @param milis      Number of miliseconds the fadeout should take.
	 */
	public void fadeOut(String sourcename, URL url, String identifier, long milis) {
		FilenameURL fu = null;
		if (url != null && identifier != null)
			fu = new FilenameURL(url, identifier);
		// Queue a command to fade out:
		CommandQueue(new CommandObject(CommandObject.FADE_OUT, sourcename, fu, milis));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * Fades out the volume of whatever the specified source is currently playing,
	 * then fades the volume back in playing the specified filename. Final volume
	 * after fade-in completes will be equal to the source's previously assigned
	 * volume level. The filename parameter may not be null or empty. The file may
	 * either be located within the JAR or at an online location. If the file is
	 * online, filename must begin with "http://", since that is how SoundSystem
	 * recognizes URL paths. If the file is located within the compiled JAR, the
	 * package in which sound files are located may be set by calling
	 * SoundSystemConfig.setSoundFilesPackage(). The miliseconds parameters must be
	 * non-negative or zero. This method will remove anything that is currently in
	 * the specified source's list of queued sounds that would have played next when
	 * the current sound finished playing. This method may only be used for
	 * streaming and MIDI sources.
	 * 
	 * @param sourcename Name of the source to fade out/in.
	 * @param filename   Name of a sound 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(String sourcename, String filename, long milisOut, long milisIn) {
		// Queue a command to load the sound file:
		CommandQueue(
				new CommandObject(CommandObject.FADE_OUT_IN, sourcename, new FilenameURL(filename), milisOut, milisIn));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * Fades out the volume of whatever the specified source is currently playing,
	 * then fades the volume back in playing the specified file. Final volume after
	 * fade-in completes will be equal to the source's previously assigned volume
	 * level. The url parameter may not be null or empty. The third parameter
	 * 'identifier' should look like a filename, and it must have the correct
	 * extension so SoundSystem knows what codec to use for the file referenced by
	 * the URL instance. The miliseconds parameters must be non-negative or zero.
	 * This method will remove anything that is currently in the specified source's
	 * list of queued sounds that would have played next when the current sound
	 * finished playing. This method may only be used for streaming and MIDI
	 * sources.
	 * 
	 * @param sourcename Name of the source to fade out/in.
	 * @param url        URL handle to the sound file to play next.
	 * @param identifier Filename/identifier of the file referenced by the URL.
	 * @param milisOut   Number of miliseconds the fadeout should take.
	 * @param milisIn    Number of miliseconds the fadein should take.
	 */
	public void fadeOutIn(String sourcename, URL url, String identifier, long milisOut, long milisIn) {
		// Queue a command to load the sound file:
		CommandQueue(new CommandObject(CommandObject.FADE_OUT_IN, sourcename, new FilenameURL(url, identifier),
				milisOut, milisIn));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * Makes sure the current volume levels of streaming sources and MIDI are
	 * correct. This method is designed to help reduce the "jerky" fading behavior
	 * that happens when using some library and codec pluggins (such as
	 * LibraryJavaSound and CodecJOrbis). This method has no effect on normal
	 * "non-streaming" sources. It would normally be called somewhere in the main
	 * "game loop". IMPORTANT: To optimize frame-rates, do not call this method for
	 * every frame. It is better to just call this method at some acceptable
	 * "granularity" (play around with different granularities to find what sounds
	 * acceptable for a particular situation).
	 */
	public void checkFadeVolumes() {
		// Queue a command to load check fading source volumes:
		CommandQueue(new CommandObject(CommandObject.CHECK_FADE_VOLUMES));
		// Wake the command thread to process commands:
		commandThread.interrupt();
	}

	/**
	 * Creates a new permanant, streaming, priority source with zero attenuation.
	 * The file may either be located within the JAR or at an online location. If
	 * the file is online, filename must begin with "http://", since that is how
	 * SoundSystem recognizes URL paths. If the file is located within the compiled
	 * JAR, the package in which sound files are located may be set by calling
	 * SoundSystemConfig.setSoundFilesPackage().
	 * 
	 * @param sourcename A unique identifier for this source. Two sources may not
	 *                   use the same sourcename.
	 * @param filename   Filename of the sound file to stream at this source.
	 * @param toLoop     Should this source loop, or play only once.
	 */
	public void backgroundMusic(String sourcename, String filename, boolean toLoop) {
		// Queue a command to quick stream a new source:
		CommandQueue(new CommandObject(CommandObject.QUICK_PLAY, true, true, toLoop, sourcename,
				new FilenameURL(filename), 0, 0, 0, SoundSystemConfig.ATTENUATION_NONE, 0, false));
		CommandQueue(new CommandObject(CommandObject.PLAY, sourcename));

		commandThread.interrupt();
	}

	/**
	 * Creates a new permanant, streaming, priority source with zero attenuation.
	 * The third parameter 'identifier' should look like a filename, and it must
	 * have the correct extension so SoundSystem knows what codec to use for the
	 * file referenced by the URL instance.
	 * 
	 * @param sourcename A unique identifier for this source. Two sources may not
	 *                   use the same sourcename.
	 * @param url        URL handle to the sound file to stream at this source.
	 * @param identifier Filename/identifier of the file referenced by the URL.
	 * @param toLoop     Should this source loop, or play only once.
	 */
	public void backgroundMusic(String sourcename, URL url, String identifier, boolean toLoop) {
		// Queue a command to quick stream a new source:
		CommandQueue(new CommandObject(CommandObject.QUICK_PLAY, true, true, toLoop, sourcename,
				new FilenameURL(url, identifier), 0, 0, 0, SoundSystemConfig.ATTENUATION_NONE, 0, false));
		CommandQueue(new CommandObject(CommandObject.PLAY, sourcename));

		commandThread.interrupt();
	}

	/**
	 * Creates a new non-streaming source. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Attenuation, fade distance, and rolloff factor.
	 * 
	 * @param priority   Setting this to true will prevent other sounds from
	 *                   overriding this one.
	 * @param sourcename A unique identifier for this source. Two sources may not
	 *                   use the same sourcename.
	 * @param filename   Filename/identifier of the sound file to play at this
	 *                   source.
	 * @param toLoop     Should this source loop, or play only once.
	 * @param x          X position for this source.
	 * @param y          Y position for this source.
	 * @param z          Z position for this source.
	 * @param attmodel   Attenuation model to use.
	 * @param distOrRoll Either the fading distance or rolloff factor, depending on
	 *                   the value of "attmodel".
	 */
	public void newSource(boolean priority, String sourcename, String filename, boolean toLoop, float x, float y,
			float z, int attmodel, float distOrRoll) {
		CommandQueue(new CommandObject(CommandObject.NEW_SOURCE, priority, false, toLoop, sourcename,
				new FilenameURL(filename), x, y, z, attmodel, distOrRoll));
		commandThread.interrupt();
	}

	/**
	 * Creates a new non-streaming source. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Attenuation, fade distance, and rolloff factor.
	 * 
	 * @param priority   Setting this to true will prevent other sounds from
	 *                   overriding this one.
	 * @param sourcename A unique identifier for this source. Two sources may not
	 *                   use the same sourcename.
	 * @param url        URL handle to the sound file to stream at this source.
	 * @param identifier Filename/identifier of the file referenced by the URL.
	 * @param toLoop     Should this source loop, or play only once.
	 * @param x          X position for this source.
	 * @param y          Y position for this source.
	 * @param z          Z position for this source.
	 * @param attmodel   Attenuation model to use.
	 * @param distOrRoll Either the fading distance or rolloff factor, depending on
	 *                   the value of "attmodel".
	 */
	public void newSource(boolean priority, String sourcename, URL url, String identifier, boolean toLoop, float x,
			float y, float z, int attmodel, float distOrRoll) {
		CommandQueue(new CommandObject(CommandObject.NEW_SOURCE, priority, false, toLoop, sourcename,
				new FilenameURL(url, identifier), x, y, z, attmodel, distOrRoll));
		commandThread.interrupt();
	}

	/**
	 * Creates a new streaming source. The file may either be located within the JAR
	 * or at an online location. If the file is online, filename must begin with
	 * "http://", since that is how SoundSystem recognizes URL paths. If the file is
	 * located within the compiled JAR, the package in which sound files are located
	 * may be set by calling SoundSystemConfig.setSoundFilesPackage().
	 * 
	 * @param priority   Setting this to true will prevent other sounds from
	 *                   overriding this one.
	 * @param sourcename A unique identifier for this source. Two sources may not
	 *                   use the same sourcename.
	 * @param filename   The filename of the sound file to play at this source.
	 * @param toLoop     Should this source loop, or play only once.
	 * @param x          X position for this source.
	 * @param y          Y position for this source.
	 * @param z          Z position for this source.
	 * @param attmodel   Attenuation model to use.
	 * @param distOrRoll Either the fading distance or rolloff factor, depending on
	 *                   the value of "attmodel".
	 */
	public void newStreamingSource(boolean priority, String sourcename, String filename, boolean toLoop, float x,
			float y, float z, int attmodel, float distOrRoll) {
		CommandQueue(new CommandObject(CommandObject.NEW_SOURCE, priority, true, toLoop, sourcename,
				new FilenameURL(filename), x, y, z, attmodel, distOrRoll));
		commandThread.interrupt();
	}

	/**
	 * Creates a new streaming source. The fourth parameter 'identifier' should look
	 * like a filename, and it must have the correct extension so SoundSystem knows
	 * what codec to use for the file referenced by the URL instance.
	 * 
	 * @param priority   Setting this to true will prevent other sounds from
	 *                   overriding this one.
	 * @param sourcename A unique identifier for this source. Two sources may not
	 *                   use the same sourcename.
	 * @param url        URL handle to the sound file to stream at this source.
	 * @param identifier Filename/identifier of the file referenced by the URL.
	 * @param toLoop     Should this source loop, or play only once.
	 * @param x          X position for this source.
	 * @param y          Y position for this source.
	 * @param z          Z position for this source.
	 * @param attmodel   Attenuation model to use.
	 * @param distOrRoll Either the fading distance or rolloff factor, depending on
	 *                   the value of "attmodel".
	 */
	public void newStreamingSource(boolean priority, String sourcename, URL url, String identifier, boolean toLoop,
			float x, float y, float z, int attmodel, float distOrRoll) {
		CommandQueue(new CommandObject(CommandObject.NEW_SOURCE, priority, true, toLoop, sourcename,
				new FilenameURL(url, identifier), x, y, z, attmodel, distOrRoll));
		commandThread.interrupt();
	}

	/**
	 * Opens a direct line for streaming audio data. This method creates a new
	 * streaming source to play the data at. The resulting streaming source can be
	 * manipulated the same as any other streaming source. Raw data can be sent to
	 * the new streaming source using the feedRawAudioData() method.
	 * 
	 * @param audioFormat Format that the data will be in.
	 * @param priority    Setting this to true will prevent other sounds from
	 *                    overriding this one.
	 * @param sourcename  A unique identifier for this source. Two sources may not
	 *                    use the same sourcename.
	 * @param x           X position for this source.
	 * @param y           Y position for this source.
	 * @param z           Z position for this source.
	 * @param attModel    Attenuation model to use.
	 * @param distOrRoll  Either the fading distance or rolloff factor, depending on
	 *                    the value of "attmodel".
	 */
	public void rawDataStream(AudioFormat audioFormat, boolean priority, String sourcename, float x, float y, float z,
			int attModel, float distOrRoll) {
		CommandQueue(new CommandObject(CommandObject.RAW_DATA_STREAM, audioFormat, priority, sourcename, x, y, z,
				attModel, distOrRoll));
		commandThread.interrupt();
	}

	/**
	 * Creates a temporary source and plays it. After the source finishes playing,
	 * it is removed. Returns a randomly generated name for the new source. NOTE: to
	 * make a source created by this method permanant, call the setActive() method
	 * using the return value for sourcename.
	 * 
	 * @param priority   Setting this to true will prevent other sounds from
	 *                   overriding this one.
	 * @param filename   Filename/identifier of the sound file to play at this
	 *                   source.
	 * @param toLoop     Should this source loop, or play only once.
	 * @param x          X position for this source.
	 * @param y          Y position for this source.
	 * @param z          Z position for this source.
	 * @param attmodel   Attenuation model to use.
	 * @param distOrRoll Either the fading distance or rolloff factor, depending on
	 *                   the value of "attmodel".
	 * @return The new sorce's name.
	 */
	public String quickPlay(boolean priority, String filename, boolean toLoop, float x, float y, float z, int attmodel,
			float distOrRoll) {
		// generate a random name for this source:
		String sourcename = "Source_" + randomNumberGenerator.nextInt() + "_" + randomNumberGenerator.nextInt();

		// Queue a command to quick play this new source:
		CommandQueue(new CommandObject(CommandObject.QUICK_PLAY, priority, false, toLoop, sourcename,
				new FilenameURL(filename), x, y, z, attmodel, distOrRoll, true));
		CommandQueue(new CommandObject(CommandObject.PLAY, sourcename));
		// Wake the command thread to process commands:
		commandThread.interrupt();

		// return the new source name.
		return sourcename;
	}

	/**
	 * Creates a temporary source and plays it. After the source finishes playing,
	 * it is removed. Returns a randomly generated name for the new source. NOTE: to
	 * make a source created by this method permanant, call the setActive() method
	 * using the return value for sourcename.
	 * 
	 * @param priority   Setting this to true will prevent other sounds from
	 *                   overriding this one.
	 * @param url        URL handle to the sound file to stream at this source.
	 * @param identifier Filename/identifier of the file referenced by the URL.
	 * @param toLoop     Should this source loop, or play only once.
	 * @param x          X position for this source.
	 * @param y          Y position for this source.
	 * @param z          Z position for this source.
	 * @param attmodel   Attenuation model to use.
	 * @param distOrRoll Either the fading distance or rolloff factor, depending on
	 *                   the value of "attmodel".
	 * @return The new sorce's name.
	 */
	public String quickPlay(boolean priority, URL url, String identifier, boolean toLoop, float x, float y, float z,
			int attmodel, float distOrRoll) {
		// generate a random name for this source:
		String sourcename = "Source_" + randomNumberGenerator.nextInt() + "_" + randomNumberGenerator.nextInt();

		// Queue a command to quick play this new source:
		CommandQueue(new CommandObject(CommandObject.QUICK_PLAY, priority, false, toLoop, sourcename,
				new FilenameURL(url, identifier), x, y, z, attmodel, distOrRoll, true));
		CommandQueue(new CommandObject(CommandObject.PLAY, sourcename));
		// Wake the command thread to process commands:
		commandThread.interrupt();

		// return the new source name.
		return sourcename;
	}

	/**
	 * Creates a temporary source and streams it. After the source finishes playing,
	 * it is removed. The file may either be located within the JAR or at an online
	 * location. If the file is online, filename must begin with "http://", since
	 * that is how SoundSystem recognizes URL paths. If the file is located within
	 * the compiled JAR, the package in which sound files are located may be set by
	 * calling SoundSystemConfig.setSoundFilesPackage(). Returns a randomly
	 * generated name for the new source. NOTE: to make a source created by this
	 * method permanant, call the setActive() method using the return value for
	 * sourcename.
	 * 
	 * @param priority   Setting this to true will prevent other sounds from
	 *                   overriding this one.
	 * @param filename   Filename of the sound file to stream at this source.
	 * @param toLoop     Should this source loop, or play only once.
	 * @param x          X position for this source.
	 * @param y          Y position for this source.
	 * @param z          Z position for this source.
	 * @param attmodel   Attenuation model to use.
	 * @param distOrRoll Either the fading distance or rolloff factor, depending on
	 *                   the value of "attmodel".
	 * @return The new sorce's name.
	 */
	public String quickStream(boolean priority, String filename, boolean toLoop, float x, float y, float z,
			int attmodel, float distOrRoll) {
		// generate a random name for this source:
		String sourcename = "Source_" + randomNumberGenerator.nextInt() + "_" + randomNumberGenerator.nextInt();

		// Queue a command to quick stream this new source:
		CommandQueue(new CommandObject(CommandObject.QUICK_PLAY, priority, true, toLoop, sourcename,
				new FilenameURL(filename), x, y, z, attmodel, distOrRoll, true));
		CommandQueue(new CommandObject(CommandObject.PLAY, sourcename));
		// Wake the command thread to process commands:
		commandThread.interrupt();

		// return the new source name.
		return sourcename;
	}

	/**
	 * Creates a temporary source and streams it. After the source finishes playing,
	 * it is removed. The third parameter 'identifier' should look like a filename,
	 * and it must have the correct extension so SoundSystem knows what codec to use
	 * for the file referenced by the URL instance. Returns a randomly generated
	 * name for the new source. NOTE: to make a source created by this method
	 * permanant, call the setActive() method using the return value for sourcename.
	 * 
	 * @param priority   Setting this to true will prevent other sounds from
	 *                   overriding this one.
	 * @param url        URL handle to the sound file to stream at this source.
	 * @param identifier Filename/identifier of the file referenced by the URL.
	 * @param toLoop     Should this source loop, or play only once.
	 * @param x          X position for this source.
	 * @param y          Y position for this source.
	 * @param z          Z position for this source.
	 * @param attmodel   Attenuation model to use.
	 * @param distOrRoll Either the fading distance or rolloff factor, depending on
	 *                   the value of "attmodel".
	 * @return The new sorce's name.
	 */
	public String quickStream(boolean priority, URL url, String identifier, boolean toLoop, float x, float y, float z,
			int attmodel, float distOrRoll) {
		// generate a random name for this source:
		String sourcename = "Source_" + randomNumberGenerator.nextInt() + "_" + randomNumberGenerator.nextInt();

		// Queue a command to quick stream this new source:
		CommandQueue(new CommandObject(CommandObject.QUICK_PLAY, priority, true, toLoop, sourcename,
				new FilenameURL(url, identifier), x, y, z, attmodel, distOrRoll, true));
		CommandQueue(new CommandObject(CommandObject.PLAY, sourcename));
		// Wake the command thread to process commands:
		commandThread.interrupt();

		// return the new source name.
		return sourcename;
	}

	/**
	 * Move a source to the specified location.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param x          destination X coordinate.
	 * @param y          destination Y coordinate.
	 * @param z          destination Z coordinate.
	 */
	public void setPosition(String sourcename, float x, float y, float z) {
		CommandQueue(new CommandObject(CommandObject.SET_POSITION, sourcename, x, y, z));
		commandThread.interrupt();
	}

	/**
	 * Manually sets the specified source's volume.
	 * 
	 * @param sourcename Source to move.
	 * @param value      New volume, float value ( 0.0f - 1.0f ).
	 */
	public void setVolume(String sourcename, float value) {
		CommandQueue(new CommandObject(CommandObject.SET_VOLUME, sourcename, value));
		commandThread.interrupt();
	}

	/**
	 * Returns the current volume of the specified source, or zero if the specified
	 * source was not found.
	 * 
	 * @param sourcename Source to read volume from.
	 * @return Float value representing the source volume (0.0f - 1.0f).
	 */
	public float getVolume(String sourcename) {
		synchronized (SoundSystemConfig.THREAD_SYNC) {
			if (soundLibrary != null)
				return soundLibrary.getVolume(sourcename);
			else
				return 0.0f;
		}
	}

	/**
	 * Manually sets the specified source's pitch.
	 * 
	 * @param sourcename The source's name.
	 * @param value      A float value ( 0.5f - 2.0f ).
	 */
	public void setPitch(String sourcename, float value) {
		CommandQueue(new CommandObject(CommandObject.SET_PITCH, sourcename, value));
		commandThread.interrupt();
	}

	/**
	 * Returns the pitch of the specified source.
	 * 
	 * @param sourcename The source's name.
	 * @return Float value representing the source pitch (0.5f - 2.0f).
	 */
	public float getPitch(String sourcename) {
		if (soundLibrary != null)
			return soundLibrary.getPitch(sourcename);
		else
			return 1.0f;
	}

	/**
	 * Set a source's priority factor. A priority source will not be overriden when
	 * too many sources are playing at once.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param pri        Setting this to true makes this source a priority source.
	 */
	public void setPriority(String sourcename, boolean pri) {
		CommandQueue(new CommandObject(CommandObject.SET_PRIORITY, sourcename, pri));
		commandThread.interrupt();
	}

	/**
	 * Changes a source to looping or non-looping.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param lp         This source should loop.
	 */
	public void setLooping(String sourcename, boolean lp) {
		CommandQueue(new CommandObject(CommandObject.SET_LOOPING, sourcename, lp));
		commandThread.interrupt();
	}

	/**
	 * Changes a source's attenuation model. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Attenuation.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param model      Attenuation model to use.
	 */
	public void setAttenuation(String sourcename, int model) {
		CommandQueue(new CommandObject(CommandObject.SET_ATTENUATION, sourcename, model));
		commandThread.interrupt();
	}

	/**
	 * Changes a source's fade distance or rolloff factor. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about fade distance and rolloff.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param dr         Either the fading distance or rolloff factor, depending on
	 *                   the attenuation model used.
	 */
	public void setDistOrRoll(String sourcename, float dr) {
		CommandQueue(new CommandObject(CommandObject.SET_DIST_OR_ROLL, sourcename, dr));
		commandThread.interrupt();
	}

	/**
	 * Changes the Doppler factor, for determining Doppler effect scale. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Doppler effect.
	 * 
	 * @param dopplerFactor New value for Doppler factor.
	 */
	public void changeDopplerFactor(float dopplerFactor) {
		CommandQueue(new CommandObject(CommandObject.CHANGE_DOPPLER_FACTOR, dopplerFactor));
		commandThread.interrupt();
	}

	/**
	 * Changes the Doppler velocity, for use in Doppler effect. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Doppler effect.
	 * 
	 * @param dopplerVelocity New value for Doppler velocity.
	 */
	public void changeDopplerVelocity(float dopplerVelocity) {
		CommandQueue(new CommandObject(CommandObject.CHANGE_DOPPLER_VELOCITY, dopplerVelocity));
		commandThread.interrupt();
	}

	/**
	 * Sets the specified source's velocity, for use in Doppler effect. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Doppler effect.
	 * 
	 * @param sourcename The source's name.
	 * @param x          Velocity along world x-axis.
	 * @param y          Velocity along world y-axis.
	 * @param z          Velocity along world z-axis.
	 */
	public void setVelocity(String sourcename, float x, float y, float z) {
		CommandQueue(new CommandObject(CommandObject.SET_VELOCITY, sourcename, x, y, z));
		commandThread.interrupt();
	}

	/**
	 * Sets the listener's velocity, for use in Doppler effect. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Doppler effect.
	 * 
	 * @param x Velocity along world x-axis.
	 * @param y Velocity along world y-axis.
	 * @param z Velocity along world z-axis.
	 */
	public void setListenerVelocity(float x, float y, float z) {
		CommandQueue(new CommandObject(CommandObject.SET_LISTENER_VELOCITY, x, y, z));
		commandThread.interrupt();
	}

	/**
	 * Returns the number of miliseconds since the specified source began playing.
	 * 
	 * @return miliseconds, or -1 if not playing or unable to calculate
	 */
	public float millisecondsPlayed(String sourcename) {
		synchronized (SoundSystemConfig.THREAD_SYNC) {
			return soundLibrary.millisecondsPlayed(sourcename);
		}
	}

	/**
	 * Feeds raw data through the specified source. The source must be a streaming
	 * source and it can not be already associated with a file or URL to stream
	 * from. Only use this for streaming sources created with the rawDataStream()
	 * method. NOTE: Be carefull how much data you send to a source to stream. The
	 * data will be processed at playback speed, so if you queue up 1 hour worth of
	 * data, it will take 1 hour to play (not to mention hogging a ton of memory).
	 * To clear out all queued data from the source, use the flush() method. Also
	 * note: if there is a break in the data stream, you will hear clicks and
	 * studders, so ensure that the data flow is steady.
	 * 
	 * @param sourcename Name of the streaming source to play from.
	 * @param buffer     Byte buffer containing raw audio data to stream.
	 */
	public void feedRawAudioData(String sourcename, byte[] buffer) {
		CommandQueue(new CommandObject(CommandObject.FEED_RAW_AUDIO_DATA, sourcename, buffer));
		commandThread.interrupt();
	}

	/**
	 * Plays the specified source.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	public void play(String sourcename) {
		CommandQueue(new CommandObject(CommandObject.PLAY, sourcename));
		commandThread.interrupt();
	}

	/**
	 * Pauses the specified source.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	public void pause(String sourcename) {
		CommandQueue(new CommandObject(CommandObject.PAUSE, sourcename));
		commandThread.interrupt();
	}

	/**
	 * Stops the specified source.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	public void stop(String sourcename) {
		CommandQueue(new CommandObject(CommandObject.STOP, sourcename));
		commandThread.interrupt();
	}

	/**
	 * Rewinds the specified source.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	public void rewind(String sourcename) {
		CommandQueue(new CommandObject(CommandObject.REWIND, sourcename));
		commandThread.interrupt();
	}

	/**
	 * Flushes all previously queued audio data from a streaming source.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	public void flush(String sourcename) {
		CommandQueue(new CommandObject(CommandObject.FLUSH, sourcename));
		commandThread.interrupt();
	}

	/**
	 * Culls the specified source. A culled source can not be played until it has
	 * been activated again.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	public void cull(String sourcename) {
		CommandQueue(new CommandObject(CommandObject.CULL, sourcename));
		commandThread.interrupt();
	}

	/**
	 * Activates the specified source after it was culled, so it can be played
	 * again.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	public void activate(String sourcename) {
		CommandQueue(new CommandObject(CommandObject.ACTIVATE, sourcename));
		commandThread.interrupt();
	}

	/**
	 * Sets a flag for a source indicating whether it should be used or if it should
	 * be removed after it finishes playing. One possible use for this method is to
	 * make temporary sources that were created with quickPlay() permanant. Another
	 * use could be to have a source automatically removed after it finishes
	 * playing. NOTE: Setting a source to temporary does not stop it, and setting a
	 * source to permanant does not play it. It is also important to note that a
	 * looping temporary source will not be removed as long as it keeps playing.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param temporary  True = temporary, False = permanant.
	 */
	public void setTemporary(String sourcename, boolean temporary) {
		CommandQueue(new CommandObject(CommandObject.SET_TEMPORARY, sourcename, temporary));
		commandThread.interrupt();
	}

	/**
	 * Removes the specified source and clears up any memory it used.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	public void removeSource(String sourcename) {
		CommandQueue(new CommandObject(CommandObject.REMOVE_SOURCE, sourcename));
		commandThread.interrupt();
	}

	/**
	 * Moves the listener relative to the current location.
	 * 
	 * @param x X offset.
	 * @param y Y offset.
	 * @param z Z offset.
	 */
	public void moveListener(float x, float y, float z) {
		CommandQueue(new CommandObject(CommandObject.MOVE_LISTENER, x, y, z));
		commandThread.interrupt();
	}

	/**
	 * Moves the listener to the specified location.
	 * 
	 * @param x Destination X coordinate.
	 * @param y Destination Y coordinate.
	 * @param z Destination Z coordinate.
	 */
	public void setListenerPosition(float x, float y, float z) {
		CommandQueue(new CommandObject(CommandObject.SET_LISTENER_POSITION, x, y, z));
		commandThread.interrupt();
	}

	/**
	 * Turns the listener counterclockwise by "angle" radians around the y-axis,
	 * relative to the current angle.
	 * 
	 * @param angle radian offset.
	 */
	public void turnListener(float angle) {
		CommandQueue(new CommandObject(CommandObject.TURN_LISTENER, angle));
		commandThread.interrupt();
	}

	/**
	 * Sets the listener's angle in radians around the y-axis.
	 * 
	 * @param angle radians.
	 */
	public void setListenerAngle(float angle) {
		CommandQueue(new CommandObject(CommandObject.SET_LISTENER_ANGLE, angle));
		commandThread.interrupt();
	}

	/**
	 * Sets the listener's orientation.
	 * 
	 * @param lookX X coordinate of the (normalized) look-at vector.
	 * @param lookY Y coordinate of the (normalized) look-at vector.
	 * @param lookZ Z coordinate of the (normalized) look-at vector.
	 * @param upX   X coordinate of the (normalized) up-direction vector.
	 * @param upY   Y coordinate of the (normalized) up-direction vector.
	 * @param upZ   Z coordinate of the (normalized) up-direction vector.
	 */
	public void setListenerOrientation(float lookX, float lookY, float lookZ, float upX, float upY, float upZ) {
		CommandQueue(new CommandObject(CommandObject.SET_LISTENER_ORIENTATION, lookX, lookY, lookZ, upX, upY, upZ));
		commandThread.interrupt();
	}

	/**
	 * Sets the overall volume, affecting all sources.
	 * 
	 * @param value New volume, float value ( 0.0f - 1.0f ).
	 */
	public void setMasterVolume(float value) {
		CommandQueue(new CommandObject(CommandObject.SET_MASTER_VOLUME, value));
		commandThread.interrupt();
	}

	/**
	 * Returns the overall volume, affecting all sources.
	 * 
	 * @return Float value representing the master volume (0.0f - 1.0f).
	 */
	public float getMasterVolume() {
		return SoundSystemConfig.getMasterGain();
	}

	/**
	 * Method for obtaining information about the listener's position and
	 * orientation.
	 * 
	 * @return a {@link paulscode.sound.ListenerData ListenerData} object.
	 */
	public ListenerData getListenerData() {
		synchronized (SoundSystemConfig.THREAD_SYNC) {
			return soundLibrary.getListenerData();
		}
	}

	/**
	 * Switches to the specified library, and preserves all sources.
	 * 
	 * @param libraryClass Library to use.
	 * @return True if switch was successful. See
	 *         {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for
	 *         information about chosing a sound library.
	 */
	public boolean switchLibrary(Class libraryClass) throws SoundSystemException {
		synchronized (SoundSystemConfig.THREAD_SYNC) {
			initialized(SET, false);

			HashMap<String, Source> sourceMap = null;
			ListenerData listenerData = null;

			boolean wasMidiChannel = false;
			MidiChannel midiChannel = null;
			FilenameURL midiFilenameURL = null;
			String midiSourcename = "";
			boolean midiToLoop = true;

			if (soundLibrary != null) {
				currentLibrary(SET, null);
				sourceMap = copySources(soundLibrary.getSources());
				listenerData = soundLibrary.getListenerData();
				midiChannel = soundLibrary.getMidiChannel();
				if (midiChannel != null) {
					wasMidiChannel = true;
					midiToLoop = midiChannel.getLooping();
					midiSourcename = midiChannel.getSourcename();
					midiFilenameURL = midiChannel.getFilenameURL();
				}

				soundLibrary.cleanup();
				soundLibrary = null;
			}
			message("", 0);
			message("Switching to " + SoundSystemConfig.getLibraryTitle(libraryClass), 0);
			message("(" + SoundSystemConfig.getLibraryDescription(libraryClass) + ")", 1);

			try {
				soundLibrary = (Library) libraryClass.newInstance();
			} catch (InstantiationException ie) {
				errorMessage("The specified library did not load properly", 1);
			} catch (IllegalAccessException iae) {
				errorMessage("The specified library did not load properly", 1);
			} catch (ExceptionInInitializerError eiie) {
				errorMessage("The specified library did not load properly", 1);
			} catch (SecurityException se) {
				errorMessage("The specified library did not load properly", 1);
			}

			if (errorCheck(soundLibrary == null, "Library null after " + "initialization in method 'switchLibrary'",
					1)) {
				SoundSystemException sse = new SoundSystemException(
						className + " did not load properly.  " + "Library was null after initialization.",
						SoundSystemException.LIBRARY_NULL);
				lastException(SET, sse);
				initialized(SET, true);
				throw sse;
			}

			try {
				soundLibrary.init();
			} catch (SoundSystemException sse) {
				lastException(SET, sse);
				initialized(SET, true);
				throw sse;
			}

			soundLibrary.setListenerData(listenerData);
			if (wasMidiChannel) {
				if (midiChannel != null)
					midiChannel.cleanup();
				midiChannel = new MidiChannel(midiToLoop, midiSourcename, midiFilenameURL);
				soundLibrary.setMidiChannel(midiChannel);
			}
			soundLibrary.copySources(sourceMap);

			message("", 0);

			lastException(SET, null);
			initialized(SET, true);

			return true;
		}
	}

	/**
	 * Switches to the specified library, loosing all sources.
	 * 
	 * @param libraryClass Library to use. See
	 *                     {@link paulscode.sound.SoundSystemConfig
	 *                     SoundSystemConfig} for information about chosing a sound
	 *                     library.
	 */
	public boolean newLibrary(Class libraryClass) throws SoundSystemException {
		initialized(SET, false);

		CommandQueue(new CommandObject(CommandObject.NEW_LIBRARY, libraryClass));
		commandThread.interrupt();

		for (int x = 0; (!initialized(GET, XXX)) && (x < 100); x++) {
			snooze(400);
			commandThread.interrupt();
		}

		if (!initialized(GET, XXX)) {
			SoundSystemException sse = new SoundSystemException(className + " did not load after 30 seconds.",
					SoundSystemException.LIBRARY_NULL);
			lastException(SET, sse);
			throw sse;
		} else {
			SoundSystemException sse = lastException(GET, null);
			if (sse != null)
				throw sse;
		}
		return true;
	}

	/**
	 * Switches to the specified library, loosing all sources. This method is used
	 * internally by SoundSystem for thread synchronization, and it can not be
	 * called directly - please use the newLibrary() method instead.
	 * 
	 * @param libraryClass Library to use. See
	 *                     {@link paulscode.sound.SoundSystemConfig
	 *                     SoundSystemConfig} for information about chosing a sound
	 *                     library.
	 */
	private void CommandNewLibrary(Class libraryClass) {
		initialized(SET, false);

		String headerMessage = "Initializing ";
		if (soundLibrary != null) {
			currentLibrary(SET, null);
			// we are switching libraries
			headerMessage = "Switching to ";
			soundLibrary.cleanup();
			soundLibrary = null;
		}
		message(headerMessage + SoundSystemConfig.getLibraryTitle(libraryClass), 0);
		message("(" + SoundSystemConfig.getLibraryDescription(libraryClass) + ")", 1);

		try {
			soundLibrary = (Library) libraryClass.newInstance();
		} catch (InstantiationException ie) {
			errorMessage("The specified library did not load properly", 1);
		} catch (IllegalAccessException iae) {
			errorMessage("The specified library did not load properly", 1);
		} catch (ExceptionInInitializerError eiie) {
			errorMessage("The specified library did not load properly", 1);
		} catch (SecurityException se) {
			errorMessage("The specified library did not load properly", 1);
		}

		if (errorCheck(soundLibrary == null, "Library null after " + "initialization in method 'newLibrary'", 1)) {
			lastException(SET,
					new SoundSystemException(
							className + " did not load properly.  " + "Library was null after initialization.",
							SoundSystemException.LIBRARY_NULL));
			importantMessage("Switching to silent mode", 1);

			try {
				soundLibrary = new Library();
			} catch (SoundSystemException sse) {
				lastException(SET,
						new SoundSystemException(
								"Silent mode did not load properly.  " + "Library was null after initialization.",
								SoundSystemException.LIBRARY_NULL));
				initialized(SET, true);
				return;
			}
		}

		try {
			soundLibrary.init();
		} catch (SoundSystemException sse) {
			lastException(SET, sse);
			initialized(SET, true);
			return;
		}

		lastException(SET, null);
		initialized(SET, true);

		return;
	}

	/**
	 * Calls the library's initialize() method. This method is used internally by
	 * SoundSystem for thread synchronization, and it can not be called directly.
	 */
	private void CommandInitialize() {
		try {
			if (errorCheck(soundLibrary == null, "Library null after " + "initialization in method 'CommandInitialize'",
					1)) {
				SoundSystemException sse = new SoundSystemException(
						className + " did not load properly.  " + "Library was null after initialization.",
						SoundSystemException.LIBRARY_NULL);
				lastException(SET, sse);
				throw sse;
			}
			soundLibrary.init();
		} catch (SoundSystemException sse) {
			lastException(SET, sse);
			initialized(SET, true);
		}
	}

	/**
	 * Loads sample data from a sound file or URL into memory. This method is used
	 * internally by SoundSystem for thread synchronization, and it can not be
	 * called directly - please use the loadSound() method instead.
	 * 
	 * @param filenameURL Filename/URL of the sound file to load.
	 */
	private void CommandLoadSound(FilenameURL filenameURL) {
		if (soundLibrary != null)
			soundLibrary.loadSound(filenameURL);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandLoadSound'", 0);
	}

	/**
	 * Saves the specified sample data, under the specified identifier. This
	 * identifier can be later used in place of 'filename' parameters to reference
	 * the sample data. This method is used internally by SoundSystem for thread
	 * synchronization, and it can not be called directly - please use the
	 * loadSound() method instead.
	 * 
	 * @param buffer     the sample data and audio format to save.
	 * @param identifier What to call the sample.
	 */
	private void CommandLoadSound(SoundBuffer buffer, String identifier) {
		if (soundLibrary != null)
			soundLibrary.loadSound(buffer, identifier);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandLoadSound'", 0);
	}

	/**
	 * Removes previously loaded sampled data from memory. This method is used
	 * internally by SoundSystem for thread synchronization, and it can not be
	 * called directly - please use the unloadSound() method instead.
	 * 
	 * @param filename Filename or string identifyer of sound to unload.
	 */
	private void CommandUnloadSound(String filename) {
		if (soundLibrary != null)
			soundLibrary.unloadSound(filename);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandLoadSound'", 0);
	}

	/**
	 * If the specified source is a streaming source or MIDI source, this method
	 * queues up the next sound to play when the previous playback ends. This method
	 * has no effect on non-streaming sources. This method is used internally by
	 * SoundSystem for thread synchronization, and it can not be called directly -
	 * please use the queueSound() method instead.
	 * 
	 * @param sourcename  Source identifier.
	 * @param filenameURL Filename/URL of the sound file to play next.
	 */
	private void CommandQueueSound(String sourcename, FilenameURL filenameURL) {
		if (soundLibrary != null)
			soundLibrary.queueSound(sourcename, filenameURL);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandQueueSound'", 0);
	}

	/**
	 * Removes the first occurrence of the specified filename/identifier from the
	 * specified source's list of sounds to play when previous playback ends. This
	 * method has no effect on non-streaming sources. This method is used internally
	 * by SoundSystem for thread synchronization, and it can not be called directly
	 * - please use the dequeueSound() method instead.
	 * 
	 * @param sourcename Source identifier.
	 * @param filename   Filename/identifier of the sound file to remove from the
	 *                   queue.
	 */
	private void CommandDequeueSound(String sourcename, String filename) {
		if (soundLibrary != null)
			soundLibrary.dequeueSound(sourcename, filename);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandDequeueSound'", 0);
	}

	/**
	 * Fades out the volume of whatever the specified source is currently playing,
	 * then begins playing the specified file at the source's previously assigned
	 * volume level. If the filenameURL parameter is null or empty, the specified
	 * source 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 specified source's list of queued sounds that would have played next when
	 * the current sound finished playing. This method may only be used for
	 * streaming and MIDI sources. This method is used internally by SoundSystem for
	 * thread synchronization, and it can not be called directly - please use the
	 * fadeOut() method instead.
	 * 
	 * @param sourcename  Name of the source to fade out.
	 * @param filenameURL Filename/URL of a sound file to play next, or null for
	 *                    none.
	 * @param milis       Number of miliseconds the fadeout should take.
	 */
	private void CommandFadeOut(String sourcename, FilenameURL filenameURL, long milis) {
		if (soundLibrary != null)
			soundLibrary.fadeOut(sourcename, filenameURL, milis);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandFadeOut'", 0);
	}

	/**
	 * Fades out the volume of whatever the specified source is currently playing,
	 * then fades the volume back in playing the specified file. Final volume after
	 * fade-in completes will be equal to the source's 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 specified source's list of queued sounds that would
	 * have played next when the current sound finished playing. This method may
	 * only be used for streaming and MIDI sources. This method is used internally
	 * by SoundSystem for thread synchronization, and it can not be called directly
	 * - please use the fadeOutIn() method instead.
	 * 
	 * @param sourcename  Name of the source to fade out/in.
	 * @param filenameURL Filename/URL of a sound 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.
	 */
	private void CommandFadeOutIn(String sourcename, FilenameURL filenameURL, long milisOut, long milisIn) {
		if (soundLibrary != null)
			soundLibrary.fadeOutIn(sourcename, filenameURL, milisOut, milisIn);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandFadeOutIn'", 0);
	}

	/**
	 * Makes sure the current volume levels of streaming sources and MIDI are
	 * correct. This method is designed to help reduce the "jerky" fading behavior
	 * that happens when using some library and codec pluggins (such as
	 * LibraryJavaSound and CodecJOrbis). This method has no effect on normal
	 * "non-streaming" sources. It would normally be called somewhere in the main
	 * "game loop". IMPORTANT: To optimize frame-rates, do not call this method for
	 * every frame. It is better to just call this method at some acceptable
	 * "granularity" (play around with different granularities to find what sounds
	 * acceptable for a particular situation). This method is used internally by
	 * SoundSystem for thread synchronization, and it can not be called directly -
	 * please use the checkFadeVolumes() method instead.
	 */
	private void CommandCheckFadeVolumes() {
		if (soundLibrary != null)
			soundLibrary.checkFadeVolumes();
		else
			errorMessage("Variable 'soundLibrary' null in method " + "'CommandCheckFadeVolumes'", 0);
	}

	/**
	 * Loads a sound file into memory. This method is used internally by SoundSystem
	 * for thread synchronization, and it can not be called directly - please use
	 * the newSource() method instead.
	 * 
	 * @param priority    Setting this to true will prevent other sounds from
	 *                    overriding this one.
	 * @param toStream    Whether or not to stream the source.
	 * @param toLoop      Whether or not to loop the source.
	 * @param sourcename  A unique identifier for the source.
	 * @param filenameURL Filename/URL of the sound file to play at this source.
	 * @param x           X position for this source.
	 * @param y           Y position for this source.
	 * @param z           Z position for this source.
	 * @param attModel    Attenuation model to use.
	 * @param distORroll  Either the fading distance or rolloff factor, depending on
	 *                    the value of "attmodel".
	 */
	private void CommandNewSource(boolean priority, boolean toStream, boolean toLoop, String sourcename,
			FilenameURL filenameURL, float x, float y, float z, int attModel, float distORroll) {
		if (soundLibrary != null) {
			if (filenameURL.getFilename().matches(SoundSystemConfig.EXTENSION_MIDI) && !SoundSystemConfig.midiCodec()) {
				soundLibrary.loadMidi(toLoop, sourcename, filenameURL);
			} else {
				soundLibrary.newSource(priority, toStream, toLoop, sourcename, filenameURL, x, y, z, attModel,
						distORroll);
			}
		} else
			errorMessage("Variable 'soundLibrary' null in method 'CommandNewSource'", 0);
	}

	/**
	 * Opens a direct line for streaming audio data. This method is used internally
	 * by SoundSystem, and it can not be called directly - please use the
	 * rawDataStream() method instead.
	 * 
	 * @param audioFormat Format that the data will be in.
	 * @param priority    Setting this to true will prevent other sounds from
	 *                    overriding this one.
	 * @param sourcename  A unique identifier for this source. Two sources may not
	 *                    use the same sourcename.
	 * @param x           X position for this source.
	 * @param y           Y position for this source.
	 * @param z           Z position for this source.
	 * @param attModel    Attenuation model to use.
	 * @param distOrRoll  Either the fading distance or rolloff factor, depending on
	 *                    the value of "attmodel".
	 */
	private void CommandRawDataStream(AudioFormat audioFormat, boolean priority, String sourcename, float x, float y,
			float z, int attModel, float distOrRoll) {
		if (soundLibrary != null)
			soundLibrary.rawDataStream(audioFormat, priority, sourcename, x, y, z, attModel, distOrRoll);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandRawDataStream'", 0);
	}

	/**
	 * Creates a temporary source and either plays or streams it. After the source
	 * finishes playing, it is removed. This method is used internally by
	 * SoundSystem for thread synchronization, and it can not be called directly -
	 * please use the quickPlay() method instead.
	 * 
	 * @param priority    Setting this to true will prevent other sounds from
	 *                    overriding this one.
	 * @param toStream    Whether or not to stream the source.
	 * @param toLoop      Whether or not to loop the source.
	 * @param sourcename  A unique identifier for the source.
	 * @param filenameURL Filename/URL of the sound file to play at this source.
	 * @param x           X position for this source.
	 * @param y           Y position for this source.
	 * @param z           Z position for this source.
	 * @param attModel    Attenuation model to use.
	 * @param distORroll  Either the fading distance or rolloff factor, depending on
	 *                    the value of "attmodel".
	 * @param temporary   Whether or not the source should be removed after it
	 *                    finishes playing.
	 */
	private void CommandQuickPlay(boolean priority, boolean toStream, boolean toLoop, String sourcename,
			FilenameURL filenameURL, float x, float y, float z, int attModel, float distORroll, boolean temporary) {
		if (soundLibrary != null) {
			if (filenameURL.getFilename().matches(SoundSystemConfig.EXTENSION_MIDI) && !SoundSystemConfig.midiCodec()) {
				soundLibrary.loadMidi(toLoop, sourcename, filenameURL);
			} else {
				soundLibrary.quickPlay(priority, toStream, toLoop, sourcename, filenameURL, x, y, z, attModel,
						distORroll, temporary);
			}
		} else
			errorMessage("Variable 'soundLibrary' null in method 'CommandQuickPlay'", 0);
	}

	/**
	 * Moves a source to the specified coordinates. This method is used internally
	 * by SoundSystem for thread synchronization, and it can not be called directly
	 * - please use the setPosition() method instead.
	 * 
	 * @param sourcename Source to move.
	 * @param x          Destination X coordinate.
	 * @param y          Destination Y coordinate.
	 * @param z          Destination Z coordinate.
	 */
	private void CommandSetPosition(String sourcename, float x, float y, float z) {
		if (soundLibrary != null)
			soundLibrary.setPosition(sourcename, x, y, z);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandMoveSource'", 0);
	}

	/**
	 * Manually sets the specified source's volume. This method is used internally
	 * by SoundSystem for thread synchronization, and it can not be called directly
	 * - please use the setVolume() method instead.
	 * 
	 * @param sourcename Source to change the volume of.
	 * @param value      New volume, float value ( 0.0f - 1.0f ).
	 */
	private void CommandSetVolume(String sourcename, float value) {
		if (soundLibrary != null)
			soundLibrary.setVolume(sourcename, value);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetVolume'", 0);
	}

	/**
	 * Manually sets the specified source's pitch. This method is used internally by
	 * SoundSystem for thread synchronization, and it can not be called directly -
	 * please use the setPitch() method instead.
	 * 
	 * @param sourcename Source to change the pitch of.
	 * @param value      New pitch, float value ( 0.5f - 2.0f ).
	 */
	private void CommandSetPitch(String sourcename, float value) {
		if (soundLibrary != null)
			soundLibrary.setPitch(sourcename, value);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetPitch'", 0);
	}

	/**
	 * Set a source's priority factor. A priority source will not be overriden when
	 * too many sources are playing at once. This method is used internally by
	 * SoundSystem for thread synchronization, and it can not be called directly -
	 * please use the setPriority() method instead.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param pri        Setting this to true makes this source a priority source.
	 */
	private void CommandSetPriority(String sourcename, boolean pri) {
		if (soundLibrary != null)
			soundLibrary.setPriority(sourcename, pri);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetPriority'", 0);
	}

	/**
	 * Changes a source to looping or non-looping. This method is used internally by
	 * SoundSystem for thread synchronization, and it can not be called directly -
	 * please use the setLooping() method instead.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param lp         This source should loop.
	 */
	private void CommandSetLooping(String sourcename, boolean lp) {
		if (soundLibrary != null)
			soundLibrary.setLooping(sourcename, lp);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetLooping'", 0);
	}

	/**
	 * Changes a source's attenuation model. This method is used internally by
	 * SoundSystem for thread synchronization, and it can not be called directly -
	 * please use the setAttenuation() method instead. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Attenuation.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param model      Attenuation model to use.
	 */
	private void CommandSetAttenuation(String sourcename, int model) {
		if (soundLibrary != null)
			soundLibrary.setAttenuation(sourcename, model);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetAttenuation'", 0);
	}

	/**
	 * Changes a source's fade distance or rolloff factor. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about fade distance and rolloff.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param dr         Either the fading distance or rolloff factor, depending on
	 *                   the attenuation model used.
	 */
	private void CommandSetDistOrRoll(String sourcename, float dr) {
		if (soundLibrary != null)
			soundLibrary.setDistOrRoll(sourcename, dr);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetDistOrRoll'", 0);
	}

	/**
	 * Changes the Doppler factor. This method is used internally by SoundSystem for
	 * thread synchronization, and it can not be called directly - please use the
	 * setDopplerFactor() method instead. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Doppler effect.
	 * 
	 * @param dopplerFactor New Doppler factor, for determining Doppler effect
	 *                      scale.
	 */
	private void CommandChangeDopplerFactor(float dopplerFactor) {
		if (soundLibrary != null) {
			SoundSystemConfig.setDopplerFactor(dopplerFactor);
			soundLibrary.dopplerChanged();
		} else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetDopplerFactor'", 0);
	}

	/**
	 * Changes the Doppler velocity. This method is used internally by SoundSystem
	 * for thread synchronization, and it can not be called directly - please use
	 * the setDopplerVelocity() method instead. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Doppler effect.
	 * 
	 * @param dopplerVelocity New Doppler velocity, for use in Doppler effect.
	 */
	private void CommandChangeDopplerVelocity(float dopplerVelocity) {
		if (soundLibrary != null) {
			SoundSystemConfig.setDopplerVelocity(dopplerVelocity);
			soundLibrary.dopplerChanged();
		} else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetDopplerFactor'", 0);
	}

	/**
	 * Changes a source's velocity, for use in Doppler effect. This method is used
	 * internally by SoundSystem for thread synchronization, and it can not be
	 * called directly - please use the setVelocity() method instead. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Doppler effect.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param x          Source's velocity along the world x-axis.
	 * @param y          Source's velocity along the world y-axis.
	 * @param z          Source's velocity along the world z-axis.
	 */
	private void CommandSetVelocity(String sourcename, float x, float y, float z) {
		if (soundLibrary != null)
			soundLibrary.setVelocity(sourcename, x, y, z);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandVelocity'", 0);
	}

	/**
	 * Changes the listener's velocity, for use in Doppler effect. This method is
	 * used internally by SoundSystem for thread synchronization, and it can not be
	 * called directly - please use the setListenerVelocity() method instead. See
	 * {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} for more
	 * information about Doppler effect.
	 * 
	 * @param x Velocity along the world x-axis.
	 * @param y Velocity along the world y-axis.
	 * @param z Velocity along the world z-axis.
	 */
	private void CommandSetListenerVelocity(float x, float y, float z) {
		if (soundLibrary != null)
			soundLibrary.setListenerVelocity(x, y, z);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetListenerVelocity'", 0);
	}

	/**
	 * Plays the specified source. This method is used internally by SoundSystem for
	 * thread synchronization, and it can not be called directly - please use the
	 * play() method instead.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	private void CommandPlay(String sourcename) {
		if (soundLibrary != null)
			soundLibrary.play(sourcename);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandPlay'", 0);
	}

	/**
	 * Feeds raw data through the specified source. The source must be a streaming
	 * source and it can not be already associated with a file or URL to stream
	 * from. This method is used internally by SoundSystem for thread
	 * synchronization, and it can not be called directly - please use the
	 * feedRawAudioData() method instead.
	 * 
	 * @param sourcename Name of the streaming source to play from.
	 * @param buffer     Byte buffer containing raw audio data to stream.
	 */
	private void CommandFeedRawAudioData(String sourcename, byte[] buffer) {
		if (soundLibrary != null)
			soundLibrary.feedRawAudioData(sourcename, buffer);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandFeedRawAudioData'", 0);
	}

	/**
	 * Pauses the specified source. This method is used internally by SoundSystem
	 * for thread synchronization, and it can not be called directly - please use
	 * the pause() method instead.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	private void CommandPause(String sourcename) {
		if (soundLibrary != null)
			soundLibrary.pause(sourcename);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandPause'", 0);
	}

	/**
	 * Stops the specified source. This method is used internally by SoundSystem for
	 * thread synchronization, and it can not be called directly - please use the
	 * stop() method instead.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	private void CommandStop(String sourcename) {
		if (soundLibrary != null)
			soundLibrary.stop(sourcename);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandStop'", 0);
	}

	/**
	 * Rewinds the specified source. This method is used internally by SoundSystem
	 * for thread synchronization, and it can not be called directly - please use
	 * the rewind() method instead.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	private void CommandRewind(String sourcename) {
		if (soundLibrary != null)
			soundLibrary.rewind(sourcename);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandRewind'", 0);
	}

	/**
	 * Flushes all previously queued audio data from a streaming source. This method
	 * is used internally by SoundSystem for thread synchronization, and it can not
	 * be called directly - please use the flush() method instead.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	private void CommandFlush(String sourcename) {
		if (soundLibrary != null)
			soundLibrary.flush(sourcename);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandFlush'", 0);
	}

	/**
	 * Sets a flag for a source indicating whether it should be used or if it should
	 * be removed after it finishes playing. One possible use for this method is to
	 * make temporary sources that were created with quickPlay() permanant. Another
	 * use could be to have a source automatically removed after it finishes
	 * playing. NOTE: Setting a source to inactive does not stop it, and setting a
	 * source to active does not play it. It is also important to note that a
	 * looping inactive source will not be removed as long as it keeps playing. This
	 * method is used internally by SoundSystem for thread synchronization, and it
	 * can not be called directly - please use the setTemporary() method instead.
	 * 
	 * @param sourcename Identifier for the source.
	 * @param temporary  True or False.
	 */
	private void CommandSetTemporary(String sourcename, boolean temporary) {
		if (soundLibrary != null)
			soundLibrary.setTemporary(sourcename, temporary);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetActive'", 0);
	}

	/**
	 * Removes the specified source and clears up any memory it used. This method is
	 * used internally by SoundSystem for thread synchronization, and it can not be
	 * called directly - please use the removeSource() method instead.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	private void CommandRemoveSource(String sourcename) {
		if (soundLibrary != null)
			soundLibrary.removeSource(sourcename);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandRemoveSource'", 0);
	}

	/**
	 * Moves the listener relative to the current location. This method is used
	 * internally by SoundSystem for thread synchronization, and it can not be
	 * called directly - please use the moveListener() method instead.
	 * 
	 * @param x X offset.
	 * @param y Y offset.
	 * @param z Z offset.
	 */
	private void CommandMoveListener(float x, float y, float z) {
		if (soundLibrary != null)
			soundLibrary.moveListener(x, y, z);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandMoveListener'", 0);
	}

	/**
	 * Moves the listener to the specified location. This method is used internally
	 * by SoundSystem for thread synchronization, and it can not be called directly
	 * - please use the setListenerPosition() method instead.
	 * 
	 * @param x Destination X coordinate.
	 * @param y Destination Y coordinate.
	 * @param z Destination Z coordinate.
	 */
	private void CommandSetListenerPosition(float x, float y, float z) {
		if (soundLibrary != null)
			soundLibrary.setListenerPosition(x, y, z);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetListenerPosition'", 0);
	}

	/**
	 * Turns the listener counterclockwise by "angle" radians around the y-axis,
	 * relative to the current angle. This method is used internally by SoundSystem
	 * for thread synchronization, and it can not be called directly - please use
	 * the turnListener() method instead.
	 * 
	 * @param angle radian offset.
	 */
	private void CommandTurnListener(float angle) {
		if (soundLibrary != null)
			soundLibrary.turnListener(angle);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandTurnListener'", 0);
	}

	/**
	 * Sets the listener's angle in radians around the y-axis. This method is used
	 * internally by SoundSystem for thread synchronization, and it can not be
	 * called directly - please use the setListenerAngle() method instead.
	 * 
	 * @param angle radians.
	 */
	private void CommandSetListenerAngle(float angle) {
		if (soundLibrary != null)
			soundLibrary.setListenerAngle(angle);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetListenerAngle'", 0);
	}

	/**
	 * Sets the listener's orientation. This method is used internally by
	 * SoundSystem for thread synchronization, and it can not be called directly -
	 * please use the setListenerOrientation() method instead.
	 * 
	 * @param lookX X coordinate of the (normalized) look-at vector.
	 * @param lookY Y coordinate of the (normalized) look-at vector.
	 * @param lookZ Z coordinate of the (normalized) look-at vector.
	 * @param upX   X coordinate of the (normalized) look-at vector.
	 * @param upY   Y coordinate of the (normalized) look-at vector.
	 * @param upZ   Z coordinate of the (normalized) look-at vector.
	 */
	private void CommandSetListenerOrientation(float lookX, float lookY, float lookZ, float upX, float upY, float upZ) {
		if (soundLibrary != null)
			soundLibrary.setListenerOrientation(lookX, lookY, lookZ, upX, upY, upZ);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetListenerOrientation'", 0);
	}

	/**
	 * Culls the specified source. A culled source can not be played until it has
	 * been activated again. This method is used internally by SoundSystem for
	 * thread synchronization, and it can not be called directly - please use the
	 * cull() method instead.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	private void CommandCull(String sourcename) {
		if (soundLibrary != null)
			soundLibrary.cull(sourcename);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandCull'", 0);
	}

	/**
	 * Activates a previously culled source, so it can be played again. This method
	 * is used internally by SoundSystem for thread synchronization, and it can not
	 * be called directly - please use the activate() method instead.
	 * 
	 * @param sourcename Identifier for the source.
	 */
	private void CommandActivate(String sourcename) {
		if (soundLibrary != null)
			soundLibrary.activate(sourcename);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandActivate'", 0);
	}

	/**
	 * Sets the overall volume, affecting all sources. This method is used
	 * internally by SoundSystem for thread synchronization, and it can not be
	 * called directly - please use the setMasterVolume() method instead.
	 * 
	 * @param value New volume, float value ( 0.0f - 1.0f ).
	 */
	private void CommandSetMasterVolume(float value) {
		if (soundLibrary != null)
			soundLibrary.setMasterVolume(value);
		else
			errorMessage("Variable 'soundLibrary' null in method 'CommandSetMasterVolume'", 0);
	}

	/**
	 * This method can be overridden by extended classes to be used for source
	 * management (culling and activating sources based on established rules). One
	 * possible use for this method is sorting sources by distance, culling the
	 * furthest, and activating the closest. This method is automatically called on
	 * the CommandThread before processing queued commands, so you do not need to
	 * call it anywhere else. Note: use methods cull() and activate() here, NOT
	 * CommandCull() and CommandActivate() (thread safety issue). IMPORTANT: Always
	 * use synchronized( SoundSystemConfig.THREAD_SYNC ) when manually manipulating
	 * sources from outside the Command Thread!
	 */
	protected void ManageSources() {
		// OVERRIDDEN METHODS MUST USE THIS:

		/*******
		 * synchronized( SoundSystemConfig.THREAD_SYNC ) { // TODO: Sort the sources,
		 * cull and activate, etc. }
		 ********/
	}

	/**
	 * Queues a command. If newCommand is null, all commands are dequeued and
	 * executed. This is automatically used by the sound system, so it is not likely
	 * that a user would ever need to use this method. See
	 * {@link paulscode.sound.CommandObject CommandObject} for more information
	 * about commands.
	 * 
	 * @param newCommand Command to queue, or null to execute commands.
	 * @return True if more commands exist, false if queue is empty.
	 */
	public boolean CommandQueue(CommandObject newCommand) {
		synchronized (SoundSystemConfig.THREAD_SYNC) {
			if (newCommand == null) {
				// New command is null - that means execute all queued commands.
				boolean activations = false;
				CommandObject commandObject;

				// Loop through the command queue:
				while (commandQueue != null && commandQueue.size() > 0) {
					// Grab the oldest command in the queue:
					commandObject = commandQueue.remove(0);
					// See what it is, and execute the proper Command method:
					if (commandObject != null) {
						switch (commandObject.Command) {
						case CommandObject.INITIALIZE:
							CommandInitialize();
							break;
						case CommandObject.LOAD_SOUND:
							CommandLoadSound((FilenameURL) commandObject.objectArgs[0]);
							break;
						case CommandObject.LOAD_DATA:
							CommandLoadSound((SoundBuffer) commandObject.objectArgs[0], commandObject.stringArgs[0]);
							break;
						case CommandObject.UNLOAD_SOUND:
							CommandUnloadSound(commandObject.stringArgs[0]);
							break;
						case CommandObject.QUEUE_SOUND:
							CommandQueueSound(commandObject.stringArgs[0], (FilenameURL) commandObject.objectArgs[0]);
							break;
						case CommandObject.DEQUEUE_SOUND:
							CommandDequeueSound(commandObject.stringArgs[0], commandObject.stringArgs[1]);
							break;
						case CommandObject.FADE_OUT:
							CommandFadeOut(commandObject.stringArgs[0], (FilenameURL) commandObject.objectArgs[0],
									commandObject.longArgs[0]);
							break;
						case CommandObject.FADE_OUT_IN:
							CommandFadeOutIn(commandObject.stringArgs[0], (FilenameURL) commandObject.objectArgs[0],
									commandObject.longArgs[0], commandObject.longArgs[1]);
							break;
						case CommandObject.CHECK_FADE_VOLUMES:
							CommandCheckFadeVolumes();
							break;
						case CommandObject.NEW_SOURCE:
							CommandNewSource(commandObject.boolArgs[0], commandObject.boolArgs[1],
									commandObject.boolArgs[2], commandObject.stringArgs[0],
									(FilenameURL) commandObject.objectArgs[0], commandObject.floatArgs[0],
									commandObject.floatArgs[1], commandObject.floatArgs[2], commandObject.intArgs[0],
									commandObject.floatArgs[3]);
							break;
						case CommandObject.RAW_DATA_STREAM:
							CommandRawDataStream((AudioFormat) commandObject.objectArgs[0], commandObject.boolArgs[0],
									commandObject.stringArgs[0], commandObject.floatArgs[0], commandObject.floatArgs[1],
									commandObject.floatArgs[2], commandObject.intArgs[0], commandObject.floatArgs[3]);
							break;
						case CommandObject.QUICK_PLAY:
							CommandQuickPlay(commandObject.boolArgs[0], commandObject.boolArgs[1],
									commandObject.boolArgs[2], commandObject.stringArgs[0],
									(FilenameURL) commandObject.objectArgs[0], commandObject.floatArgs[0],
									commandObject.floatArgs[1], commandObject.floatArgs[2], commandObject.intArgs[0],
									commandObject.floatArgs[3], commandObject.boolArgs[3]);
							break;
						case CommandObject.SET_POSITION:
							CommandSetPosition(commandObject.stringArgs[0], commandObject.floatArgs[0],
									commandObject.floatArgs[1], commandObject.floatArgs[2]);
							break;
						case CommandObject.SET_VOLUME:
							CommandSetVolume(commandObject.stringArgs[0], commandObject.floatArgs[0]);
							break;
						case CommandObject.SET_PITCH:
							CommandSetPitch(commandObject.stringArgs[0], commandObject.floatArgs[0]);
							break;
						case CommandObject.SET_PRIORITY:
							CommandSetPriority(commandObject.stringArgs[0], commandObject.boolArgs[0]);
							break;
						case CommandObject.SET_LOOPING:
							CommandSetLooping(commandObject.stringArgs[0], commandObject.boolArgs[0]);
							break;
						case CommandObject.SET_ATTENUATION:
							CommandSetAttenuation(commandObject.stringArgs[0], commandObject.intArgs[0]);
							break;
						case CommandObject.SET_DIST_OR_ROLL:
							CommandSetDistOrRoll(commandObject.stringArgs[0], commandObject.floatArgs[0]);
							break;
						case CommandObject.CHANGE_DOPPLER_FACTOR:
							CommandChangeDopplerFactor(commandObject.floatArgs[0]);
							break;
						case CommandObject.CHANGE_DOPPLER_VELOCITY:
							CommandChangeDopplerVelocity(commandObject.floatArgs[0]);
							break;
						case CommandObject.SET_VELOCITY:
							CommandSetVelocity(commandObject.stringArgs[0], commandObject.floatArgs[0],
									commandObject.floatArgs[1], commandObject.floatArgs[2]);
							break;
						case CommandObject.SET_LISTENER_VELOCITY:
							CommandSetListenerVelocity(commandObject.floatArgs[0], commandObject.floatArgs[1],
									commandObject.floatArgs[2]);
							break;
						// Methods related to playing sources must be processed
						// after cull/activate commands in order for source
						// management to work properly, so save them for
						// later:
						// ------------------------------------------------------
						case CommandObject.PLAY:
							sourcePlayList.add(commandObject);
							break;
						case CommandObject.FEED_RAW_AUDIO_DATA:
							sourcePlayList.add(commandObject);
							break;
						// ------------------------------------------------------
						case CommandObject.PAUSE:
							CommandPause(commandObject.stringArgs[0]);
							break;
						case CommandObject.STOP:
							CommandStop(commandObject.stringArgs[0]);
							break;
						case CommandObject.REWIND:
							CommandRewind(commandObject.stringArgs[0]);
							break;
						case CommandObject.FLUSH:
							CommandFlush(commandObject.stringArgs[0]);
							break;
						case CommandObject.CULL:
							CommandCull(commandObject.stringArgs[0]);
							break;
						case CommandObject.ACTIVATE:
							activations = true;
							CommandActivate(commandObject.stringArgs[0]);
							break;
						case CommandObject.SET_TEMPORARY:
							CommandSetTemporary(commandObject.stringArgs[0], commandObject.boolArgs[0]);
							break;
						case CommandObject.REMOVE_SOURCE:
							CommandRemoveSource(commandObject.stringArgs[0]);
							break;
						case CommandObject.MOVE_LISTENER:
							CommandMoveListener(commandObject.floatArgs[0], commandObject.floatArgs[1],
									commandObject.floatArgs[2]);
							break;
						case CommandObject.SET_LISTENER_POSITION:
							CommandSetListenerPosition(commandObject.floatArgs[0], commandObject.floatArgs[1],
									commandObject.floatArgs[2]);
							break;
						case CommandObject.TURN_LISTENER:
							CommandTurnListener(commandObject.floatArgs[0]);
							break;
						case CommandObject.SET_LISTENER_ANGLE:
							CommandSetListenerAngle(commandObject.floatArgs[0]);
							break;
						case CommandObject.SET_LISTENER_ORIENTATION:
							CommandSetListenerOrientation(commandObject.floatArgs[0], commandObject.floatArgs[1],
									commandObject.floatArgs[2], commandObject.floatArgs[3], commandObject.floatArgs[4],
									commandObject.floatArgs[5]);
							break;
						case CommandObject.SET_MASTER_VOLUME:
							CommandSetMasterVolume(commandObject.floatArgs[0]);
							break;
						case CommandObject.NEW_LIBRARY:
							CommandNewLibrary(commandObject.classArgs[0]);
							break;
						// If we don't recognize the command, just skip it:
						default:
							break;
						}
					}
				}

				// If any sources were reactivated, check if they need to be
				// replayed:
				if (activations)
					soundLibrary.replaySources();

				// Now that we have the correct sources culled and activated, we
				// can start playing sources. Loop through the playlist and
				// execute the commands:
				while (sourcePlayList != null && sourcePlayList.size() > 0) {
					// Grab the oldest command in the queue:
					commandObject = sourcePlayList.remove(0);
					if (commandObject != null) {
						// See what it is, and execute the proper Command method:
						switch (commandObject.Command) {
						case CommandObject.PLAY:
							CommandPlay(commandObject.stringArgs[0]);
							break;
						case CommandObject.FEED_RAW_AUDIO_DATA:
							CommandFeedRawAudioData(commandObject.stringArgs[0], commandObject.buffer);
							break;
						}
					}
				}

				return (commandQueue != null && commandQueue.size() > 0);
			} else {
				// make sure the commandQueue exists:
				if (commandQueue == null)
					return false;
				// queue a new command
				commandQueue.add(newCommand);
				// Of course there is something in the list now, since we just
				// added it:
				return true;
			}
		}
	}

	/**
	 * Searches for and removes any temporary sources that have finished playing.
	 * This method is used internally by SoundSystem, and it is unlikely that the
	 * user will ever need to use it.
	 */
	public void removeTemporarySources() {
		synchronized (SoundSystemConfig.THREAD_SYNC) {
			if (soundLibrary != null)
				soundLibrary.removeTemporarySources();
		}
	}

	/**
	 * Returns true if the specified source is playing.
	 * 
	 * @param sourcename Unique identifier of the source to check.
	 * @return True or false.
	 */
	public boolean playing(String sourcename) {
		synchronized (SoundSystemConfig.THREAD_SYNC) {
			if (soundLibrary == null)
				return false;

			Source src = soundLibrary.getSources().get(sourcename);

			if (src == null)
				return false;

			return src.playing();
		}
	}

	/**
	 * Returns true if anything is currently playing.
	 * 
	 * @return True or false.
	 */
	public boolean playing() {
		synchronized (SoundSystemConfig.THREAD_SYNC) {
			if (soundLibrary == null)
				return false;

			HashMap<String, Source> sourceMap = soundLibrary.getSources();
			if (sourceMap == null)
				return false;

			Set<String> keys = sourceMap.keySet();
			Iterator<String> iter = keys.iterator();
			String sourcename;
			Source source;

			while (iter.hasNext()) {
				sourcename = iter.next();
				source = sourceMap.get(sourcename);
				if (source != null)
					if (source.playing())
						return true;
			}

			return false;
		}
	}

	/**
	 * Copies and returns the peripheral information from a map of sources. This
	 * method is used internally by SoundSystem, and it is unlikely that the user
	 * will ever need to use it.
	 * 
	 * @param sourceMap Sources to copy.
	 * @return New map of sources.
	 */
	private HashMap<String, Source> copySources(HashMap<String, Source> sourceMap) {
		Set<String> keys = sourceMap.keySet();
		Iterator<String> iter = keys.iterator();
		String sourcename;
		Source source;

		// New map of generic source data:
		HashMap<String, Source> returnMap = new HashMap<String, Source>();

		// loop through and store information from all the sources:
		while (iter.hasNext()) {
			sourcename = iter.next();
			source = sourceMap.get(sourcename);
			if (source != null)
				returnMap.put(sourcename, new Source(source, null));
		}
		return returnMap;
	}

	/**
	 * Checks if the specified library type is compatible.
	 * 
	 * @param libraryClass Libary type to check.
	 * @return True or false.
	 */
	public static boolean libraryCompatible(Class libraryClass) {
		// create the message logger:
		SoundSystemLogger logger = SoundSystemConfig.getLogger();
		// if the user didn't create one, then do it now:
		if (logger == null) {
			logger = new SoundSystemLogger();
			SoundSystemConfig.setLogger(logger);
		}
		logger.message("", 0);
		logger.message("Checking if " + SoundSystemConfig.getLibraryTitle(libraryClass) + " is compatible...", 0);

		boolean comp = SoundSystemConfig.libraryCompatible(libraryClass);

		if (comp)
			logger.message("...yes", 1);
		else
			logger.message("...no", 1);

		return comp;
	}

	/**
	 * Returns the currently loaded library, or -1 if none.
	 * 
	 * @return Global library identifier
	 */
	public static Class currentLibrary() {
		return (currentLibrary(GET, null));
	}

	/**
	 * Returns false if a sound library is busy initializing.
	 * 
	 * @return True or false.
	 */
	public static boolean initialized() {
		return (initialized(GET, XXX));
	}

	/**
	 * Returns the last SoundSystemException thrown, or null if none.
	 * 
	 * @return The last exception.
	 */
	public static SoundSystemException getLastException() {
		return (lastException(GET, null));
	}

	/**
	 * Stores a SoundSystemException which can be retreived later with the
	 * 'getLastException' method.
	 * 
	 * @param e Exception to store.
	 */
	public static void setException(SoundSystemException e) {
		lastException(SET, e);
	}

	/**
	 * Sets or returns the value of boolean 'initialized'.
	 * 
	 * @param action Action to perform (GET or SET).
	 * @param value  New value if action is SET, otherwise XXX.
	 * @return value of boolean 'initialized'.
	 */
	private static boolean initialized(boolean action, boolean value) {
		synchronized (SoundSystemConfig.THREAD_SYNC) {
			if (action == SET)
				initialized = value;
			return initialized;
		}
	}

	/**
	 * Sets or returns the value of boolean 'initialized'.
	 * 
	 * @param action Action to perform (GET or SET).
	 * @param value  New value if action is SET, otherwise XXX.
	 * @return value of boolean 'initialized'.
	 */
	private static Class currentLibrary(boolean action, Class value) {
		synchronized (SoundSystemConfig.THREAD_SYNC) {
			if (action == SET)
				currentLibrary = value;
			return currentLibrary;
		}
	}

	/**
	 * Sets or returns the error code for the last error that occurred. If no errors
	 * have occurred, returns SoundSystem.ERROR_NONE
	 * 
	 * @param action Action to perform (GET or SET).
	 * @param e      New exception if action is SET, otherwise XXX.
	 * @return Last SoundSystemException thrown.
	 */
	private static SoundSystemException lastException(boolean action, SoundSystemException e) {
		synchronized (SoundSystemConfig.THREAD_SYNC) {
			if (action == SET)
				lastException = e;
			return lastException;
		}
	}

	/**
	 * Sleeps for the specified number of milliseconds.
	 */
	protected static void snooze(long milliseconds) {
		try {
			Thread.sleep(milliseconds);
		} catch (InterruptedException e) {
		}
	}

	/**
	 * Prints a message.
	 * 
	 * @param message Message to print.
	 * @param indent  Number of tabs to indent the message.
	 */
	protected void message(String message, int indent) {
		logger.message(message, indent);
	}

	/**
	 * Prints an important message.
	 * 
	 * @param message Message to print.
	 * @param indent  Number of tabs to indent the message.
	 */
	protected void importantMessage(String message, int indent) {
		logger.importantMessage(message, indent);
	}

	/**
	 * Prints the specified message if error is true.
	 * 
	 * @param error   True or False.
	 * @param message Message to print if error is true.
	 * @param indent  Number of tabs to indent the message.
	 * @return True if error is true.
	 */
	protected boolean errorCheck(boolean error, String message, int indent) {
		return logger.errorCheck(error, className, message, indent);
	}

	/**
	 * Prints an error message.
	 * 
	 * @param message Message to print.
	 * @param indent  Number of tabs to indent the message.
	 */
	protected void errorMessage(String message, int indent) {
		logger.errorMessage(className, message, indent);
	}
}
