// Copyright 2001-2004, Danger, Inc.  All Rights Reserved.
// This file is subject to the Danger, Inc. Sample Code License,
// which is provided in the file SAMPLE_CODE_LICENSE.
// Copies are also available from http://developer.danger.com/

package com.example.AudioDemo;

import danger.app.Event;
import danger.audio.AudioException;
import danger.audio.AudioManager;
import danger.audio.Sequence;
import danger.audio.Sound;
import danger.audio.ToneFilter;
import danger.util.DEBUG;

/**
 * Manage the DemoSounds.
 * 
 * This class handles sound operation for the application. It will be called in
 * response to user interface commands to play sounds, or to mute them and
 * dispose them when the app is suspended.
 * 
 * It is worth noting that the Simulator has only a partial subset of the
 * overall sound abilities of an actual device. Not everything will work
 * correctly in the Simulator, but enough works that it is possible to do most
 * of the debugging there.
 * 
 * In particular, compressed sounds are not supported in the Simulator, but
 * compressed sounds are absolutely necessary on the device itself to save
 * space. Start/stop and transport controls don't work well in the simulator but
 * will sometimes work. Volume levels can't be changed in the simulator. A
 * number of APIs will malfunction in confusing ways.
 * 
 * If in doubt about sound functionality, testing upon the actual device is a
 * must.
 * 
 * This class is expected to be used in a static fashion. That way there will
 * only be one user of the audio resources. It is implicitly created when
 * setup() is called. This object is created in a singleton style, but does not
 * supply a reference to the object itself, but rather supplies static routines
 * that can be safely called at any time.
 * 
 * And, the code adopts a safety-first approach to its variables, to avoid null
 * pointer errors. This allows for free use of the routines at any time.
 */
public class DemoSounds implements Resources {

	/**
	 * The sLoop variable holds the MIDI, loaded as a Sequence variable. If it
	 * has failed to load it will be null.
	 */
	private static Sequence sLoop = null;

	/**
	 * Whether the background music is supposed to be playing or shut off. When
	 * initially run, it will be playing, but could be disabled via menus. This
	 * allows for proper suspend/resume behavior.
	 */
	private static boolean sLoopPlaying = true;

	// Loop volume is a percentage of the maximum volume. The maximum is
	// specified by the sound profile that is in use.
	private static final int LOOP_VOLUME = 30;

	// Loop filters can be used to specify specific types of files, like only
	// copyrighted versions, or only those that vibrate.
	private static final int LOOP_FILTER = ToneFilter.NONE;

	// A convenience constant to make its use more clear.
	private static final boolean kDontAutoDelete = false;

	/**
	 * The photon Torpedo sound is loaded into this variable. It it fails to
	 * load the variable will be null.
	 */
	private static Sound sFireSound = null;

	/**
	 * sFireSoundRate is set to the standard rate of the fire sound when it is
	 * loaded from resources. This way its pitch can be changed at will.
	 */
	private static int sFireSoundRate;

	/**
	 * Constructor: The constructor is private, to avoid having the object
	 * instantiated accidentally. It is expected that this object will be used
	 * in a static fashion.
	 */
	private DemoSounds() {
	}

	/**
	 * Static constructor for DemoSounds object. Create, then Load from the
	 * resource file the loop music. In this case the resource is a MIDI file.
	 * If it cannot be created it will throw an exception, which might very well
	 * happen under the simulator, but is less likely on the device itself.
	 * 
	 * Upon suspend, the sLoop is destroyed, and upon resume it is recreated by
	 * calling this setup. Using the sLoopPlaying boolean, this will decide
	 * whether to start the music or hold off.
	 * 
	 * The kDontAutoDelete flag is used to prevent the sound resources from
	 * being automatically disposed. Disposal is specifically handled.
	 * 
	 * It is worth noting that the rocksDemo.mid file has an internal
	 * specification to loop, so that it is only necessary to call start once.
	 * Other .mid files may need to be restarted, or have setLoop() called.
	 * 
	 * This is usually called from resume(), but can be called at any time.
	 */
	public static void setup() {
		DEBUG.p("DemoSounds: setup");

		if (sLoop == null) { // safety first, ensure there is only ever one.
			try {
				sLoop = AudioManager.createSequence(kDontAutoDelete);
				if (sLoop.loadFromResource(ID_SOUNDTRACK)) {
					// Loading worked. Start the music!
					if (sLoopPlaying) {
						startLoop();
					}
				} else {
					// Failed to load the resource, destroy datastructure.
					sLoop.destroy();
					sLoop = null;
					DEBUG.p("AudioDemo: Failed to load background music");
				}
			} catch (AudioException e) {
				// Failed to create the sequence, safely destroy datastructure.
				if (sLoop != null) {
					sLoop.destroy();
					sLoop = null;
				}
				DEBUG.p("AudioDemo: Failed to create background music");
			}
		}

		// Try to create and load the one-shot WAV sound. If it fails,
		// the exception will be thrown. This may very well happen
		// in the simulator, where the audio is not fully supported.
		// In particular, compressed WAV files are not supported in the
		// simulator, and will fail to load here.
		if (sFireSound == null) { // ensure there is only ever one.
			try {
				sFireSound = AudioManager.createSound(kDontAutoDelete);
				if (sFireSound.loadFromResource(ID_TORPEDO)) {
					// Successfully loaded, save the standard sample rate.
					sFireSoundRate = sFireSound.getRate();
				} else {
					sFireSound.destroy();
					sFireSound = null;
					DEBUG.p("AudioDemo: Failed to load left-arrow sound");
				}
			} catch (AudioException e) {
				if (sFireSound != null) {
					sFireSound.destroy();
					sFireSound = null;
				}
				DEBUG.p("AudioDemo: Failed to create left-arrow sound");
			}
		}
	}

	/**
	 * Cleanup for audio. Destroy the sounds so that they free up both the
	 * memory being used, as well as the limited audio hardware resources. In
	 * the worst case of using too many audio channels, the telephone could be
	 * blocked from ringing, which is why this is so important.
	 * 
	 * This is usually called from suspend(), but can be called at any time.
	 */
	public static void cleanup() {
		DEBUG.p("DemoSounds: cleanup");

		if (sLoop != null) {
			// stop() is not necessary, because the destroy() method will stop
			// the sound as well. But, on the simulator destroy() does not work,
			// so if that is important, sLoop.stop() could be added.
			sLoop.destroy();
			sLoop = null;
		}
		if (sFireSound != null) {
			sFireSound.destroy();
			sFireSound = null;
		}
	}

	/**
	 * Stop the loop music because the user chose the menu Stop. But, don't
	 * destroy it because it might be restarted at any time.
	 */
	public static void stopLoop() {
		DEBUG.p("DemoSounds: stopLoop");

		sLoopPlaying = false;
		if (sLoop != null) {
			sLoop.stop();
		}
	}

	/**
	 * Start the loop music based upon a user choice of Play music. This uses
	 * the LOOP_VOLUME parameter, to set the background music at a lower volume
	 * than the torpedo sounds. LOOP_FILTER is set to None, to allow for any and
	 * all sounds.
	 */
	public static void startLoop() {
		DEBUG.p("DemoSounds: startLoop");

		sLoopPlaying = true;
		if (sLoop != null) {
			sLoop.start(LOOP_FILTER, LOOP_VOLUME);
		}
	}

	/**
	 * Convenient final boolean value to make the if isDone comparison more
	 * clear.
	 */
	static final boolean kStillPlaying = false;

	/**
	 * startTorpedo() the one-shot sound in response to a padClick.
	 * 
	 * This also demonstrates a simple way to change the pitch of a sound, by
	 * modifying the playback rate. Using the 4 edges of the joypad, this will
	 * use setRate to double, quadruple, or halve the playback rate.
	 * 
	 * It is notable that getRate() and setRate() do not work in the simulator,
	 * so the same unmodified sound will always play there.
	 */
	public static void startTorpedo(int padClick) {
		DEBUG.p("DemoSounds: startTorpedo");

		if (sFireSound != null) {

			// If the previous torpedo sound has not finished yet, stop it
			// so that a new one can be started. Preempting the last sound.
			if (sFireSound.isDone() == kStillPlaying) {
				sFireSound.stop();
			}

			// For the different directions on the JoyPad, change the sample
			// rate to make the torpedo sound different. This is a simple
			// and quick way to change the pitch.
			switch (padClick) {
			case Event.DEVICE_ARROW_LEFT:
				sFireSound.setRate(sFireSoundRate);
				break;
			case Event.DEVICE_ARROW_UP:
				sFireSound.setRate(sFireSoundRate * 2);
				break;
			case Event.DEVICE_ARROW_RIGHT:
				sFireSound.setRate(sFireSoundRate * 4);
				break;
			case Event.DEVICE_ARROW_DOWN:
				sFireSound.setRate(sFireSoundRate / 2);
				break;
			}

			// rate changed to match user choice, Bombs away.
			sFireSound.start();
		}
	}

	/**
	 * This pause() method is to just pause all sounds that might be running. It
	 * is used to handle System Dialogs popping up while the music is playing.
	 * When a System Dialog is up, the audio needs to pause to provide the
	 * proper UI.
	 */
	public static void pause() {
		DEBUG.p("DemoSounds: pause");

		if (sLoop != null) {
			sLoop.pause();
		}
		if (sFireSound != null) {
			sFireSound.pause();
		}
	}

	/**
	 * resume() is the counterpart to pause(), and will restart the music and
	 * Audio where it left off.
	 */
	public static void resume() {
		DEBUG.p("DemoSounds: resume");

		if (sLoop != null) {
			sLoop.resume();
		}
		if (sFireSound != null) {
			sFireSound.resume();
		}
	}
}
