////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 RWS Inc, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as published by
// the Free Software Foundation
//
// 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
///////////////////////////////////////////////////////////////////////////////
//
//	snd.cpp
//
// History:
//		04/28/95 JMI	Started.
//
//		05/22/95	JMI	Added GetPos().
//
//		06/08/95	JMI	Added GetTime().
//
//		06/14/95	JMI	Added ability to use any number of buffers to stream.
//							This is limited, of course, by the capabilities of BLUE.
//
//		07/15/95	JMI	Added loop ability through public member m_sLoop.
//
//		07/31/96	JMI	Now responds to BLU_SNDMSG_DATA instead of
//							RSP_SNDMSG_OK/PREPLAYERR/POSTPLAYERR.
//
//		08/26/96	JMI	Changed BLU_SNDMSG_* to RSP_SNDMSG_*.  Removed
//							lPrimeInterval parm from play.  It has not been used
//							since 07/15/95, I think.
//
//		08/30/96	JMI	Abort() now calls m_mix.Suspend() as it should have been.
//
//		09/03/96	JMI	Adapted to newest revision of Blue Sound API (which
//							removed rspStart/StopSoundOutCallbacks,
//							rspIsSoundOutCallingBack, and callback messages
//							(RSP_SNDMSG_DONE and RSP_SNDMSG_DATA).
//
//		10/27/96 MJR	Fixed "unused variable" warnings.
//
//		10/28/96	JMI	Removed unused variable lPrimeInterval.
//
//		10/30/96	JMI	Changed:
//							Old label:		New label:
//							=========		=========
//							CMix				RMix
//							RSnd				RSnd
//							MIX_STATE_*		*
//							MIX_MSG_*		*
//							SND_STATE_*		*				Macros changed to enum.
//
//		01/29/97	JMI	Added callback on done.
//
//		04/02/97	JMI	When I added the callback, I forgot to initialize it in
//							Init().  Fixed.
//
//		05/09/97	JMI	Added ability to loop sub samples.
//
//		05/13/97	JMI	Casted instances of warning C4018 (signed/unsigned mismatch)
//							to make MSVC 4.1(Alpha) happy (these seem to fall under
//							Warning Level 3 in 4.1(Alpha) but not in 4.2(Intel)).
//
//		07/15/97 JRD	Added members to hold sound volume information.  Made
//							sure callbacks updated volume levels and all levels
//							were passed on.
//
//		07/30/97	JMI	Added ASSERTs so stupid people (let's call one of them
//							JMI) don't pass loop points that exceed the overall size
//							of the sample buffer causing cool music.
//							Also, added if's to check these in non-TRACENASSERT mode.
//
//		08/01/97	JMI	End loop time parameter to Play() was not working as
//							stated for 0 case b/c it had a < 0 instead of < 1.
//
//		08/11/97	JMI	Placed Sound Done callback such that the user can still
//							access the sample safely, if desired.
//
//		09/25/97	JMI	Now PlayCall() only calls the callback if Abort() was
//							not called (Abort() calls the callback).
//
//////////////////////////////////////////////////////////////////////////////
//
// This thing uses blue to play or stream a music file.  Currently only WAV
// files are supported, but I tried to make it easy for it to be modified to
// autodetect and load other wave files.
//
//////////////////////////////////////////////////////////////////////////////

#include "System.h"

#ifdef PATHS_IN_INCLUDES
#include "GREEN/Snd/snd.h"
#include "BLUE/Blue.h"
#else
#include "snd.h"
#include "Blue.h"
#endif // PATHS_IN_INCLUDES

#include <kos.h>
#include "dc\sound\sound.h"
#include "dc\sound\sfxmgr.h"
#include "dc\sound\aica_comm.h"
#include "aica_cmd_iface.h"

#include "../../../../AICA/aica.h"
#include "../../../../AICA/aica_lowlevel.h"

// todo: clean up
extern CAicaSoundPack *g_AicaSounds;

#define GLOBAL_AUDIO_SCALE .7f

//////////////////////////////////////////////////////////////////////////////
// Initialize static member variables.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Macros.
//////////////////////////////////////////////////////////////////////////////
#define SND_TYPE_UNKNOWN 0x0000
#define SND_TYPE_WAVE 0x0001

//////////////////////////////////////////////////////////////////////////////
// Functions.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// Default Constructor.
// Returns nothing.
// (public)
//
//////////////////////////////////////////////////////////////////////////////
RSnd::RSnd()
{
	Init();
}

//////////////////////////////////////////////////////////////////////////////
//
// Destructor.
// Returns nothing.
// (public)
//
//////////////////////////////////////////////////////////////////////////////
RSnd::~RSnd()
{
	// Reset and free.	
	Reset();
}

//////////////////////////////////////////////////////////////////////////////
//
// Initialize instantiable members.
// Returns nothing.
// (public)
//
//////////////////////////////////////////////////////////////////////////////
void RSnd::Init(void)
{
	// Initialize members.
	m_lBufSize = 0L;
	m_sState = Stopped;
	m_psample = NULL;
	m_sOwnSample = FALSE;
	m_sLoop = FALSE;
	m_dcUser = NULL;
	m_lLoopStartPos = 0;
	m_lLoopEndPos = 0;
	m_sTypeVolume = 255;	// should be overwritten by Play
	m_sChannelVolume = 255; // should be overwritten by Play
}

///////////////////////////////////////////////////////////////////////////////
//
// Reset object.  Release play data and reset variables.
// Returns nothing.
// (public)
//
///////////////////////////////////////////////////////////////////////////////
void RSnd::Reset(void)
{
	ASSERT(GetState() == Stopped);

	if (GetState() == Stopped)
	{
		if (m_psample != NULL)
		{
			// Unlock the sample.
			m_psample->Unlock();
			// If we are responsible for the sample . . .
			if (m_sOwnSample == TRUE)
			{
				delete m_psample;
				m_psample = NULL;
			}
		}

		Init();
	}
	else
	{
		TRACE("Reset():  Attempt to Reset a playing WAVE!\n");
	}
}

// todo
int16_t RSnd::playchnloop(int chn, sfxhnd_t idx, int vol, int pan, int32_t startPos, int32_t endPos)
{
	int size;

	snd_sfx_stop(chn);
	snd_effect_t *t = (snd_effect_t *)idx;
	AICA_CMDSTR_CHANNEL(tmp, cmd, chan);

	size = t->len;
	if (size >= 65535)
		size = 65534;

	if (!t->stereo)
	{
		cmd->cmd = AICA_CMD_CHAN;
		cmd->timestamp = 0;
		cmd->size = AICA_CMDSTR_CHANNEL_SIZE;
		cmd->cmd_id = chn;
		chan->cmd = AICA_CH_CMD_START;
		chan->base = t->locl;
		chan->type = t->fmt;
		chan->length = size;
		chan->loop = m_sLoop;
		chan->loopstart = startPos * 2;
		chan->loopend = endPos * 2;
		chan->freq = t->rate;
		chan->vol = vol;
		chan->pan = pan;
		snd_sh4_to_aica(tmp, cmd->size);
	}

	return chn;
}

int16_t RSnd::playchn(int chn, sfxhnd_t idx, int vol, int pan)
{
	snd_sfx_stop(chn);
	snd_effect_t *t = (snd_effect_t *)idx;

	if (t)
	{
		int size = t->len;
		AICA_CMDSTR_CHANNEL(tmp, cmd, chan);

		if (size >= 65535)
			size = 65534;
		
		if (!t->stereo)
		{
			cmd->cmd = AICA_CMD_CHAN;
			cmd->timestamp = 0;
			cmd->size = AICA_CMDSTR_CHANNEL_SIZE;
			cmd->cmd_id = chn;
			chan->cmd = AICA_CH_CMD_START;
			chan->base = t->locl;
			chan->type = t->fmt;
			chan->length = size;
			chan->loop = 0;
			chan->loopstart = 0;
			chan->loopend = size;
			chan->freq = t->rate;
			chan->vol = vol;
			chan->pan = pan;

			snd_sh4_to_aica(tmp, cmd->size);
		}
	}
	else
	{	
		return -1;
	}

	return chn;
}

///////////////////////////////////////////////////////////////////////////////
//
// Plays RSample supplied via ptr with buffer size of lPlayBufSize
// (this is the size of the chunks sent to RMix).
// (public)
// Note on looping:
//	 Start																		  End
//		1-----------------------------------------------------------4
//				2=======================================3
//			lLoopStartTime									lLoopEndTime
//
// In a looping scenario, 1..2 of the sample is played, then 2..3
// is repeated until m_sLoop is FALSE, at which time, once 3 is reached,
// 3..4 is played.
//
///////////////////////////////////////////////////////////////////////////////
int16_t RSnd::Play(					  // Returns 0 on success.
	RSample *psample,				  // In:  Sample to play.
	int32_t lPlayBufSize,			  // In:  Size of play buffer in bytes.
	uint8_t ucMainVolume /* = 255 */, // In:  Primary Volume (0 - 255)
	uint8_t ucVolume2 /* = 255 */,	  // In:  Secondary Volume (0 - 255)
	int32_t lLoopStartTime /* = -1*/, // In:  Where to loop back to in milliseconds.
									  //	-1 indicates no looping (unless m_sLoop is
									  // explicitly set).
	int32_t lLoopEndTime /* = 0*/)	  // In:  Where to loop back from in milliseconds.
									  // In:  If less than 1, the end + lLoopEndTime is used.
{
	int16_t sRes = 0; // Assume success.

	ASSERT(psample != NULL);
	ASSERT(GetState() == Stopped);

	// Use supplied sample.
	m_psample = psample;
	// Remember we're not responsible for this buffer (i.e., freeing it).
	m_sOwnSample = FALSE;

	// Attempt to lock sample . . .
	if (m_psample->Lock() == 0)
	{	
		// Attempt to open a sound channel . . .
		if (g_AicaSounds->Add(this) == 0)
		{		
			m_sState = Starting;

			if (lLoopStartTime > -1)
			{
				// Set looping parameters.
				m_sLoop = TRUE;
				m_lLoopEndTime = lLoopEndTime;
				m_lLoopStartTime = lLoopStartTime;
				m_lLoopStartPos = psample->GetPos(lLoopStartTime);
				m_lLoopEndPos = psample->GetPos(lLoopEndTime);
				// If using the end . . .
				if (lLoopEndTime < 1)
				{
					// Use the duration plus the specified negative time.
					m_lLoopEndPos += psample->m_lBufSize;
				}
				
				// Cannot be off end of buffer or beginning of buffer.
				ASSERT(m_lLoopStartPos <= psample->m_lBufSize);
				ASSERT(m_lLoopEndPos <= psample->m_lBufSize);
				ASSERT(m_lLoopStartPos >= 0);
				ASSERT(m_lLoopStartPos < m_lLoopEndPos);

				// Fix these values in case we're in release mode.
				if (m_lLoopStartPos > psample->m_lBufSize)
				{
					m_lLoopStartPos = psample->m_lBufSize;
				}
				if (m_lLoopEndPos > psample->m_lBufSize)
				{
					m_lLoopEndPos = psample->m_lBufSize;
				}

				if (m_lLoopStartPos < 0)
				{
					m_lLoopStartPos = 0;
				}

				if (m_lLoopStartPos > m_lLoopEndPos)
				{
					m_lLoopStartPos = m_lLoopEndPos;
				}
			}
			else
			{
				// If m_sLoop was set . . .
				if (m_sLoop != FALSE)
				{					
					m_lLoopStartPos = 0;
					// Use end of sample as loop back point.
					m_lLoopEndPos = psample->m_lBufSize;
				}
			}
		}
		else
		{			
			TRACE("Play(): Unable to play sound buffer.\n");
			sRes = -3;
		}

		// If any failures . . .
		if (sRes != 0)
		{
			// Unlock sample.
			m_psample->Unlock();
			// We have no more use for this sample.
			m_psample = NULL;
		}
	}
	else
	{		
		TRACE("Play(): Unable to lock supplied sample.\n");
		sRes = -1;
	}

	return sRes;
}

///////////////////////////////////////////////////////////////////////////////
//
// Aborts current play or stream.
// Returns 0 on success.
// (public)
//
///////////////////////////////////////////////////////////////////////////////
int16_t RSnd::Abort(void)
{
	int16_t sRes = 0; // Assume success.

	ASSERT(GetState() != Stopped);

	// If we have a sample (i.e., we are streaming/playing) . . .
	if (m_psample != NULL)
	{
		// Unlock the sample.
		m_psample->Unlock();

		g_AicaSounds->Delete(this);
		snd_sfx_stop(channel);
		m_sState = Stopped;

		activeLoopDuration = 0;
		startTime = 0;
		loopStartTime = 0;
		hardwareLoop = false;

		// Call callback, if specified.
		if (m_dcUser != NULL)
		{
			(*m_dcUser)(this);
		}

		if (m_sOwnSample == TRUE)
		{			
			// Destroy sample.
			delete m_psample;
			// We no longer own the sample.
			m_sOwnSample = FALSE;
		}

		// Clear the sample ptr.  This will cause streaming/playing to end.
		m_psample = NULL;	
	}
	else
	{				
		sRes = -1;
	}

	return sRes;
}

//////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
int8_t RSnd::PlayCallAica(uint8_t volume)
{	
	if (m_psample != NULL)
	{		
		// If we were starting . . .
		if (m_sState == Starting)
		{
			if (m_psample->aicaLoaded == FALSE)
			{				
				m_psample->InitAicaSound();
			}

			if (m_psample->aicaLoaded)
			{
				// Switch state to playing.
				m_sState = Playing;
				startTime = rspGetMilliseconds();
				pauseOffset = 0;
				pauseOffset1 = 0;

				CAicaBuffer *buffer = m_psample->aica->GetActiveAicaBuffer();
				m_psample->aica->soundStartTime = m_psample->aica->activeSegmentStartTime = rspGetMilliseconds();
				if (m_sLoop == TRUE)
				{
					loopStartTime = startTime;
					if (m_psample->aicaLoaded && m_psample->aica->aicaBufferCount == 1)
					{
						hardwareLoop = true;						
						playchnloop(channel, buffer->buffer, uint16_t(float(volume) * GLOBAL_AUDIO_SCALE), 128, m_lLoopStartPos, m_lLoopEndPos);
					}
					else
					{
						hardwareLoop = false;
						playchn(channel, buffer->buffer, uint16_t(float(volume) * GLOBAL_AUDIO_SCALE), 128);						
					}
				}
				else
				{
					playchn(channel, buffer->buffer, uint16_t(float(volume) * GLOBAL_AUDIO_SCALE), 128);										
				}
			}
		}

		if (m_sState == Playing)
		{		
			// Loop sound
			if (m_sLoop == TRUE && !hardwareLoop)
			{			
				activeLoopDuration = (m_lLoopEndTime < 1 ? m_psample->GetDuration() : m_lLoopEndTime);

				// If finished then restart the sound from beginning
				int32_t currTime = rspGetMilliseconds() - pauseOffset1;
				int32_t endTime = loopStartTime + activeLoopDuration;
				if (currTime >= endTime)
				{
					pauseOffset = 0;
					pauseOffset1 = 0;
					m_psample->aica->ResetActiveAicaBuffer();
					CAicaBuffer *buffer = m_psample->aica->GetActiveAicaBuffer();
					m_psample->aica->soundStartTime = m_psample->aica->activeSegmentStartTime = loopStartTime = rspGetMilliseconds();
					playchn(channel, buffer->buffer, uint16_t(float(volume) * GLOBAL_AUDIO_SCALE), 128);					
				}				

				// Otherwise check if the sound needs to switch aica buffers								
				else if (m_psample->aicaLoaded && m_psample->aica->aicaBufferCount > 1)
				{					
					if (m_psample->aica->UpdateActiveAicaBuffer(rspGetMilliseconds(), pauseOffset))
					{
						CAicaBuffer *buffer = m_psample->aica->GetActiveAicaBuffer();
						playchn(channel, buffer->buffer, uint16_t(float(volume) * GLOBAL_AUDIO_SCALE), 128);
						pauseOffset = 0;
					}
				}												
			}
			else
			{
				// Sound has ended.
				if (m_sLoop == FALSE && ((rspGetMilliseconds() >= startTime + m_psample->GetDuration() + pauseOffset1) || m_psample->aica->GetActiveAicaBuffer() == NULL))
				{
					m_psample->aica->ResetActiveAicaBuffer();
					pauseOffset = 0;
					pauseOffset1 = 0;
					g_AicaSounds->Delete(this);

					// Unlock the sample.
					m_psample->Unlock();

					// Set state to stopped.
					m_sState = Stopped;

					snd_sfx_stop(channel);

					// Clear sample.  No longer needed.
					m_psample = NULL;

					activeLoopDuration = 0;
					startTime = 0;
					loopStartTime = 0;
					hardwareLoop = false;

					// Call callback, if specified.
					if (m_dcUser != NULL)
					{
						(*m_dcUser)(this);
					}

					// If we own the sample . . .
					if (m_sOwnSample == TRUE)
					{
						// Delete it.
						delete m_psample;
						// No longer own it.
						m_sOwnSample = FALSE;
					}

					return -1;
				}				
				
				// Sound is playing and needs to switch aica buffers
				else if (m_psample->aicaLoaded && m_psample->aica->aicaBufferCount > 1)
				{				
					if (m_psample->aica->UpdateActiveAicaBuffer(rspGetMilliseconds(), pauseOffset))			
					{						
						CAicaBuffer *buffer = m_psample->aica->GetActiveAicaBuffer();			
						playchn(channel, buffer->buffer, uint16_t(float(volume) * GLOBAL_AUDIO_SCALE), 128);					
					}
				}
			}
		
			// Update volume for snd
			AICA_CMDSTR_CHANNEL(tmp, cmd, chan);
			cmd->cmd = AICA_CMD_CHAN;
			cmd->timestamp = 0;
			cmd->size = AICA_CMDSTR_CHANNEL_SIZE;
			cmd->cmd_id = channel;
			chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_VOL;
			chan->vol = uint16_t(float(volume) * GLOBAL_AUDIO_SCALE);
			snd_sh4_to_aica(tmp, cmd->size);

			return 0;
		}	
	}
}

//////////////////////////////////////////////////////////////////////////////
// EOF
//////////////////////////////////////////////////////////////////////////////
