/* libsswf_tag_sound.c++ -- written by Alexis WILKE for Made to Order Software Corp. (c) 2002-2008 */

/*

Copyright (c) 2002-2008 Made to Order Software Corp.

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and
associated documentation files (the "Software"), to
deal in the Software without restriction, including
without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

/** \file
 *
 * \brief The implementation of all the different sound classes
 *
 * This file declares the body of the functions which are not
 * inline. It is part of the SSWF library.
 */

#include	"sswf/libsswf.h"

using namespace sswf;




/////////////////////////////////////////// Envelope


/** \class sswf::Envelope 
 *
 * \brief An array of volumes for a sound effect
 *
 * This class is used to defined volume changes to be used
 * against a sound effect or music.
 *
 * This is useful if you have several places where you
 * want to place the same sound at different volume levels.
 *
 * \warning
 * The volume goes from 0 to 32768. Larger values have an
 * undefined result.
 *
 * \sa sswf::SoundInfo
 * \sa <a href="../SWFalexref.html#swf_envelope">SWF Alexis' Reference&mdash;swf_envelope</a>
 */


/** \brief Initializes an envelope
 *
 * The constructor initializes an envelope with a position
 * and the volume on the left and right sides. If the sound
 * is not stereo, the volume of both sides should be set to
 * the same value.
 *
 * The left and right volumes go from 0 (mute) to
 * 32768 (max. power). The effect of using values outside this
 * range is undefined.
 *
 * The position is always defined as if the rate of the sound
 * was 44,100k. So you need to multiply all positions by some
 * value to obtain the correct envelope position.
 *
 * So, if the rate of the sound is defined a R, the factor you
 * need to use is:
 *
 *	44,100 / rate
 *
 * In other words:
 *
 * \htmlonly
 * <blockquote>
 * <table cellpadding="5" cellspacing="0" border="1">
 * 	<tr><th>rate</th><th>factor</th></tr>
 *	<tr><td align="right"> 5,512</td><td align="right">8</td></tr>
 *	<tr><td align="right">11,025</td><td align="right">4</td></tr>
 *	<tr><td align="right">22,050</td><td align="right">2</td></tr>
 *	<tr><td align="right">44,100</td><td align="right">1</td></tr>
 * </table>
 * </blockquote>
 * \endhtmlonly
 *
 * Also, positions need to be defined between the start and
 * stop positions of the corresponding sswf::SoundInfo (see the
 * sswf::SoundInfo::SetRange(unsigned long start, unsigned long end)
 * function.)
 *
 * Note that the volume is clamped to the range 0 to 32768.
 *
 * \bug
 * The Envelope does not know whether the sound it will affect
 * is stereo or mono. This may not be a problem with newer
 * versions of the Macromedia players.
 *
 * \param[in] p The position where the new volume applies
 * \param[in] l The volume on the left side (0 to 32768)
 * \param[in] r The volume on the left side (0 to 32768)
 *
 * \sa sswf::Envelope::Envelope(const Envelope& envelope)
 * \sa sswf::SoundInfo
 * \sa sswf::SoundInfo::SetRange(unsigned long start, unsigned long end)
 * \sa sswf::TagStartSound
 * \sa sswf::TagSound
 */
Envelope::Envelope(unsigned long p, unsigned short l, unsigned short r)
{
	// since we are in a constructor, we continue so as to avoid
	// throwing... (also the OnError() function is not available...)
	if(l > 32768) {
		l = 32768;
	}
	if(r > 32768) {
		r = 32768;
	}

	f_position = p;
	f_left     = l;
	f_right    = r;
}


/** \brief Copy an envelope in another one
 *
 * This function can be used to copy an envelope in another.
 *
 * \sa sswf::Envelope::Envelope(unsigned long p, unsigned short l, unsigned short r)
 */
Envelope::Envelope(const Envelope& envelope)
{
	f_position = envelope.f_position;
	f_left     = envelope.f_left;
	f_right    = envelope.f_right;
}


/** \brief Retrieve the position of this envelope
 *
 * This function returns the position of the this envelope.
 *
 * \return The position of the envelope
 */
unsigned long Envelope::Position(void) const
{
	return f_position;
}


/** \brief Retrieve the volume for the left side
 *
 * This function returns the volume for the left side.
 *
 * \return The left side volume
 */
unsigned short Envelope::Left(void) const
{
	return f_left;
}


/** \brief Retrieve the volume for the right side
 *
 * This function returns the volume for the right side.
 *
 * \return The right side volume
 */
unsigned short Envelope::Right(void) const
{
	return f_right;
}


/** \brief Save an envelope in a Data buffer
 *
 * This function is used to save an envelope as the SWF
 * player expects. It saves the position, left and then
 * right volumes.
 *
 * \param[in] data The Data buffer where the envelope is to be saved
 */
void Envelope::Save(Data& data) const
{
	data.PutLong(f_position);
	data.PutShort(f_left);
	data.PutShort(f_right);
}



/////////////////////////////////////////// SoundInfo


/** \class sswf::SoundInfo 
 *
 * \brief Information about out to playback a sound effect
 *
 * A SoundInfo object defines how to playback a sound effect.
 * In general, you play a sound effect from start to finish
 * once. The sound info lets you have a large sound effect and
 * only play a portion of it with different volume information
 * (i.e. envelope)
 *
 * A SoundInfo object is set in a TagStartSound object.
 *
 * \warning
 * The volume goes from 0 to 32768. Larger values have an
 * undefined result.
 *
 * \bug
 * This object does not accept 0 as a sound object identifier.
 * No object should have its identifier set to zero anyway.
 *
 * \sa sswf::Envelope
 * \sa sswf::TagStartSound
 * \sa <a href="../SWFalexref.html#swf_soundinfo">SWF Alexis' Reference&mdash;swf_soundinfo</a>
 */


/** \brief Initializes a sound information class.
 *
 * This constructor resets the SoundInfo object to defaults:
 *
 * Stop the sound, the sound effect cannot be played more
 * than once, the start and end position are 0 and end of
 * the sound effect respectively, it loops just once and
 * there is no envelopes by default.
 *
 * \param[in] error_manager A pointer to your TagHeader (i.e. it is the error manager!)
 *
 * \sa sswf::Envelope
 */
SoundInfo::SoundInfo(ErrorManager& error_manager)
	: f_error_manager(error_manager)
{
	f_sound_id = 0;
	f_stop = false;
	f_no_multiple = false;
	f_start_position = 0;
	f_end_position = 0;
	f_loop = 1;
	//f_envelopes = ... -- auto init by constructor
}


/** \brief Set the reference to the sound to playback.
 *
 * This function must be called with a valid identifier (i.e. an
 * identifier to a sound effect object.)
 *
 * The default value (0) is not valid. Thus you have to call
 * this function at least once.
 *
 * \param[in] id The identifier of the corresponding TagSound
 */
void SoundInfo::SetSoundID(sswf_id_t id)
{
	if(id == 0) {
		f_error_manager.OnError(ErrorManager::ERROR_CODE_INVALID_IDENTIFIER, "A SoundInfo object must be given a valid TagSound object reference.");
		return;
	}
	f_sound_id = id;
}


/** \brief Whether this SoundInfo is used to stop a sound.
 *
 * This function is used to mark a SoundInfo entry as a "Stop Button"
 * (true) or a "Start Button" (false.)
 *
 * When a sound is marked to be stopped (default), then the other
 * parameters (except the sound identifier) are ignored.
 *
 * \param[in] stop If true, stop the sound.
 */
void SoundInfo::StopSound(bool stop)
{
	f_stop = stop;
}


/** \brief Whether the referenced sound can be played multiple times in parallel.
 *
 * This flag is used to determine whether the referenced sound can be
 * played multiple times in parallel. In general, this flag is set to
 * true but the default is false.
 *
 * \param[in] no_multiple Whether multiple instances will play in parallel
 */
void SoundInfo::NoMultiple(bool no_multiple)
{
	f_no_multiple = no_multiple;
}


/** \brief The start and end points to playback.
 *
 * The SoundInfo can be used to play only a small portion of a sound effect.
 * This function can be used to define the start and end points in the sound
 * samples.
 *
 * The start parameter must be small than the end parameter unless one or
 * the other is set to zero. (i.e. zero represents the default; for the start
 * zero is zero and for the end zero represents the total number of samples
 * of the referenced sound tag.)
 *
 * The position is defined in number of samples. So when set to 1 with stereo
 * 16 bits sample, the sound starts playing at byte offset 4.
 *
 * \param[in] start The start position
 * \param[in] end The end position
 */
void SoundInfo::SetRange(unsigned long start, unsigned long end)
{
	if(start > end && start != 0 && end != 0) {
		f_error_manager.OnError(ErrorManager::ERROR_CODE_EMPTY_POSITION_RANGE, "The start position is larger than the end in a SoundInfo object.");
		return;
	}
	f_start_position = start;
	f_end_position = end;
}


/** \brief Set the number of times a sound shall be played.
 *
 * This function changes the number of time a sound effect will be
 * repeated. By default a sound effect is played just once.
 *
 * \bug
 * The loop parameter cannot be set to zero.
 *
 * \param[in] loop The number of times the sound effect shall be played
 */
void SoundInfo::SetLoop(unsigned short loop)
{
	if(loop == 0) {
		f_error_manager.OnError(ErrorManager::ERROR_CODE_LOOP_ZERO, "The loop parameter cannot be zero in a SoundInfo object.");
		return;
	}

	f_loop = loop;
}


/** \brief This function adds one Envelope to the SoundInfo object
 *
 * This function accepts an envelope to add to the SoundInfo.
 *
 * The Envelope can be used to fade a sound in or out.
 *
 * Note that a SoundInfo is limited to a maximum of 255 envelopes.
 *
 * The input Envelope is duplicated in the SoundInfo.
 *
 * Note that the new envelope is placed where it needs to be so as
 * to be properly ordered by position (since version 1.8.1--in
 * previous versions, additional envelopes would be appended so
 * they had to be added in order.)
 *
 * \param[in] envelope The envelope to add
 */
void SoundInfo::AddEnvelope(const Envelope& envelope)
{
	Envelope	*e, *env;
	long		r;
	int		idx;

	if(f_envelopes.Count() >= 255) {
		f_error_manager.OnError(ErrorManager::ERROR_CODE_ENVELOPE_OVERFLOW, "Too many envelopes for a SoundInfo object.");
		return;
	}

	e = new Envelope(envelope);
	MemAttach(e, sizeof(Envelope), "SoundInfo::AddEnvelope(): duplication of the envelope object");

	// Not too sure whether we need to order those, but most certainly!
	// Now it's done; also we avoid two envelopes at the same position.
	for(idx = f_envelopes.Count() - 1; idx >= 0; --idx) {
		env = dynamic_cast<Envelope *>(f_envelopes.Get(idx));
		r = env->Position() - e->Position();
		if(r < 0) {
			f_envelopes.Set(idx + 1, e);
			return;
		}
		if(r == 0) {
			f_error_manager.OnError(ErrorManager::ERROR_CODE_ENVELOPE_EXISTS, "Another envelope already exists at that position");
			return;
		}
	}

	f_envelopes.Set(-1, e);
}


/** \brief Makes sure that the sound identifier was defined
 *
 * This function ensures that:
 *
 * \li The sound identifier was set to a valid sound identifier;
 * \li The loop counter is not zero;
 * \li The start position is before the end position.
 *
 * \return ErrorManager::ERROR_CODE_NONE or some other ErrorManager::error_code_t
 */
ErrorManager::error_code_t SoundInfo::PreSave(void)
{
	ErrorManager::error_code_t	err;

	if(f_sound_id == 0) {
		f_error_manager.OnError(ErrorManager::ERROR_CODE_INVALID_IDENTIFIER, "A SoundInfo object must be given a valid TagSound object reference.");
		return ErrorManager::ERROR_CODE_INVALID_IDENTIFIER;
	}

	// if stopped, it doesn't matter much that the other info is invalid
	if(!f_stop) {
		if(f_start_position > f_end_position && f_start_position != 0 && f_end_position != 0) {
			err = f_error_manager.OnError(ErrorManager::ERROR_CODE_EMPTY_POSITION_RANGE, "The start position is larger than the end in a SoundInfo object.");
			if(err != ErrorManager::ERROR_CODE_NONE) {
				return err;
			}
		}

		// this should never happen
		if(f_loop == 0) {
			err = f_error_manager.OnError(ErrorManager::ERROR_CODE_LOOP_ZERO, "The loop parameter cannot be zero in a SoundInfo object.");
			if(err != ErrorManager::ERROR_CODE_NONE) {
				return err;
			}
		}
	}

	return ErrorManager::ERROR_CODE_NONE;
}



/** \brief Save the information in the specified Data buffer
 *
 * This function saves the SoundInfo parameters in the specified
 * Data buffer.
 *
 * \param[in,out] data The buffer where the tag is saved
 */
void SoundInfo::Save(Data& data) const
{
	int		idx, max;

	data.PutShort(f_sound_id);
	data.WriteBits(0, 2);
	data.WriteBits(f_stop, 1);
	if(f_stop) {
		// if the sound is being stopped, we don't need any other info
		data.WriteBits(0, 5);
	}
	else {
		max = f_envelopes.Count();

		data.WriteBits(f_no_multiple, 1);
		data.WriteBits(max > 0, 1);
		data.WriteBits(f_loop != 1, 1);
		data.WriteBits(f_start_position != 0, 1);
		data.WriteBits(f_end_position != 0, 1);

		if(f_start_position != 0) {
			data.PutLong(f_start_position);
		}
		if(f_end_position != 0) {
			data.PutLong(f_end_position);
		}
		if(f_loop != 1) {
			data.PutShort(f_loop);
		}
		// when we have envelops, save them all!
		if(max > 0) {
			data.PutByte(max);
			for(idx = 0; idx < max; idx++) {
				dynamic_cast<const Envelope *>(f_envelopes.Get(idx))->Save(data);
			}
		}
	}
}


/////////////////////////////////////////// TagStartSound


/** \class sswf::TagStartSound
 *
 * \brief Class used to start/stop a sound
 *
 * This class is used to start and/or stop a sound in an SWF
 * animation.
 *
 * In order to work properly, you must call
 * sswf::TagStartSound::SetInfo(SoundInfo *sound_info)
 * at least once.
 *
 * \sa sswf::TagStartSound::SetInfo(SoundInfo *sound_info)
 * \sa <a href="../SWFalexref.html#tag_startsound">SWF Alexis' Reference&mdash;Start Sound</a>
 * \sa <a href="../SWFalexref.html#swf_tag">SWF Alexis' Reference&mdash;swf_tag</a>
 */


/** \brief Initialize a start sound tag
 *
 * This function initializes a start sound tag. This means setting
 * the pointer to the sound info to NULL.
 *
 * \param[in] parent The parent of this TagStartSound
 *
 * \sa sswf::TagStartSound::SetInfo(SoundInfo *sound_info)
 */
TagStartSound::TagStartSound(TagBase *parent)
	: TagBase("startsound", parent)
{
	f_sound_info = 0;
}



/** \brief Returns the type flags of this start sound tag
 *
 * The start sound tag is a CONTROL tag.
 *
 * The start sound tag has a reference to a sound (actually
 * defined in the sound info.)
 *
 * \return A set of SWF TYPE flags
 *
 * \sa sswf::TagStartSound::SetInfo(SoundInfo *sound_info)
 */
TagBase::swf_type_t TagStartSound::TypeFlags(void) const
{
	return SWF_TYPE_CONTROL | SWF_TYPE_REFERENCE;
}


/** \brief Defines the sound info of this start sound tag
 *
 * This function saves the specified sound info pointer in
 * the TagStartSound object.
 *
 * \warning
 * The sound_info structure will be used by the TagStartSound until
 * replaced by a new pointer or set to NULL.
 *
 * \par
 * You are responsible to ensure that this structure remains valid
 * until the object does not require it anymore.
 */
void TagStartSound::SetInfo(SoundInfo *sound_info)
{
	// NOTE: should we copy the user object?
	f_sound_info = sound_info;
}


/** \brief Test the validity of the start sound tag
 *
 * This function makes sure that you have set a valid sound info.
 *
 * If the sound info pointer is null, this function returns
 * an error.
 *
 * \return sswf::ErrorManager::ERROR_CODE_START_SOUND_NO_INFO
 * 	if no sound info was defined; no error otherwise
 */
ErrorManager::error_code_t TagStartSound::PreSave(void)
{
	if(f_sound_info == 0) {
		OnError(ErrorManager::ERROR_CODE_START_SOUND_NO_INFO, "Cannot start a sound without info.");
		return ErrorManager::ERROR_CODE_START_SOUND_NO_INFO;
	}

	return f_sound_info->PreSave();
}



/** \brief Save the start sound tag in the Data buffer
 *
 * This function saves the start sound tag in the specified Data buffer.
 * This calls the sswf::SoundInfo::Save(Data& data) function.
 *
 * \param[in] data The Data buffer where the start sound tag is saved
 *
 * \return an error manager code
 *
 * \sa sswf::SoundInfo::Save(Data& data)
 */
ErrorManager::error_code_t TagStartSound::Save(Data& data)
{
	Data		sub_data;

	// the sound info size varies with envelopes
	f_sound_info->Save(sub_data);

	SaveTag(data, SWF_TAG_START_SOUND, sub_data.ByteSize());
	data.Append(sub_data);

	return  ErrorManager::ERROR_CODE_NONE;
}








/////////////////////////////////////////// TagSound


/** \class sswf::TagSound
 *
 * \brief Declare a sound
 *
 * This class defines a sound rate, samples, mono or stereo, 8 or 16 bits, etc.
 *
 * \todo
 * This sound does not yet support for streaming sound.
 *
 * \sa <a href="../SWFalexref.html#tag_definesound">SWF Alexis' Reference&mdash;Define Sound</a>
 * \sa <a href="../SWFalexref.html#swf_tag">SWF Alexis' Reference&mdash;swf_tag</a>
 */


/** \typedef sswf::TagSound::sound_format_t
 *
 * \brief Valid formats of sounds in Flash
 *
 * This type lists all the formats supported by the Flash players.
 *
 * \todo
 * Note that all of these formats are not yet supported by the
 * SSWF library. The ADPCM and NELLYMOSER have no support.
 */


/** \var sswf::TagSound::SOUND_FORMAT_UNKNOWN
 *
 * \brief An undefined or unknown format
 *
 * By default a sound tag is defined as having an unknown format.
 *
 * It is not valid to call SetData() with this value.
 *
 * \sa sswf::TagSound::SetData()
 *
 * <br>
 * <br>
 */


/** \var sswf::TagSound::SOUND_FORMAT_RAW
 *
 * \brief This format is used to save uncompressed sound data
 *
 * Most of the time, sound samples will be compressed to save space.
 * Very small sounds may be saved in an uncompressed format for better
 * quality.
 *
 * This format should not be used with 16 bits data since it is not
 * possible to know for sure whether the sound effects are in little
 * or big endian (it is dependent on the machine on which you created
 * the Flash animation!)
 *
 * <br>
 * <br>
 */


/** \var sswf::TagSound::SOUND_FORMAT_ADPCM
 *
 * \brief This format is used to save compressed sound data using the ADPCM format
 *
 * This is a simple compression scheme somewhat similar in concept to the
 * RLE for images. This is better suited for sound samples of effects than
 * for songs.
 *
 * \todo
 * The libsswf library does not support this compression mode yet.
 *
 * <br>
 * <br>
 */



/** \var sswf::TagSound::SOUND_FORMAT_MP3
 *
 * \brief This format is used to save compressed music data using the MP3 format
 *
 * This is a complex compression scheme somewhat similar in concept to
 * JPEG for images. This is better suited for sound samples of musics than
 * for talk or effects.
 *
 * \todo
 * The libsswf library does not yet support this compression. However, it
 * can load and use an existing MP3 file.
 *
 * <br>
 * <br>
 */


/** \var sswf::TagSound::SOUND_FORMAT_UNCOMPRESSED
 *
 * \brief This format is used to save small uncompressed sound effect with full quality
 *
 * This is the uncompressed format to use with uncompressed data. This format
 * ensures the endianess of the data is known (i.e. little endian.) This can
 * be created and played on any system with no problem.
 *
 * <br>
 * <br>
 */


/** \var sswf::TagSound::SOUND_FORMAT_NELLYMOSER
 *
 * \brief This format is used to save compressed voice with high quality
 *
 * This is the compressed format to use with voices (with no music in the
 * background). Though MP3 can also be used for voices, you general get a
 * better result with this compression scheme for voices.
 *
 * \todo
 * This compression format is not yet supported by the sswf library.
 *
 * <br>
 * <br>
 */







/** \typedef sswf::TagSound::sound_endian_t
 *
 * \brief Valid endians for sound samples
 *
 * This type lists all the endians supported by the sswf library.
 */


/** \var sswf::TagSound::SOUND_ENDIAN_LITTLE
 *
 * \brief The data is in little endian
 *
 * Little endian means that the first byte is the least significant
 * and the last byte is the most significant.
 *
 * Pentium or older MIPS based computers are in little endian.
 * 
 * \code
 * 	unsigned short file_samples[10];
 *	char *ptr = (char *) file_samples;
 *	unsigned short machine_samples[10];
 *
 *	machine_samples[0] = ptr[0] + ptr[1] * 256;
 *	...
 * \endcode
 *
 * <br>
 * <br>
 */


/** \var sswf::TagSound::SOUND_ENDIAN_BIG
 *
 * \brief The data is in big endian
 *
 * Big endian means that the first byte is the most significant
 * and the last byte is the least significant.
 *
 * PowerPC or newer MIPS based computers are in big endian.
 * 
 * \code
 * 	unsigned short file_samples[10];
 *	char *ptr = (char *) file_samples;
 *	unsigned short machine_samples[10];
 *
 *	machine_samples[0] = ptr[0] * 256 + ptr[1];
 *	...
 * \endcode
 *
 * <br>
 * <br>
 */



/** \var sswf::TagSound::SOUND_ENDIAN_SAME
 *
 * \brief The data is in the same endian
 *
 * This is used whenever the data is known to be of the same endian
 * as of the currently running machine. So if you system is a little
 * endian machine, this tells the system to use little endian.
 * 
 * \code
 * 	unsigned short file_samples[10];
 *	unsigned short machine_samples[10];
 *
 *	machine_samples[0] = file_samples[0];
 *	...
 * \endcode
 *
 * <br>
 * <br>
 */



/** \var sswf::TagSound::SOUND_ENDIAN_UNKNOWN
 *
 * \brief The data endianess is not known
 *
 * At this time this value cannot be used. It is not expected that
 * it will be possible to use this value since in all cases we must
 * know the endianess of the samples before to be able to use them.
 * 
 * <br>
 * <br>
 */



/** \var sswf::TagSound::SOUND_ENDIAN_DONTUSE
 *
 * \brief The data is defined on 8 bits
 *
 * For data defined on 8 bits, the endian is not defined and thus
 * it can be marked as "don't use".
 * 
 * <br>
 * <br>
 */






#define	SNDINFMT(width, endian)		((width)|((endian)&7))

/** \brief The few valid sound rates supported by Flash
 *
 * Sound effects in Flash only support these rates.
 *
 * Using an encoding like MP3 can be used to specify different rates
 * in different packets. This is properly supported by newer Flash
 * versions (version 7+)
 *
 * If possible, you should use one of these rates whenever you call
 * the SetData() function.
 *
 * \sa sswf::TagSound::SetData()
 */
const int TagSound::g_sound_rates[4] = { 5512, 11025, 22050, 44100 };


/// used by the MP3 loader
const int TagSound::g_bitrates[2][16] =
{
	{ -1, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 },
	{ -1, 8,  16, 24, 32, 40, 48, 56,  64,  80,  96, 112, 128, 144, 160, -1 }
};


/// used by the MP3 loader
const int TagSound::g_frequencies[4][4] =
{
	{ 11025, -1, -1, -1 },
	{    -1, -1, -1, -1 },
	{ 22050, -1, -1, -1 },
	{ 44100, -1, -1, -1 }
};


/** \brief Initialize a sound tag.
 *
 * This constructor initializes the sound as empty.
 *
 * \param[in] parent The TagHeader where this sound is added
 */
TagSound::TagSound(TagBase *parent)
	: TagBaseID("sound", parent)
{
	// the output compression format
	f_format = SOUND_FORMAT_RAW;

	// the following are all set at the time the SetData() function
	// is called -- it is done that way so the coherency of the
	// data is preserved (changing from stereo to mono would require
	// changing the data!)
	f_stereo = false;
	f_rate = 0;
	f_width = 0;
	f_samples = 0;
	f_data_size = 0;
	f_data_maxsize = 0;
	f_data = 0;
	f_latency_seek = 0;
}


/** \brief Returns the type of this tag: Define with an ID.
 *
 * The TagSound is a definition tag with an identifier
 *
 * \returns SWF_TYPE_DEFINE | SWF_TYPE_HAS_ID
 */
TagBase::swf_type_t TagSound::TypeFlags(void) const
{
	// TODO: with streaming sound effects, the object may have references
	//	 though it will be to itself... thus we should be fine.
	return SWF_TYPE_DEFINE | SWF_TYPE_HAS_ID;
}




/** \brief Set the name of a sound file.
 *
 * This function takes the name of a file representing a sound
 * effect. The file is loaded on the spot and its content is
 * kept in memory.
 *
 * This function calls the LoadWaveFile() with a handle to the
 * file it just opened. If that function fails to read the file,
 * then this function tries again by calling the LoadMP3File().
 *
 * \param[in] filename The name of the file to load
 *
 * \return ErrorManager::ERROR_CODE_NONE when no error occured,
 *	some other ErrorManager::error_code_t on an error
 *
 * \sa sswf::TagSound::LoadWaveFile(FILE *f)
 * \sa sswf::TagSound::LoadMP3File(FILE *f)
 */
ErrorManager::error_code_t TagSound::SetFilename(const char *filename)
{
	int	ec;
	FILE	*f;

	f = fopen(filename, "rb");
	if(f == NULL) {
		return OnError(ErrorManager::ERROR_CODE_IO, "cannot open sound file \"%s\" for reading.", filename);
	}

	ec = LoadWaveFile(f);
	if(ec != 0) {
		rewind(f);
		ec = LoadMP3File(f);
	}

	fclose(f);

	if(ec != 0) {
		return OnError(ErrorManager::ERROR_CODE_IO, "cannot open sound file \"%s\" for reading.", filename);
	}

	return ErrorManager::ERROR_CODE_NONE;
}


/** \brief Load a RIFF or WAVE sound file.
 *
 * This function is called with a file descriptor to read a file which is
 * expected to be a RIFF or WAVE file. If the file cannot be read properly,
 * the function returns with an error (-1).
 *
 * \param[in] f The file descriptor
 *
 * \return 0 when the function succeeds in reading the entire file; -1 otherwise
 */
int TagSound::LoadWaveFile(FILE *f)
{
	unsigned char	header[12];
	sound_wave_t	fmt;
	unsigned long	sz, data_size;
	char		*data;
	int		cnt;

	if(fread(header, 12, 1, f) != 1) {
		return -1;
	}

	if(header[0] != 'R' || header[1] != 'I' || header[ 2] != 'F' || header [ 3] != 'F'
	|| header[8] != 'W' || header[9] != 'A' || header[10] != 'V' || header [11] != 'E') {
		return -1;
	}

	// size is in header[4..7] -- we don't really need it do we?

	// we go through the list of chunks until we got the fmt
	// and data chunks
	data = 0;
	data_size = 0;	// if data is defined, this is too -- avoid warnings
	cnt = 0;
	while(cnt != 3) {
		// this read will fail if we reach the end of the file
		// before we've got the fmt and data chunks
		if(fread(header, 8, 1, f) != 1) {
			MemFree(data);
			return -1;
		}
		if(header[0] == 'f' && header[1] == 'm' && header[2] == 't' && header[3] == ' ') {
			/* we found the format info, read it! */
			if((cnt & 1) != 0) {
				// Ooooops! more than one fmt chunk?!?
				MemFree(data);
				return -1;
			}
			sz = header[4] + (header[5] << 8)
					+ (header[6] << 16) + (header[7] << 24);
			if(sz != sizeof(fmt)) {
				// incompatible/unknown format chunk size
				MemFree(data);
				return -1;
			}
			if(fread(&fmt, sizeof(fmt), 1, f) != 1) {
				// can't read format info
				MemFree(data);
				return -1;
			}
			// on big endian systems it is necessary to swap values here
#if BYTE_ORDER == BIG_ENDIAN
			fmt.format   = swap_short(fmt.format);
			fmt.channels = swap_short(fmt.channels);
			fmt.rate     = swap_int(fmt.rate);
			fmt.width    = swap_short(fmt.width);
			// what isn't swapped we don't use
			//fmt.align  = swap_short(fmt.align);
			//fmt.average_rate = swap_int(fmt.average_rate);
#endif
			// quickly check to see if we understand this info
			if(fmt.format != 1	// only PCM files understood at this time!
			|| (fmt.channels != 1 && fmt.channels != 2)
			|| (fmt.width != 8 && fmt.width != 16)) {
				MemFree(data);
				return -1;
			}
			cnt |= 1;
		}
		else if(header[0] == 'd' && header[1] == 'a' && header[2] == 't' && header[3] == 'a') {
			if((cnt & 2) != 0) {
				// Ooooops! more than one data chunk?!?
				MemFree(data);
				return -1;
			}
			data_size = header[4] + (header[5] << 8)
					+ (header[6] << 16) + (header[7] << 24);
			data = (char *) MemAlloc(data_size, "TagSound::SetFilename(): temporary data buffer");
			if(fread(data, data_size, 1, f) != 1) {
				// can't read data samples
				MemFree(data);
				return -1;
			}
			cnt |= 2;
		}
	}

	// got the format info and (uncompressed) data now
	// note that 8 bits data is unsigned whereas 16 bits
	// is signed!!! data is always in little endian which
	// thus need to be swapped on big endian computers
	return SetData(
		data,
		data_size,
		SOUND_ENDIAN_LITTLE,
		fmt.width == 8 ? 8 : -16,
		fmt.rate,
		fmt.channels == 2);
}



/** \brief Load an MP3 sound file.
 *
 * This function is called with a file descriptor to read a file which is
 * expected to be an MP3 file. If the file cannot be read properly,
 * the function returns with an error (-1).
 *
 * \param[in] f The file descriptor
 *
 * \return 0 when the function succeeds in reading the entire file; -1 otherwise
 */
int TagSound::LoadMP3File(FILE *f)
{
	unsigned char	header[4];
	int		ec, frame_size;

	// TODO: we certainly should check whether
	//	 this looks like an MP3 file...

	// That's an MP3 (hmmm)
	f_format = SOUND_FORMAT_MP3;

	// Not too sure about this one at this time we kind of ignore
	// this parameter...
	//f_latency_seek = 0;

	// MP3s are always 16 bits
	f_width = 16;

	// Restart from the start each time a new file is loaded
	f_data_size = 0;
	f_samples = 0;

	// save the total size of the buffer at this time
	//for(int cnt = 0; cnt  < 50; cnt++) {
	for(;;) {
		// get the size of frame
		ec = CheckMP3Header(f, header, frame_size);
		if(ec != 0) {
			break;
		}

		// now that we have the size, make sure the data
		// buffer is large enough
		if(f_data_size + frame_size > f_data_maxsize) {
			// large blocks will speed things up for large MP3 files
			f_data_maxsize = (f_data_size + frame_size + 1024 * 1024 - 1) & -1024 * 1024;
			f_data = (unsigned char *) MemRealloc(f_data, f_data_maxsize, "TagSound::LoadMP3File() -- frame buffer");
		}

		// copy the header
		f_data[f_data_size + 0] = header[0];
		f_data[f_data_size + 1] = header[1];
		f_data[f_data_size + 2] = header[2];
		f_data[f_data_size + 3] = header[3];

		// finally read the frame in our buffer
		if(fread(f_data + f_data_size + 4, 1, frame_size - 4, f) != (size_t) (frame_size - 4)) {
			return -1;
		}

		// move to the next frame
		f_data_size += frame_size;
	}

	// the value 2 represents EOF
	return ec == 2 ? 0 : ec;
}


/** \brief Internal function to test the validity of an MP3 header
 *
 * This function checks the validity of an MP3 header.
 *
 * \param[in] f The file descriptor of the MP3 file to check
 * \param[in] header A pointer to the header buffer
 * \param[out] frame_size Set to the frame size
 *
 * \return 0 when no error occurs; -1 on errors
 */
int TagSound::CheckMP3Header(FILE *f, unsigned char *header, int& frame_size)
{
	int	ec, mode;
	int	bitrate, frequency;

	// Read the first or next header
	// (we always try to re-sync.)
	ec = ReadMP3Header(f, header);
	if(ec != 0) {
		return ec;
	}

        // Only layer 3 files are valid for the flash player
        if(((header[1] >> 1) & 3) != 1) {
		return -1;
	}

	mode = (header[1] >> 3) & 3;

	// Get bitrate from bitrates array -- depends on mode as well
	bitrate = g_bitrates[mode == 3 ? 0 : 1][(header[2] >> 4) & 15];

	// Get frequency -- also depends on mode
	frequency = g_frequencies[mode][(header[2] >> 2) & 3];

	// make sure these are valid
	if(bitrate == -1 || frequency == -1) {
		return -1;
	}

	// WARNING: not too sure whether the frequence and stereo
	// information should reflect the first or last MP3 frame
	// at this time it's the last (but I would guess it won't
	// change very much in a single MP3?)
	switch(frequency) {
	//case  5512: f_rate = 0; break; -- this is not supported in MP3
	case 11025: f_rate = 1; break;
	case 22050: f_rate = 2; break;
	case 44100: f_rate = 3; break;

	// we can't change the MP3 rate without decompressing
	// and recompressing which we don't currently support
	// [also the table only gives us the three valid values
	// anyway...]
	default:
		return -1;

	}

	// Channel mode
	f_stereo = ((header[3] >> 6) & 3) != 3;

	// Increment the samples counter
	f_samples += mode == 3 ? 1152 : 576; // V2 and V1 sample counts

	// Calculate the frame size (including the pad)
	frame_size = (mode == 3 ? 144 : 72) * bitrate * 1000 / frequency
			+ ((header[2] >> 1) & 1);

	return 0;
}


/** \brief Internal function used to read an MP3 header
 *
 * This function reads one MP3 header.
 *
 * \note
 * If we can't find the next Sync. then we tell the caller that we found the
 * end of the file.
 *
 * \todo
 * These MP3 functions badly need to handle a large buffer instead of reading the
 * file byte per byte!
 *
 * \return 0 when no error occured; 2 when the end of the file is reached
 * 	and -1 when an error occurs
 */
int TagSound::ReadMP3Header(FILE *f, unsigned char *header)
{
	int	r;

	// TODO: we may want to load the entire file at once
	//	 so it is much faster than this -- imagine that
	//	 we read a 2Mb file 1 byte at a time...

	// sync to first or next frame
	do {
		r = fread(header, 1, 1, f);
		if(r != 1) {
			goto err;
		}
	} while(header[0] != 255);

	do {
		r = fread(header + 1, 1, 1, f);
		if(r != 1) {
			goto err;
		}
	} while((header[1] & 0xE0) != 0xE0);

	r = fread(header + 2, 1, 2, f);
	if(r == 2) {
		return 0;
	}
err:
	return r >= 0 ? 2 : -1;
}


/** \brief Change the format of the tag
 *
 * This function changes the format of the samples.
 *
 * \note
 * If you loaded an MP3 file, then the format cannot be changed.
 * This may change in a future version of the library.
 *
 * \param[in] format The new format.
 */
void TagSound::SetFormat(sound_format_t format)
{
	// SetFormat() doesn't work when it's already MP3...
	// [this is because we don't support the compression
	// we only support loading an existing MP3 and saving
	// it as is...]
	if(f_format == SOUND_FORMAT_MP3) {
		if(format != SOUND_FORMAT_MP3) {
			OnError(ErrorManager::ERROR_CODE_FORMAT_LOCKED, "cannot change sound format when it is set to MP3.");
		}
		return;
	}

	f_format = format;
}


/** \brief Change the format of the tag
 *
 * This function reads one sample from a buffer expected to be of format
 * in_fmt and adjusts it as required.
 *
 * The function is used to resample input samples to samples valid
 * for the specified mode (rate, format, mono or stereo, etc.)
 *
 * \param[in] data The buffer to read from
 * \param[in] adjust The adjustment to make to the input value unsigned
 * \param[in] in_fmt The format of the input data
 *
 * \return The adjusted sample
 */
short TagSound::ReadSample(const unsigned char *data, unsigned short adjust, int in_fmt)
{
	switch(in_fmt) {
	case SNDINFMT(8, SOUND_ENDIAN_LITTLE):
	case SNDINFMT(8, SOUND_ENDIAN_BIG):
	case SNDINFMT(8, SOUND_ENDIAN_SAME):
	case SNDINFMT(8, SOUND_ENDIAN_DONTUSE):
		// 8 bits are returned on 8 bits.
		return (signed char) (data[0] + adjust);

	case SNDINFMT(16, SOUND_ENDIAN_LITTLE):
#if BYTE_ORDER == LITTLE_ENDIAN
	case SNDINFMT(16, SOUND_ENDIAN_SAME):
#endif
		return (data[0] + data[1] * 256) + adjust;

	case SNDINFMT(16, SOUND_ENDIAN_BIG):
#if BYTE_ORDER == BIG_ENDIAN
	case SNDINFMT(16, SOUND_ENDIAN_SAME):
#endif
		return (data[1] + data[0] * 256) + adjust;

	case SNDINFMT(24, SOUND_ENDIAN_LITTLE):
#if BYTE_ORDER == LITTLE_ENDIAN
	case SNDINFMT(24, SOUND_ENDIAN_SAME):
#endif
		return (data[1] + data[2] * 256) + adjust;

	case SNDINFMT(24, SOUND_ENDIAN_BIG):
#if BYTE_ORDER == BIG_ENDIAN
	case SNDINFMT(24, SOUND_ENDIAN_SAME):
#endif
		return (data[2] + data[1] * 256) + adjust;

	case SNDINFMT(32, SOUND_ENDIAN_LITTLE):
#if BYTE_ORDER == LITTLE_ENDIAN
	case SNDINFMT(32, SOUND_ENDIAN_SAME):
#endif
		return (data[2] + data[3] * 256) + adjust;

	case SNDINFMT(32, SOUND_ENDIAN_BIG):
#if BYTE_ORDER == BIG_ENDIAN
	case SNDINFMT(32, SOUND_ENDIAN_SAME):
#endif
		return (data[1] + data[0] * 256) + adjust;

	}

	/* the list of valid formats is checked sooner so we should
	 * never reach this place!
	 */

	/*NOTREACHED*/
	return 0;	/* avoid warnings */
}



/** \brief Function used to resample a set of samples from some format to another.
 *
 * This function is used whenever the SetData() or similar function is called with
 * data in a format which is not directly supported by Flash. Then it is resampled
 * to a way that works in Flash.
 *
 * This means duplicating or removing some of the input samples. When removing samples,
 * an average of a certain number of samples will be computed. This is not always the
 * proper method, but in most cases it works well.
 *
 * \param[in] snd The destination buffer
 * \param[in] out_bytes Size of one sample in the destination buffer (snd + out_bytes moves the pointer to the 2nd sample)
 * \param[in] src The source buffer
 * \param[in] size The size of the source buffer in bytes
 * \param[in] in_bytes Size of one sample in the source buffer (src + in_bytes moves the pointer to the 2nd sample)
 * \param[in] max The maximum number of samples to work on (i.e. size of the destination buffer)
 * \param[in] fix How much the samples need to be fixed to adjust for the rate difference
 * \param[in] adjust The adjustment to make the input value unsigned
 * \param[in] in_fmt The format of the samples in the input buffer
 *
 * \sa sswf::TagSound::ReadSample
 */
void TagSound::Resample(unsigned char *snd, unsigned int out_bytes, const unsigned char *src, size_t size, unsigned int in_bytes, size_t max, double fix, unsigned short adjust, int in_fmt)
{
	size_t			idx;
	double			p1, p2, start, end, s, inv_fix;
	unsigned long		pi1, pi2, cnt;
	short			sample;
	const unsigned char	*in, *end_src;

	out_bytes -= f_width == 16 ? 2 : 1;

	inv_fix = 1.0 / fix;
	end_src = src + size;
	for(idx = 0; idx < max; idx++) {
		// start position & end position
		p1 = (double) idx * inv_fix;
		p2 = p1 + inv_fix;

		// check with integral values
		pi1 = (unsigned long) floor(p1);
		pi2 = (unsigned long) floor(p2);

		if(pi1 == pi2) {
			// in this case we only use part of one sample
			start = p2 - p1;
			end = 0.0;
			cnt = 0;
		}
		else {
			// in this case we need multiple samples
			// we may also use part of two samples
			// (when cnt == 0)
			start = 1.0 - p1 + (double) pi1;
			end = p2 - (double) pi2;
			cnt = pi2 - pi1 - 1;
		}

		// skip to the 1st byte we want to read
		in = src + pi1 * in_bytes;

		// read the first sample (note that start can be 1.0)
		assert(in < end_src, "TagSound::Resample(): source data pointer out of bounds");
		s = (double) ReadSample(in, adjust, in_fmt) * start;
		in += in_bytes;

		// when multiple samples are required to compute the
		// new sample, then read them all right here
		while(cnt > 0) {
			cnt--;
			assert(in < end_src, "TagSound::Resample(): source data pointer out of bounds");
			s += (double) ReadSample(in, adjust, in_fmt);
			in += in_bytes;
		}

		// if we have something at the end (end can be 0.0 but never 1.0)
		// and we still have samples in the input, use that bit too
		if(end > 0.0 && in < end_src) {
			s += (double) ReadSample(in, adjust, in_fmt) * end;
		}

		// note that there can't be an overflow
		// or I'm really bad at math! so the
		// following works without having to test
		// for overflows
		sample = (short) rint(s * fix);

		*snd++ = (unsigned char) sample;
		if(f_width == 16) {
			*snd++ = (unsigned char) (sample >> 8);
		}

		// in stereo the following skips the other channel too
		snd += out_bytes;
	}
}


/** \brief Returns the number of samples in that TagSound
 *
 * This function returns the current number of samples defined
 * in this TagSound.
 *
 * \return The number of samples or zero when none
 */
size_t TagSound::GetSamplesCount(void) const
{
	return f_samples;
}


/** \brief Set sample data in a TagSound object.
 *
 * This function sets the specified user defined data in the TagSound
 * object.
 *
 * The data is expected to be valid sound samples.
 *
 * The source samples must be mono or stereo, on 8, 16, 24 or 32 bits,
 * at a reasonable rate (1024 to 88000), in little or big endian.
 *
 * If the input data has samples which are signed (i.e. -128 to +127
 * or -32768 to +32767,) then the width must be passed negative (i.e.
 * -8, -16, -24 or -32). Positive values indicate that the input samples
 * are unsigned (i.e. 0 to 255 or 0 to 65535.)
 *
 * Flash understands only a very limited set of rates:
 *
 *	\li 5512
 *	\li 11025
 *	\li 22050
 *	\li 44100
 *
 * This function understands any other rate, yet the sample data will be
 * resampled when another rate is specified.
 *
 * Note that Flash only understands 8 or 16 bits data. The low bits of 24
 * and 32 bits samples are dropped.
 *
 * Stereo samples are expected to be interleaved with the left side sample
 * first.
 *
 * \param[in] data The source data samples
 * \param[in] size The number of bytes in the source buffer (should be a proper multiple)
 * \param[in] endian One of the sound endian (little, big, same or unused)
 * \param[in] width 8, 16, 24 or 32; or negative to indicate signed data
 * \param[in] rate Any rate between 1024 and 88000 inclusive
 * \param[in] stereo Whether mono (false) or stereo (true) samples are passed
 *
 * \return zero when no error occured, -1 when something happens
 */
int TagSound::SetData(const void *data, size_t size, sound_endian_t endian, int width, unsigned int rate, bool stereo)
{
	unsigned int		in_bytes, out_bytes, good_rate, in_fmt;
	unsigned short		sample, adjust;
	unsigned char		*snd;
	size_t			in_samples, in_count, cnt, out_samples;
	double			fix;
	bool			signed_data;

	// SetData() doesn't work yet for MP3
	if(f_format == SOUND_FORMAT_MP3) {
		return -1;
	}

	if((signed_data = width < 0)) {
		width = -width;
	}

	// in release mode, these are checked below and the
	// function will return 1.
	assert(width == 8 || width == 16 || width == 24 || width == 32, "TagSound::SetData(): the width of a sound must be 8, 16, 24 or 32, the value %d can't be used", width);
	assert(rate >= 1024 && rate <= 88000, "TagSound::SetData(): the samples rate must be between 1024 and 88000, the value %d is being rejected", rate);
	assert(endian >= 0 && endian < SOUND_ENDIAN_UNKNOWN, "TagSound::SetData(): unknown endianess: %d", endian);

	/* first we compute what we need (i.e. the size of the buffer we will keep) */
	switch(width) {		// bytes per samples
	case  8: in_bytes = 1; out_bytes = 1; f_width =  8; break;
	case 16: in_bytes = 2; out_bytes = 2; f_width = 16; break;
	case 24:
	case 32: in_bytes = 4; out_bytes = 2; f_width = 16; break;
	default: return -1;
	}

	/* compute the number of samples in the input buffer */
	in_count = in_samples = size / in_bytes;

	if((f_stereo = stereo)) {
		  in_bytes *= 2;
		 out_bytes *= 2;
		in_samples /= 2;
	}

	// assert the # of samples, not the size since in_samples could be
	// zero after the division of size by in_bytes
	assert(data != 0 && in_samples > 0, "TagSound::SetData(): a sound data buffer can't be empty or a null pointer");
	if(data == 0 || in_samples == 0) {
		return -1;
	}

	/* compute a correct rate if not already so */
	switch(rate) {
	case  5512: f_rate = 0; break;
	case 11025: f_rate = 1; break;
	case 22050: f_rate = 2; break;
	case 44100: f_rate = 3; break;
	default:
		if(rate < 1024) {
			return -1;
		}
		if(rate < 6063) {
			f_rate = 0;
		}
		else if(rate < 12127) {
			f_rate = 1;
		}
		else if(rate < 24255) {
			f_rate = 2;
		}
		else if(rate > 88000) {
			return -1;
		}
		else {
			f_rate = 3;
		}
	}
	good_rate = g_sound_rates[f_rate];

	/* if the rate is the same, then we can compute the output buffer as is */
	if(good_rate == rate) {
		/* use input buffer as is (beside endianess) */
		out_samples = in_samples;
		fix = 0.0;		// unused in this case, avoid warnings
	}
	else {
		/* oops! user had a strange input file? got to fix the samples */
		fix = (double) good_rate / (double) rate;
		out_samples = (size_t) ceil((double) in_samples * fix);
	}

	in_fmt = SNDINFMT(width, endian);

	switch(in_fmt) {
	case SNDINFMT(8, SOUND_ENDIAN_LITTLE):
	case SNDINFMT(8, SOUND_ENDIAN_BIG):
	case SNDINFMT(8, SOUND_ENDIAN_SAME):
	case SNDINFMT(8, SOUND_ENDIAN_DONTUSE):
		adjust = signed_data ? 0 : -128;
		break;

	case SNDINFMT(16, SOUND_ENDIAN_LITTLE):
	case SNDINFMT(16, SOUND_ENDIAN_BIG):
	case SNDINFMT(16, SOUND_ENDIAN_SAME):
	case SNDINFMT(24, SOUND_ENDIAN_LITTLE):
	case SNDINFMT(24, SOUND_ENDIAN_BIG):
	case SNDINFMT(24, SOUND_ENDIAN_SAME):
	case SNDINFMT(32, SOUND_ENDIAN_LITTLE):
	case SNDINFMT(32, SOUND_ENDIAN_BIG):
	case SNDINFMT(32, SOUND_ENDIAN_SAME):
		adjust = signed_data ? 0 : -32768;
		break;

	default:
		return -1;

	}

	/* the f_data buffer size can now be computed */
	MemFree(f_data);
	f_samples = 0;
	f_data = (unsigned char *) MemAlloc(out_samples * out_bytes, "TagSound::SetData() -- array of samples");

	/* if we have the same rate, do it quick! */
	snd = f_data;
	if(good_rate == rate) {
		cnt = in_count;
		if(stereo) {
			// we do all at once, so we need to have the proper
			// number of bytes not taking the stereo in account
			in_bytes /= 2;
		}
		do {
			// read one sample
			sample = ReadSample((const unsigned char *) data, adjust, in_fmt);
			data = (char *) data + in_bytes;
			// save in our buffer in little endian and increase
			// the snd pointer accordingly
			*snd++ = (unsigned char) sample;
			if(f_width == 16) {
				*snd++ = (unsigned char) (sample >> 8);
			}
			cnt--;
		} while(cnt > 0);
	}
	else if(stereo) {
		// resample stereo sound (resample left then right track)
		Resample(snd, out_bytes, (const unsigned char *) data, size, in_bytes, out_samples, fix, adjust, in_fmt);
		Resample(snd + out_bytes / 2, out_bytes, (const unsigned char *) data + in_bytes / 2, size, in_bytes, out_samples, fix, adjust, in_fmt);
	}
	else {
		// resample non-stereo sound (one call to the loop is enough)
		Resample(snd, out_bytes, (const unsigned char *) data, size, in_bytes, out_samples, fix, adjust, in_fmt);
	}

	f_samples = out_samples;

	return 0;
}



/** \brief Change the current samples to mono.
 *
 * Force the samples to be mono. The average of the left and right samples
 * is computed.
 *
 * \note
 * At this time, it is not possible to transform an MP3 set of samples to
 * mono. This would require a decompression and recompression which is not
 * yet available.
 */
void TagSound::SetMono(void)
{
	unsigned char	*in, *out;
	size_t		idx;
	unsigned short	sample;

	// SetMone() doesn't work yet for MP3
	if(f_format == SOUND_FORMAT_MP3) {
		OnError(ErrorManager::ERROR_CODE_FORMAT_LOCKED, "cannot change sound format when it is set to MP3.");
		return;
	}

	if(f_stereo && f_samples > 0) {
		// the user is asking us to remove the L+R and generate a Mono
		// track instead, so what we do is Mono = (L+R)/2
		in = out = f_data;
		if(f_width == 8) {
			for(idx = 0; idx < f_samples; idx++, in += 2, out++) {
				out[0] = (in[0] + in[1]) / 2;
			}
		}
		else {
			for(idx = 0; idx < f_samples; idx++, in += 4, out += 2) {
				sample = (in[0] + in[2] + ((in[1] + in[3]) << 8)) / 2;
				out[0] = (unsigned char) sample;
				out[1] = (unsigned char) (sample >> 8);
			}
		}
		f_stereo = false;
	}
}



/** \brief Force the samples to 8 bits.
 *
 * This function transforms the current samples from 16 to 8 bits. If
 * the samples already are set at 8 bits, then nothing happens.
 *
 * This function does not try to adjust the sound, it simply drops the
 * lower 8 bits of each sample.
 *
 * \note
 * It is not possible to transform an MP3 set of samples to 8 bits.
 */
void TagSound::Set8Bits(void)
{
	unsigned char	*in, *out;
	size_t		idx;

	// Set8Bits() doesn't work yet for MP3
	if(f_format == SOUND_FORMAT_MP3) {
		OnError(ErrorManager::ERROR_CODE_FORMAT_LOCKED, "cannot change sound format when it is set to MP3.");
		return;
	}

	if(f_width == 16 && f_samples > 0) {
		// the user is asking us to make the data 8 bits instead of 16
		// this is really simple: we only keep the top 8 bits!
		in = out = f_data;
		if(f_stereo) {
			for(idx = 0; idx < f_samples; idx++, in += 4, out += 2) {
				out[0] = in[1];
				out[1] = in[3];
			}
		}
		else {
			for(idx = 0; idx < f_samples; idx++, in += 2, out++) {
				out[0] = in[1];
			}
		}
		f_width = 8;
	}
}


/** \brief Check that the sound can be saved the way it is.
 *
 * This function checks that the sound can properly be saved.
 *
 * This means that all the parameters such as the width,
 * format, stereo, etc. all are acceptable.
 *
 * \return ErrorManager::ERROR_CODE_NONE when the sound is valid;
 * 	some other ErrorManager::error_code_t otherwise
 */
ErrorManager::error_code_t TagSound::PreSave(void)
{
	// no effect with an undefined sound effect
	if(f_samples <= 0) {
		return ErrorManager::ERROR_CODE_NONE;
	}

	if(f_width == 8 && f_format != SOUND_FORMAT_RAW && f_format != SOUND_FORMAT_UNCOMPRESSED) {
		// any compressed data must be 16 bits,
		// we need a function to transform 8 bits to 16 bits
		// (which may mean a decompression + re-compression...)
		OnError(ErrorManager::ERROR_CODE_COMPRESSED_SOUND_8BITS, "cannot generate compressed data which is not 16 bits.");
		return ErrorManager::ERROR_CODE_COMPRESSED_SOUND_8BITS;
	}

	switch(f_format) {
	case SOUND_FORMAT_ADPCM:
		// TODO: until this is supported, we return an error;
		//	 the minimum version for ADPCM is 2
		OnError(ErrorManager::ERROR_CODE_UNSUPPORTED_SOUND_FORMAT, "ADPCM is not supported yet.");
		return ErrorManager::ERROR_CODE_UNSUPPORTED_SOUND_FORMAT;

	case SOUND_FORMAT_RAW:
		MinimumVersion(2);
		break;

	case SOUND_FORMAT_MP3:
	case SOUND_FORMAT_UNCOMPRESSED:
		MinimumVersion(4);
		break;

	case SOUND_FORMAT_NELLYMOSER:
		MinimumVersion(6);
		// TODO: until this is supported, we return an error
		OnError(ErrorManager::ERROR_CODE_UNSUPPORTED_SOUND_FORMAT, "NELLYMOSER is not supported yet.");
		return ErrorManager::ERROR_CODE_UNSUPPORTED_SOUND_FORMAT;

	default:
		OnError(ErrorManager::ERROR_CODE_UNSUPPORTED_SOUND_FORMAT, "Unknown sound format not supported.");
		return ErrorManager::ERROR_CODE_UNSUPPORTED_SOUND_FORMAT;

	}

	return ErrorManager::ERROR_CODE_NONE;
}


/** \brief Save the TagSound in the specified Data buffer
 *
 * This function saves the sound samples in the specified Data buffer.
 *
 * \param[in,out] data The data buffer where the tag is saved
 *
 * \return ErrorManager::ERROR_CODE_NONE when the save succeeds
 * 	and another ErrorManager::error_code_t otherwise
 */
ErrorManager::error_code_t TagSound::Save(Data& data)
{
	Data		sub_data;
	unsigned int	size;

	// no effect with an undefined sound effect
	if(f_samples <= 0) {
		return ErrorManager::ERROR_CODE_NONE;
	}

	// TODO: the following doesn't support streaming sound...

	SaveID(sub_data);

	// info
	sub_data.WriteBits(f_format, 4);
	sub_data.WriteBits(f_rate, 2);
	sub_data.WriteBits(f_width == 16 ? 1 : 0, 1);
	sub_data.WriteBits(f_stereo, 1);

	switch(f_format) {
	case SOUND_FORMAT_RAW:			// though we don't check the processor, we save this in little endian!!!
	case SOUND_FORMAT_UNCOMPRESSED:		// always little endian (V4.x)
		// the size in bytes of the f_data buffer
		size = f_samples;
		if(f_stereo) {
			size *= 2;
		}
		if(f_width == 16) {
			size *= 2;
		}
		sub_data.PutLong(f_samples);
		sub_data.Write(f_data, size);
		break;

	case SOUND_FORMAT_MP3:
		sub_data.PutLong(f_samples);
		sub_data.PutShort(f_latency_seek);
		sub_data.Write(f_data, f_data_size);
		break;

	// TODO: add support for ADPCM & NellyMoser

	default:
		assert(0, "the f_format (%d) is accepted in PreSave() but not in Save()", f_format);
		return OnError(ErrorManager::ERROR_CODE_INTERNAL_ERROR, "the f_format (%d) is accepted in PreSave() but not in Save()", f_format);

	}

	SaveTag(data, SWF_TAG_DEFINE_SOUND, sub_data.ByteSize());
	data.Append(sub_data);

	return ErrorManager::ERROR_CODE_NONE;
}





/* The following options fold the documentation; use 'zi' to turn on and off
 *
 * vim: foldexpr=getline(v\:lnum)!~'^/\\*\\*'&&getline(v\:lnum)!~'^\ \\*'?0\:1 foldcolumn=2 foldmethod=expr
 */
