// Part of SAASound copyright 1998-2018 Dave Hooper <dave@beermex.com>
//
// SAAImpl.cpp: implementation of the CSAASound class.
// the bones of the 'virtual SAA-1099' emulation
//
// the actual sound generation is carried out in the other classes;
// this class provides the output stage and the external interface only
//
//////////////////////////////////////////////////////////////////////

#include "SAASound.h"

#include "types.h"
#include "SAAImpl.h"
#include "defns.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CSAASoundInternal::CSAASoundInternal()
	:
m_chip(),
m_uParam(0),
m_uParamRate(0),
m_nClockRate(EXTERNAL_CLK_HZ),
m_nSampleRate(SAMPLE_RATE_HZ),
m_nOversample(DEFAULT_OVERSAMPLE),
m_bHighpass(false),
filterout_z1_left_mixed(0),
filterout_z1_right_mixed(0)
{
#ifdef USE_CONFIG_FILE
	m_Config.ReadConfig();
#endif

#if defined(DEBUGSAA)
	m_dbgfile.open(_T(DEBUG_SAA_REGISTER_LOG), std::ios_base::out);
	m_pcmfile.open(_T(DEBUG_SAA_PCM_LOG), std::ios_base::out | std::ios_base::binary);
#elif defined(USE_CONFIG_FILE)
	if (m_Config.m_bGenerateRegisterLogs)
		m_dbgfile.open(m_Config.m_strRegisterLogPath, std::ios_base::out);
	if (m_Config.m_bGeneratePcmLogs)
		m_pcmfile.open(m_Config.m_strPcmOutputPath, std::ios_base::out | std::ios_base::binary);

	if (m_Config.m_bGeneratePcmLogs && m_Config.m_bGeneratePcmSeparateChannels)
	{
		for (int i = 0; i < 6; i++)
		{
			m_channel_pcmfile[i].open(m_Config.getChannelPcmOutputPath(i), std::ios_base::out | std::ios_base::binary);
		}
	}


#endif
	// set parameters
	// TODO support defaults and overrides from config file
	// m_chip.SetSoundParameters(SAAP_FILTER | SAAP_11025 | SAAP_8BIT | SAAP_MONO);
	// reset the virtual SAA
	// m_chip.Clear();

	m_chip._SetClockRate(m_nClockRate);
	m_chip._SetOversample(m_nOversample);
}

CSAASoundInternal::~CSAASoundInternal()
{
	//
}

//////////////////////////////////////////////////////////////////////
// CSAASound members
//////////////////////////////////////////////////////////////////////

void CSAASoundInternal::SetClockRate(unsigned int nClockRate)
{
	m_nClockRate = nClockRate;
	m_chip._SetClockRate(m_nClockRate);
}

void CSAASoundInternal::Clear(void)
{
	// reinitialises virtual SAA:
	// sets reg 28 to 0x02; - sync and disabled
	// sets regs 00-31 (except 28) to 0x00;
	// sets reg 28 to 0x00;
	// sets current reg to 0
	WriteAddressData(28,2);
	for (int i=31; i>=0; i--)
	{
		if (i!=28) WriteAddressData(i,0);
	}
	WriteAddressData(28,0);
	WriteAddress(0);
}

void CSAASoundInternal::WriteData(BYTE nData)
{
	// originated from an OUT 255,d call
	m_chip._WriteData(nData);
#if defined(DEBUGSAA) || defined(USE_CONFIG_FILE)
#ifdef USE_CONFIG_FILE
	if (m_Config.m_bGenerateRegisterLogs)
	{
#endif
		m_dbgfile << m_nDebugSample << " " << (int)m_chip._ReadAddress() << ":" << (int)nData << std::endl;
#ifdef USE_CONFIG_FILE
	}
#endif
#endif
}

void CSAASoundInternal::WriteAddress(BYTE nReg)
{
	// originated from an OUT 511,r call
	m_chip._WriteAddress(nReg);
#if defined(DEBUGSAA) || defined(USE_CONFIG_FILE)
#ifdef USE_CONFIG_FILE
	if (m_Config.m_bGenerateRegisterLogs)
	{
#endif
		m_dbgfile << m_nDebugSample << " " << (int)nReg << ":";
		if (nReg==24)
		{
			m_dbgfile << "<!ENVO!>";
		}
		else if (nReg==25)
		{
			m_dbgfile << "<!ENV1!>";
		}
		m_dbgfile << std::endl;
#ifdef USE_CONFIG_FILE
	}
#endif
#endif
}

void CSAASoundInternal::WriteAddressData(BYTE nReg, BYTE nData)
{
	// performs WriteAddress(nReg) followed by WriteData(nData)
	m_chip._WriteAddress(nReg);
	m_chip._WriteData(nData);
}

#ifdef DEBUG
BYTE CSAASoundInternal::ReadAddress(void)
{
	// Not a real hardware function of the SAA-1099, which is write-only
	return(m_chip._ReadAddress());
}
#else
BYTE CSAASoundInternal::ReadAddress(void)
{
	// Not a real hardware function of the SAA-1099, which is write-only
	return(0);
}
#endif

void CSAASoundInternal::SetSoundParameters(SAAPARAM uParam)
{
	// set samplerate properties from uParam (deprecated but still supported)
	unsigned int nSampleRate = m_nSampleRate;
	switch (uParam & SAAP_MASK_SAMPLERATE)
	{
	case SAAP_44100:
		nSampleRate = 44100;
		m_uParamRate = (m_uParamRate & ~SAAP_MASK_SAMPLERATE) | SAAP_44100;
		break;
	case SAAP_22050:
		nSampleRate = 22050;
		m_uParamRate = (m_uParamRate & ~SAAP_MASK_SAMPLERATE) | SAAP_22050;
		break;
	case SAAP_11025:
		nSampleRate = 11025;
		m_uParamRate = (m_uParamRate & ~SAAP_MASK_SAMPLERATE) | SAAP_11025;
		break;
	case 0:// change nothing!
	default:
		break;
	}
	
	if (nSampleRate != m_nSampleRate)
	{
		m_nSampleRate = nSampleRate;
		m_chip._SetSampleRate(m_nSampleRate);
	}

	// set filter properties from uParam
	m_uParam = (m_uParam & ~SAAP_MASK_FILTER) | (uParam & SAAP_MASK_FILTER);
}

void CSAASoundInternal::SetSampleRate(unsigned int nSampleRate)
{
	if (nSampleRate != m_nSampleRate)
	{
		m_nSampleRate = nSampleRate;
		m_chip._SetSampleRate(m_nSampleRate);
	}
}

void CSAASoundInternal::SetOversample(unsigned int nOversample)
{
	if (nOversample != m_nOversample)
	{
		m_nOversample = nOversample;
		m_chip._SetOversample(m_nOversample);
	}
}

SAAPARAM CSAASoundInternal::GetCurrentSoundParameters(void)
{
	return m_uParam | m_uParamRate;
}

unsigned short CSAASoundInternal::GetCurrentBytesPerSample(void)
{
	// 16 bit stereo => 4 bytes per sample
	return 4;
}

/*static*/ unsigned short CSAASound::GetBytesPerSample(SAAPARAM uParam)
{
	// 16 bit stereo => 4 bytes per sample
	switch (uParam & (SAAP_MASK_CHANNELS | SAAP_MASK_BITDEPTH))
	{
		case SAAP_STEREO | SAAP_16BIT:
			return 4;
		default:
			return 0;
	}
}

unsigned int CSAASoundInternal::GetCurrentSampleRate(void)
{
	return CSAASound::GetSampleRate(m_uParamRate);
}

/*static*/ unsigned int CSAASound::GetSampleRate(SAAPARAM uParam) // static member function
{
	switch (uParam & SAAP_MASK_SAMPLERATE)
	{
		case SAAP_11025:
			return 11025;
		case SAAP_22050:
			return 22050;
		case SAAP_44100:
			return 44100;
		default:
			return 0;
	}
}

#if defined(USE_CONFIG_FILE) || (defined(DEFAULT_BOOST) && DEFAULT_BOOST>1)
#define DO_BOOST
#endif

void scale_for_output(unsigned int left_input, unsigned int right_input,
	double oversample_scalar, bool highpass, double boost,
	double& filterout_z1_left, double& filterout_z1_right,
	BYTE* &pBuffer)
{
	double float_left = (double)left_input;
	double float_right = (double)right_input;
	float_left /= oversample_scalar;
	float_right /= oversample_scalar;

	// scale output into good range
	float_left *= DEFAULT_UNBOOSTED_MULTIPLIER;
	float_right *= DEFAULT_UNBOOSTED_MULTIPLIER;

	if (highpass)
	{
		/* cutoff = 5 Hz (say)
			const double b1 = exp(-2.0 * M_PI * (Fc/Fs))
			const double a0 = 1.0 - b1;
		*/
		const double b1 = 0.99928787;
		const double a0 = 1.0 - b1;

		filterout_z1_left = float_left * a0 + filterout_z1_left * b1;
		filterout_z1_right = float_right * a0 + filterout_z1_right * b1;
		float_left -= filterout_z1_left;
		float_right -= filterout_z1_right;
	}

	// multiply by boost, if defined
#if defined(DO_BOOST)
	float_left *= boost;
	float_right *= boost;
#endif
	// convert to 16-bit signed range with hard clipping
	signed short left_output = (signed short)(float_left > 32767 ? 32767 : float_left < -32768 ? -32768 : float_left);
	signed short right_output = (signed short)(float_right > 32767 ? 32767 : float_right < -32768 ? -32768 : float_right);

	*pBuffer++ = left_output & 0x00ff;
	*pBuffer++ = (left_output >> 8) & 0x00ff;
	*pBuffer++ = right_output & 0x00ff;
	*pBuffer++ = (right_output >> 8) & 0x00ff;
}

void CSAASoundInternal::GenerateMany(BYTE* pBuffer, unsigned int nSamples, DivDispatchOscBuffer** oscBuf)
{
	unsigned int left_mixed, right_mixed;

#if defined(DEBUGSAA) || defined(USE_CONFIG_FILE)
	BYTE* pBufferStart = pBuffer;
	unsigned int nTotalSamples = nSamples;
#endif

#if defined(DO_BOOST)
#if defined(USE_CONFIG_FILE)
	double nBoost = m_Config.m_nBoost;
#else
	double nBoost = DEFAULT_BOOST;
#endif
#else
  double nBoost = DEFAULT_BOOST;
#endif

	double oversample = double(1 << m_nOversample);

#if defined(USE_CONFIG_FILE)
	static double filterout_z1_left_0 = 0, filterout_z1_right_0 = 0;
	static double filterout_z1_left_1 = 0, filterout_z1_right_1 = 0;
	static double filterout_z1_left_2 = 0, filterout_z1_right_2 = 0;
	static double filterout_z1_left_3 = 0, filterout_z1_right_3 = 0;
	static double filterout_z1_left_4 = 0, filterout_z1_right_4 = 0;
	static double filterout_z1_left_5 = 0, filterout_z1_right_5 = 0;

	if (m_Config.m_bGeneratePcmLogs && m_Config.m_bGeneratePcmSeparateChannels)
	{
		unsigned int left0, right0, left1, right1, left2, right2, left3, right3, left4, right4, left5, right5;
		BYTE* pChannelBufferPtr[6] = { m_pChannelBuffer[0], m_pChannelBuffer[1], m_pChannelBuffer[2], m_pChannelBuffer[3], m_pChannelBuffer[4], m_pChannelBuffer[5] };

		while (nSamples--)
		{
			m_chip._TickAndOutputSeparate(left_mixed, right_mixed,
				left0, right0,
				left1, right1,
				left2, right2,
				left3, right3,
				left4, right4,
				left5, right5);
			scale_for_output(left_mixed, right_mixed, oversample, m_bHighpass, nBoost, filterout_z1_left_mixed, filterout_z1_right_mixed, pBuffer);

			// and the separate channels
			scale_for_output(left0, right0, oversample, m_bHighpass, nBoost, filterout_z1_left_0, filterout_z1_right_0, pChannelBufferPtr[0]);
			scale_for_output(left1, right1, oversample, m_bHighpass, nBoost, filterout_z1_left_1, filterout_z1_right_1, pChannelBufferPtr[1]);
			scale_for_output(left2, right2, oversample, m_bHighpass, nBoost, filterout_z1_left_2, filterout_z1_right_2, pChannelBufferPtr[2]);
			scale_for_output(left3, right3, oversample, m_bHighpass, nBoost, filterout_z1_left_3, filterout_z1_right_3, pChannelBufferPtr[3]);
			scale_for_output(left4, right4, oversample, m_bHighpass, nBoost, filterout_z1_left_4, filterout_z1_right_4, pChannelBufferPtr[4]);
			scale_for_output(left5, right5, oversample, m_bHighpass, nBoost, filterout_z1_left_5, filterout_z1_right_5, pChannelBufferPtr[5]);

			// flush channel output PCM buffers when full
			if (pChannelBufferPtr[0] >= m_pChannelBuffer[0] + CHANNEL_BUFFER_SIZE)
			{
				for (int i = 0; i < 6; i++)
				{
					m_channel_pcmfile[i].write((const char*)m_pChannelBuffer[i], CHANNEL_BUFFER_SIZE);
					pChannelBufferPtr[i] = m_pChannelBuffer[i];
				}
			}
		}
		// flush remaining channel PCM output data
		if (pChannelBufferPtr[0] >= m_pChannelBuffer[0])
		{
			for (int i = 0; i < 6; i++)
			{
				m_channel_pcmfile[i].write((const char*)m_pChannelBuffer[i], pChannelBufferPtr[i]-m_pChannelBuffer[i]);
			}
		}
	}
	else
	{
#endif
		while (nSamples--)
		{
			m_chip._TickAndOutputStereo(left_mixed, right_mixed, oscBuf);
			scale_for_output(left_mixed, right_mixed, oversample, m_bHighpass, nBoost, filterout_z1_left_mixed, filterout_z1_right_mixed, pBuffer);
		}

#if defined(USE_CONFIG_FILE)
	}
#endif

#if defined(DEBUGSAA) || defined(USE_CONFIG_FILE)
#ifdef USE_CONFIG_FILE
	if (m_Config.m_bGeneratePcmLogs)
	{
#endif
		m_pcmfile.write((const char *)pBufferStart, nTotalSamples * (unsigned int)GetCurrentBytesPerSample());
		m_nDebugSample += nTotalSamples;
#ifdef USE_CONFIG_FILE
	}
#endif

#endif
}

///////////////////////////////////////////////////////

LPCSAASOUND SAAAPI CreateCSAASound(void)
{
	return (new CSAASoundInternal);
}

void SAAAPI DestroyCSAASound(LPCSAASOUND object)
{
	delete (object);
}


/* thoughts on lowpass filtering as part of oversampling.
I tried this and really it didn't seem to make a lot of (audible) difference.

// lowpass oversample filter adds complexity and not particularly audibly better than simple averaging.
// use_lowpass_oversample_filter_average_output adds an additional averaging step to the output of the oversample
// filter.  this seems critical, because without this, the raw output of the lowpass filter is full of aliases
// If use_lowpass_oversample_filter is False, then the _average_output flag is ignored.
// Default, use_lowpass_oversample_filter is False, it sounds just fine really.

//#define USE_LOWPASS_OVERSAMPLE_FILTER
#undef USE_LOWPASS_OVERSAMPLE_FILTER
//#define USE_LOWPASS_OVERSAMPLE_FILTER_AVERAGE_OUTPUT
#undef USE_LOWPASS_OVERSAMPLE_FILTER_AVERAGE_OUTPUT

#ifdef USE_LOWPASS_OVERSAMPLE_FILTER
static double oversample_lp_filterout_z1_left_stages[10] = { 0,0,0,0,0,0,0,0,0,0 };
static double oversample_lp_filterout_z1_right_stages[10] = { 0,0,0,0,0,0,0,0,0,0 };
double averaged_filterout_left = 0.0, averaged_filterout_right = 0.0;
const int nStages = 10;
for (int i = 0; i < 1 << m_nOversample; i++)
{
	Noise[0]->Tick();
	Noise[1]->Tick();
	f_left = f_right = 0;
	for (int c = 0; c < 6; c++)
	{
		Amp[c]->TickAndOutputStereo(temp_left, temp_right);
		f_left += (double)temp_left;
		f_right += (double)temp_right;
	}
	// apply lowpass here.
	// HACK: ASSUME m_nOversample is 64 (I was experimenting only using the 64x oversample anyway)
	// therefore Fs = 44100*64
	// let's set Fc = 10kHz
	// so Fc/Fs = 0.00354308390022675736961451247166
	// const double b1 = exp(-2.0 * M_PI * (Fc/Fs))
	// const double a0 = 1.0 - b1;
	// const double b1 = 0.9779841137335348363722276130195;
	const double b1 = 0.977;
	const double a0 = 1.0 - b1;

	oversample_lp_filterout_z1_left_stages[0] = f_left * a0 + oversample_lp_filterout_z1_left_stages[0] * b1;
	for (int stage = 1; stage < nStages; stage++)
		oversample_lp_filterout_z1_left_stages[stage] = oversample_lp_filterout_z1_left_stages[stage - 1] * a0 + oversample_lp_filterout_z1_left_stages[stage] * b1;
	oversample_lp_filterout_z1_right_stages[0] = f_right * a0 + oversample_lp_filterout_z1_right_stages[0] * b1;
	for (int stage = 1; stage < nStages; stage++)
		oversample_lp_filterout_z1_right_stages[stage] = oversample_lp_filterout_z1_right_stages[stage - 1] * a0 + oversample_lp_filterout_z1_right_stages[stage] * b1;

#ifdef USE_LOWPASS_OVERSAMPLE_FILTER_AVERAGE_OUTPUT
	averaged_filterout_left += oversample_lp_filterout_4z1_left;
	averaged_filterout_right += oversample_lp_filterout_4z1_right;
#endif
}

// by the end of this loop we will have computed the oversample lowpass filter m_nOversample times
// and yielded exactly ONE sample output.
#ifdef USE_LOWPASS_OVERSAMPLE_FILTER_AVERAGE_OUTPUT
f_left = averaged_filterout_left / (1 << m_nOversample);
f_right = averaged_filterout_right / (1 << m_nOversample);
#else
f_left = oversample_lp_filterout_z1_left_stages[nStages - 1];
f_right = oversample_lp_filterout_z1_right_stages[nStages - 1];
#endif

#else
	// do the simple 1/N averaging which is easier and sounds good enough

#endif

*/

