/*
 * FSSoundStreamHead2.java
 * Transform
 * 
 * Copyright (c) 2001-2008 Flagstone Software Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Flagstone Software Ltd. nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software 
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.flagstone.transform;

/**
 * FSSoundStreamHead2 defines an sound compressed using different compression
 * formats that is streamed in tight synchronisation with the movie being
 * played.
 * 
 * <p>
 * The FSSoundStreamHead2 class defines how the streaming sound is played.
 * Objects of the FSSoundStreamBlock class contains the sound data.
 * </P>
 * 
 * <p>
 * When a stream sound is played if the Flash Player cannot render the frames
 * fast enough to maintain synchronisation with the sound being played then
 * frames will be skipped. Normally the player will reduce the frame rate so
 * every frame of a movie is played.
 * </p>
 * 
 * <p>
 * To define the stream sound the parameters for and sampled sound and how it
 * will be played back must be specified:
 * </p>
 * 
 * <table class="datasheet">
 * 
 * <tr>
 * <th align="left" colspan="2">Attributes</th>
 * </tr>
 * 
 * <tr>
 * <td>type</td>
 * <td>Identifies the data structure when it is encoded. Read-only.</td>
 * </tr>
 * 
 * <tr>
 * <td>format</td>
 * <td>The format of the encoded sound data - FSSound.PCM (Little-Endian byte
 * order), FSSound.ADPCM, FSSound.NATIVE_PCM (Big-Endian or Little-Endian byte
 * order depending on the platform where the sound was created), FSSound.MP3 or
 * FSSound.NELLYMOSER</td>
 * </tr>
 * 
 * <tr>
 * <td>playbackRate</td>
 * <td>The recommended playback rate in Hertz - 5512, 11025, 22050 or 44100.</td>
 * </tr>
 * 
 * <tr>
 * <td>playbackSampleSize</td>
 * <td>The number of bytes in an uncompressed sample when the sound is played,
 * either 1 or 2.</td>
 * </tr>
 * 
 * <tr>
 * <td>playbackChannels</td>
 * <td>The recommended number of playback channels: 1 = mono or 2 = stereo.</td>
 * </tr>
 * 
 * <tr>
 * <td>streamRate</td>
 * <td>The stream sampling rate - 5512, 11025, 22050 or 44100 Hz</td>
 * </tr>
 * 
 * <tr>
 * <td>streamSampleSize</td>
 * <td>The size of an uncompressed sample in the streaming sound in bytes,
 * either 1 or 2.</td>
 * </tr>
 * 
 * <tr>
 * <td>streamChannels</td>
 * <td>The number of channels: 1 = mono or 2 = stereo in the streaming sound</td>
 * </tr>
 * 
 * <tr>
 * <td>streamSampleCount</td>
 * <td>The average number of samples in each sound stream block.</td>
 * </tr>
 * 
 * <tr>
 * <td>latency</td>
 * <td>Latency defines the number of samples to skip only when playing sounds
 * encoded with the MP3 format.</td>
 * </tr>
 * 
 * </table>
 * 
 * <p>
 * FSSoundStreamHead2 allows way the sound is played to differ from the way it
 * is encoded and streamed to the player. This allows the Player more control
 * over how the animation is rendered. Reducing the resolution or playback rate
 * can improve synchronisation with the frames displayed.
 * </p>
 * 
 * <b>History</b>
 * 
 * <p>
 * The FSSoundStreamHead2 class represents the SoundStreamHead structure from
 * the Flash specification. It was introduced in Flash 3. Support for the
 * Nellymoser Asao format was added in Flash 6.
 * </p>
 */
public class FSSoundStreamHead2 extends FSMovieObject
{
	private int format = FSSound.ADPCM;
	private int playbackRate = 5512;
	private int playbackChannels = 1;
	private int playbackSampleSize = 16;
	private int streamRate = 5512;
	private int streamChannels = 1;
	private int streamSampleSize = 16;
	private int streamSampleCount = 0;
	private int latency = 0;

	/*
	 * The following variable is used to preserve the value of a reserved field
	 * when decoding then encoding an existing Flash file. Macromedia's file
	 * file format specification states that this field is always zero - it is
	 * not, so this is used to preserve the value in case it is implementing an
	 * undocumented feature.
	 */
	private int _reserved = 0;

	/**
	 * Construct an FSSoundStreamHead2 object, initialising it with values
	 * decoded from an encoded object.
	 * 
	 * @param coder
	 *            an FSCoder containing the binary data.
	 */
	public FSSoundStreamHead2(FSCoder coder)
	{
		super(SoundStreamHead2);
		decode(coder);
	}

	/**
	 * Constructs an FSSoundStreamHead2 object specifying all the parameters
	 * required to define the sound.
	 * 
	 * @param encoding
	 *            the compression format for the sound data, either
	 *            FSSound.NATIVE_PCM, FSSound.ADPCM, FSSound.MP3, FSSound.PCM or
	 *            FSSound.NELLYMOSER (Flash 6+ only).
	 * @param playRate
	 *            the recommended rate for playing the sound, either 5512,
	 *            11025, 22050 or 44100 Hz.
	 * @param playChannels
	 *            The recommended number of playback channels: 1 = mono or 2 =
	 *            stereo.
	 * @param playSize
	 *            the recommended uncompressed sample size for playing the
	 *            sound, either 1 or 2 bytes.
	 * @param streamingRate
	 *            the rate at which the sound was sampled, either 5512, 11025,
	 *            22050 or 44100 Hz.
	 * @param streamingChannels
	 *            the number of channels: 1 = mono or 2 = stereo.
	 * @param streamingSize
	 *            the sample size for the sound, either 1 or 2 bytes.
	 * @param streamingCount
	 *            the number of samples in each subsequent FSSoundStreamBlock
	 *            object.
	 * @param latency
	 *            defines the number of samples to skip only when playing sounds
	 *            encoded with the MP3 format.
	 */
	public FSSoundStreamHead2(int encoding, int playRate, int playChannels,
								int playSize, int streamingRate,
								int streamingChannels, int streamingSize,
								int streamingCount, int latency)
	{
		super(SoundStreamHead2);

		setFormat(encoding);

		setPlaybackRate(playRate);
		setPlaybackChannels(playChannels);
		setPlaybackSampleSize(playSize);

		setStreamRate(streamingRate);
		setStreamChannels(streamingChannels);
		setStreamSampleSize(streamingSize);
		setStreamSampleCount(streamingCount);
		setLatency(latency);
	}

	/**
	 * Constructs an FSSoundStreamHead2 object by copying values from an
	 * existing object.
	 * 
	 * @param obj
	 *            an FSSoundStreamHead2 object.
	 */
	public FSSoundStreamHead2(FSSoundStreamHead2 obj)
	{
		super(obj);
		format = obj.format;
		playbackRate = obj.playbackRate;
		playbackChannels = obj.playbackChannels;
		playbackSampleSize = obj.playbackSampleSize;

		streamRate = obj.streamRate;
		streamChannels = obj.streamChannels;
		streamSampleSize = obj.streamSampleSize;
		streamSampleCount = obj.streamSampleCount;
		latency = obj.latency;
		_reserved = obj._reserved;
	}

	/**
	 * Gets the streaming sound format. For the FSSoundStreamHead2 class
	 * supports NATIVE_PCM, ADPCM, MP3, PCM or NELLYMOSER encoded sound data.
	 * 
	 * @return the format used to play the sound.
	 */
	public int getFormat()
	{
		return format;
	}

	/**
	 * Sets the format for the streaming sound.
	 * 
	 * @param encoding
	 *            the compression format for the sound data, either
	 *            FSSound.NATIVE_PCM, FSSound.ADPCM, FSSound.MP3, FSSound.PCM or
	 *            FSSound.NELLYMOSER.
	 */
	public void setFormat(int encoding)
	{
		format = encoding;
	}

	/**
	 * Gets the recommended playback rate: 5512, 11025, 22050 or 44100 Hertz.
	 * 
	 * @return the rate at which the sound will be played back.
	 */
	public int getPlaybackRate()
	{
		return playbackRate;
	}

	/**
	 * Gets the recommended number of playback channels = 1 = mono 2 = stereo.
	 * 
	 * @return the number of channels for the sound playback.
	 */
	public int getPlaybackChannels()
	{
		return playbackChannels;
	}

	/**
	 * Gets the recommended playback sample range in bytes: 1 or 2.
	 * 
	 * @return the sample size used to play back the sound.
	 */
	public int getPlaybackSampleSize()
	{
		return playbackSampleSize;
	}

	/**
	 * Gets the sample rate: 5512, 11025, 22050 or 44100 Hz in the streaming
	 * sound.
	 * 
	 * @return the rate at which the sound was sampled.
	 */
	public float getStreamRate()
	{
		return streamRate;
	}

	/**
	 * Gets the number of channels, 1 = mono 2 = stereo, in the streaming sound.
	 * 
	 * @return the number of channels in the sound.
	 */
	public int getStreamChannels()
	{
		return streamChannels;
	}

	/**
	 * Gets the sample size in bytes: 1 or 2 in the streaming sound.
	 * 
	 * @return the sample size.
	 */
	public int getStreamSampleSize()
	{
		return streamSampleSize;
	}

	/**
	 * Gets the average number of samples in each stream block following.
	 * 
	 * @return the number of sound samples in each following FSSoundStreamBlock
	 *         object.
	 */
	public int getStreamSampleCount()
	{
		return streamSampleCount;
	}

	/**
	 * Sets the recommended playback rate in Hz. Must be either: 5512, 11025,
	 * 22050 or 44100.
	 * 
	 * @param rate
	 *            the recommended rate for playing the sound.
	 */
	public void setPlaybackRate(int rate)
	{
        if (rate != 5512 && rate != 11025 && rate != 22050 && rate != 44100) {
            throw new IllegalArgumentException("Sample rate must be: 5512, 11025, 22050 or 44100");
        }
		playbackRate = rate;
	}

	/**
	 * Sets the recommended number of playback channels = 1 = mono 2 = stereo.
	 * 
	 * @param aNumber
	 *            the recommended number of playback channels.
	 */
	public void setPlaybackChannels(int aNumber)
	{
		playbackChannels = aNumber;
	}

	/**
	 * Sets the recommended playback sample size in bytes. Must be wither 1 or
	 * 2.
	 * 
	 * @param playSize
	 *            the recommended sample size for playing the sound.
	 */
	public void setPlaybackSampleSize(int playSize)
	{
		playbackSampleSize = playSize;
	}

	/**
	 * Sets the sample rate in Hz for the streaming sound. Must be either: 5512,
	 * 11025, 22050 or 44100.
	 * 
	 * @param rate
	 *            the rate at which the streaming sound was sampled.
	 */
	public void setStreamRate(int rate)
	{
        if (rate != 5512 && rate != 11025 && rate != 22050 && rate != 44100) {
            throw new IllegalArgumentException("Sample rate must be: 5512, 11025, 22050 or 44100");
        }
		streamRate = rate;
	}

	/**
	 * Sets the number of channels in the streaming sound: 1 = mono 2 = stereo.
	 * 
	 * @param streamingChannels
	 *            the number of channels in the streaming sound.
	 */
	public void setStreamChannels(int streamingChannels)
	{
		streamChannels = streamingChannels;
	}

	/**
	 * Sets the sample size in bytes for the streaming sound. Must be 1 or 2.
	 * 
	 * @param streamingSize
	 *            the sample size for the sound.
	 */
	public void setStreamSampleSize(int streamingSize)
	{
		streamSampleSize = streamingSize;
	}

	/**
	 * Sets the number of samples in each stream block.
	 * 
	 * @param streamingCount
	 *            the number of samples in each subsequent FSSoundStreamBlock
	 *            object.
	 */
	public void setStreamSampleCount(int streamingCount)
	{
		streamSampleCount = streamingCount;
	}

	/**
	 * For MP3 encoded sounds, returns the number of samples to skip when
	 * starting to play a sound.
	 * 
	 * @return the number of samples skipped in an MP3 encoded sound Returns 0
	 *         for other sound formats.
	 */
	public int getLatency()
	{
		return latency;
	}

	/**
	 * Set the number of samples to skip when starting to play an MP3 encoded
	 * sound.
	 * 
	 * @param latency
	 *            the number of samples to be skipped in an MP3 encoded sound
	 *            should be 0 for other sound formats.
	 */
	public void setLatency(int latency)
	{
		this.latency = latency;
	}

	public boolean equals(Object anObject)
	{
		boolean result = false;

		if (super.equals(anObject))
		{
			FSSoundStreamHead2 typedObject = (FSSoundStreamHead2) anObject;

			result = format == typedObject.format;
			result = result && playbackRate == typedObject.playbackRate;
			result = result && playbackChannels == typedObject.playbackChannels;
			result = result
							&& playbackSampleSize == typedObject.playbackSampleSize;
			result = result && streamRate == typedObject.streamRate;
			result = result && streamChannels == typedObject.streamChannels;
			result = result && streamSampleSize == typedObject.streamSampleSize;
			result = result
							&& streamSampleCount == typedObject.streamSampleCount;
			result = result && latency == typedObject.latency;
		}
		return result;
	}

	public void appendDescription(StringBuffer buffer, int depth)
	{
		buffer.append(name());

		if (depth > 0)
		{
			buffer.append(": { ");
			Transform.append(buffer, "format", format);
			Transform.append(buffer, "playbackRate", playbackRate);
			Transform.append(buffer, "playbackChannels", playbackChannels);
			Transform.append(buffer, "playbackSampleSize", playbackSampleSize);
			Transform.append(buffer, "streamRate", streamRate);
			Transform.append(buffer, "streamChannels", streamChannels);
			Transform.append(buffer, "streamSampleSize", streamSampleSize);
			Transform.append(buffer, "streamSampleCount", streamSampleCount);
			Transform.append(buffer, "latency", latency);
			buffer.append("}");
		}
	}

	public int length(FSCoder coder)
	{
		super.length(coder);

		length += 4;

		if (format == FSSound.MP3 && latency > 0)
			length += 2;

		return length;
	}

	public void encode(FSCoder coder)
	{
		super.encode(coder);

		coder.writeBits(_reserved, 4);

		switch (playbackRate)
		{
			case 5512:
				coder.writeBits(0, 2);
				break;
			case 11025:
				coder.writeBits(1, 2);
				break;
			case 22050:
				coder.writeBits(2, 2);
				break;
			case 44100:
				coder.writeBits(3, 2);
				break;
		}
		coder.writeBits(playbackSampleSize - 1, 1);
		coder.writeBits(playbackChannels - 1, 1);

		coder.writeBits(format, 4);

		switch (streamRate)
		{
			case 5512:
				coder.writeBits(0, 2);
				break;
			case 11025:
				coder.writeBits(1, 2);
				break;
			case 22050:
				coder.writeBits(2, 2);
				break;
			case 44100:
				coder.writeBits(3, 2);
				break;
		}
		coder.writeBits(streamSampleSize - 1, 1);
		coder.writeBits(streamChannels - 1, 1);
		coder.writeWord(streamSampleCount, 2);

		if (format == FSSound.MP3 && latency > 0)
			coder.writeWord(latency, 2);

		coder.endObject(name());
	}

	public void decode(FSCoder coder)
	{
		super.decode(coder);

		_reserved = coder.readBits(4, false);
		switch (coder.readBits(2, false))
		{
			case 0:
				playbackRate = 5512;
				break;
			case 1:
				playbackRate = 11025;
				break;
			case 2:
				playbackRate = 22050;
				break;
			case 3:
				playbackRate = 44100;
				break;
		}
		playbackSampleSize = coder.readBits(1, false) + 1;
		playbackChannels = coder.readBits(1, false) + 1;

		format = coder.readBits(4, false);

		switch (coder.readBits(2, false))
		{
			case 0:
				streamRate = 5512;
				break;
			case 1:
				streamRate = 11025;
				break;
			case 2:
				streamRate = 22050;
				break;
			case 3:
				streamRate = 44100;
				break;
		}
		streamSampleSize = coder.readBits(1, false) + 1;
		streamChannels = coder.readBits(1, false) + 1;
		streamSampleCount = coder.readWord(2, false);

		if (length == 6 && format == FSSound.MP3)
			latency = coder.readWord(2, true);

		coder.endObject(name());
	}
}
