//	StreamingSound.h - Class which streams a sound file from disk.
//  ----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2012  Niall Moody
//	
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#ifndef STREAMINGSOUND_H_
#define STREAMINGSOUND_H_

#include "c0xHeaders.h"

#include <stdint.h>
#include <string>
#include <map>

class SoundFileLoader;
typedef shared_ptr<SoundFileLoader> SoundFileLoaderPtr;

///	Class which streams a sound file from disk.
/*!
	Internally, the class has two separate ways of generating sound: it can
	stream a sound file from disk via a SoundFileLoader, or (if the sound
	file is short enough) it can stream the contents of a pre-loaded buffer
	held in memory.

	If streaming, you'll need to call the threadUpdate() method regularly from
	a thread separate from the audio thread to actually stream the data from
	disk.
 */
class StreamingSound
{
  public:
	///	Constructor.
	StreamingSound();
	///	Destructor.
	~StreamingSound();

	///	Writes the sound's audio to the passed-in buffer.
	void getAudio(float *buffer, int numSamples);

	///	Returns true if the sound is currently playing.
	bool getActive() const {return (fadeVal > 0.0f);};

	///	Starts the sound streaming from disk using a SoundFileLoader.
	/*!
		Note: to ensure sounds can start playing without delay, StreamingSound
		makes use of SoundFileLoader's getInitialBuffers() method. If you have
		not filled out the initial buffers, you will hit an assert when you call
		this method.

		Also note: StreamingSound assumes the buffers are stereo interleaved; if
		you only fill them with mono data you can expect things to mess up
		(i.e.: the sound will play twice as fast due to its having been read
		into two interleaved buffers).
	 */
	void startPlaying(SoundFileLoaderPtr loader);
	///	Starts the sound playing from a pre-loaded buffer.
	/*!
		\sa The other startPlaying regarding the buffer.
	 */
	void startPlaying(float *buffer,
					  int32_t size,
					  const std::wstring& soundName);

	///	Stops the sound playing.
	void stopPlaying();

	///	Tells the sound to fade in (startPlaying) and out (stopPlaying), or not.
	void setFade(bool val);
	///	Sets the sound's level.
	/*!
		\param val The new level (0->1).
		\param ramp If true, this will make it ramp to the new level to avoid
		clicks.
	 */
	void setLevel(float val, bool ramp = true);
	///	Sets how the sound should be panned (0->1).
	void setPan(float val);
	///	Tells the sound to loop, or not.
	void setLoop(bool val);

	///	Sets the samplerate to play at.
	void setSamplerate(float val);

	///	Used to stream the audio data from disk when using a SoundFileLoader.
	/*!
		Note: You must call this regularly if you're using a SoundFileLoader.
	 */
	void threadUpdate();

	///	Callback which can be called at certain points during the sound's playback.
	class Callback
	{
	  public:
		///	Destructor.
		virtual ~Callback() {};

		///	Called when the playback reaches a particular point in the sound file.
		/*!
			\param name The name of the sound file.
			\param time The time (in seconds) within the sound file. If this is
			-1.0f it means we have reached the end of the sound file.

			Note that this will be called from the audio thread, so don't do
			anything complex like allocating memory.
		 */
		virtual void reachedTime(const std::wstring& name, float time) = 0;
	};
	///	Registers a callback to be called at certain points within the file.
	/*!
		\param time The time (in seconds) at which the callback should be
		called. If this is -1.0f the callback will be called when playback has
		reached the end of the file.
		\param callback The callback to call.
	 */
	void registerCallback(float time, Callback *callback);
	///	Removes all instances of callback from our vector of callbacks.
	void unregisterCallback(Callback *callback);
	///	Unregisters all callbacks for this sound.
	void clearCallbacks();

	///	The size of the buffers we use for streaming.
	enum
	{
		BufferSize = 11025,
		NumBuffers = 3
	};
  private:
	///	Helper method. Calls any callbacks which want to know when we've reached the end of the file.
	inline void callFinished(const std::wstring& name)
	{
		std::multimap<float, Callback *>::iterator it = callbacks.find(-1.0f);

		for(it=callbacks.lower_bound(-1.0f);
			it!=callbacks.upper_bound(-1.0f);
			++it)
		{
			it->second->reachedTime(name, -1.0f);
		}
	}
	///	Helper method. Calls any callbacks which may be interested in the current time.
	inline void callTime(const std::wstring& name)
	{
		while(callbackIterator->first < callbackTime)
		{
			callbackIterator->second->reachedTime(name, callbackIterator->first);

			++callbackIterator;
			if(callbackIterator == callbacks.end())
				break;
		}
	}

	///	The SoundFileLoader used to fill streamingBuffers.
	SoundFileLoaderPtr soundFileLoader;
	///	The streaming buffers (stereo interleaved).
	float streamingBuffers[NumBuffers][BufferSize * 2];
	///	Which streaming buffer we're currently reading from.
	int currentBuffer;
	///	Our position within the current buffer.
	int streamingPos;
	///	Which streaming buffers need re-loaded.
	bool clearedBuffers[NumBuffers];

	///	The pre-loaded buffer (if we're not using a SoundFileLoader).
	float *preLoadedBuffer;
	///	The size of the pre-loaded buffer.
	int32_t preLoadedSize;
	///	The name of the pre-loaded audio.
	std::wstring preLoadedName;

	///	Our position within the file (in samples, doesn't account for the stereo nature of the buffers).
	/*!
		This is used by threadUpdate(). Don't touch it in getAudio().
	 */
	int64_t position;
	///	Our sample position in the file (getAudio()'s version).
	int64_t readPos;
	///	Whether we're looping.
	bool loop;

	///	The sound's level.
	float level;
	///	The sound's intended level (so we can ramp when the level changes).
	float newLevel;
	///	How the sound is panned.
	float pan;

	///	The samplerate we're playing at.
	float samplerate;

	///	A callback to be called at certain points within the file.
	std::multimap<float, Callback *> callbacks;
	///	The current time within the file in seconds.
	float callbackTime;
	///	Our current iterator within callbacks.
	/*!
		We use this to determine when to call our callbacks. If callbackTime is
		greater than this iterator's key, we call its callback, increment it and
		repeat until we reach a key which is greater than callbackTime.
	 */
	std::multimap<float, Callback *>::iterator callbackIterator;

	///	If we fade in and out on sound start/stop.
	bool fade;
	///	For fading in/out.
	float fadeVal;
	///	If we're fading in (false) or fading out (true).
	bool fadeDir;
};

#endif
