// ###########################################################################
// #
// # OSCAAudio - Implementation of OSCA Audio Emulation Class
// # Copyright (C) 2009-2011 Tony Friery [DialTone]
// #
// # ALL RIGHTS RESERVED
// #
// ###########################################################################

#pragma hdrstop

#include <stdio.h>
#include "OSCAAudio.h"
#include "FormDiag.h"
#include "OSCABoard.h"
#include "Math.h"

//---------------------------------------------------------------------------

#define BUFFLEN					32000
#define INTERNAL_SAMPLE_RATE 	48000

int sinepos = 0;

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAAudioSystem::InitSound
//
//  PURPOSE:	Initialize sound subsystems
//
//  INPUTS:		void
//
//  OUTPUT:		bool						true on success
//
///////////////////////////////////////////////////////////////////////
bool __fastcall TOSCAAudioSystem::InitSound(void)
{
	bool rv = BASS_Init(-1, INTERNAL_SAMPLE_RATE, 0, m_hHandle, NULL);

	if (rv)
	{
		for (int i = 0; i < 4; i++)
		{
			m_hChannels[i] = BASS_StreamCreate(INTERNAL_SAMPLE_RATE, 1,
				(i & 1) ? BASS_SPEAKER_FRONTLEFT : BASS_SPEAKER_FRONTRIGHT, STREAMPROC_PUSH, NULL);
			if (!m_hChannels[i])
			{
				rv = false;
			}
			else
			{
				m_pBuffers[i] = (short *)(malloc(BUFFLEN * 2));
				BASS_ChannelPlay(m_hChannels[i], true);
         }
		}
	}

	return rv;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAAudioSystem::StartChannel
//
//  PURPOSE:	Start a single audio channel playing back samples
//
//  INPUTS:		BYTE                 Channel number (0-based) to start
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAAudioSystem::StartChannel(BYTE channel)
{
	if (ControlRegs[channel].Playing)
	{
		// Perhaps if a channel is already playing it shouldn't restart,
		// but this isn't really compatible with the looping technique used, so
		// the playback is checked separately when the play register is updated
//		return;
	}

	// Convert the V6Z80P formatted location/length registers to actual values
	// compatible with the DirectX playback routine
	ControlRegs[channel].RealLocation = 0x20000 + (ControlRegs[channel].Location << 1);
	this->fOutputPos[channel] = ControlRegs[channel].RealLocation;
	ControlRegs[channel].RealLength = (ControlRegs[channel].Length << 1);

	// Reset default looping/playing flags
	ControlRegs[channel].Looping = true;
	ControlRegs[channel].Playing = true;
	LoopFlags |= (1 << channel);
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAAudioSystem::UpdateChannel
//
//  PURPOSE:	Called frequently to update the DirectSound buffers
//
//  INPUTS:		BYTE                 Channel number (0-based)
//					short *					Pointer to audio buffer for filling
//					unsigned int			Bytes required to fill buffer
//					unsigned int			Number of CPU cycles elapsed over
//												this fill period
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAAudioSystem::UpdateChannel(BYTE channel,
	unsigned int bytes, unsigned int cycles)
{
	char buf[100];
	unsigned int buflen = bytes * 2;
	short *buffer = m_pBuffers[channel];
	DWORD byteOffset = fOutputPos[channel];

	if (!buffer)
	{
		return;
	}

	/* TEST */
//	while (bytes)
//	{
//		short level = (short)(8000.0f * Sin((double)(sinepos / 32) * M_PI_2));
//		sinepos++;
//		sinepos %= (32 * 360);
//		*(buffer++) = level;
//		bytes--;
//   }
//
//	BASS_StreamPutData(m_hChannels[channel], m_pBuffers[channel], buflen);
//	return;

	// Iterate through the bytes required to fill the buffer
	while (bytes)
	{
		// Only fill with non-zero data if the channel is playing
		if (ControlRegs[channel].Playing)
		{
			short level = (short)((char)(m_pOwner->MainRAM[byteOffset]))  * (short)ControlRegs[channel].Volume * 2;
			*(buffer++) = level;

			// Check internal prescaler to see if it's time to
			// advance to the next byte in the V6Z80's sample memory
			fSamplePrescaler[channel] -= 1.0f;
			if (fSamplePrescaler[channel] < 1.0f)
			{
				// Obtain Next sample
				fSamplePrescaler[channel] += (double)INTERNAL_SAMPLE_RATE / (double)ControlRegs[channel].RealSampRate;
				fOutputPos[channel]++;

				// Check for sample looping
				if (fOutputPos[channel] >= (ControlRegs[channel].RealLocation + ControlRegs[channel].RealLength))
				{
					// Channel has looped

					// Set Audio looped IRQ flag
					LoopFlags |= (1 << (4 + channel));

					if (ControlRegs[channel].Looping)
					{
						StartChannel(channel);
					}
					else
					{
						// Clear playing flag
						ControlRegs[channel].Playing = false;
						fOutputPos[channel] = 0x20000 + (ControlRegs[channel].Location << 1);
						LoopFlags &= ~(1 << channel);
					}
				}
				byteOffset = fOutputPos[channel];
			}
		}
		else
		{
			// Channel not playing - fill with the sound of silence
			*(buffer++) = 0;
		}

		bytes--;
	}

	BASS_StreamPutData(m_hChannels[channel], m_pBuffers[channel], buflen);
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAAudioSystem::UpdateSound
//
//  PURPOSE:	Called frequently by the CPU core to update the
// 				emulated audio hardware
//
//  INPUTS:		unsigned int			Number of CPU cycles elapsed since
//												the last audio update
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAAudioSystem::UpdateSound(unsigned int cycles)
{
	DWORD	writeLen;

	// Iterate through each of the 4 channels
	for (int i = 0; i < 4; i++)
	{
		// Obtain current bytes to be filled
		DWORD buffSize = BASS_ChannelGetData(m_hChannels[i], NULL, BASS_DATA_AVAILABLE) * 2;
		if (buffSize <= BUFFLEN)
		{
			writeLen = (BUFFLEN - buffSize);
			if (m_pBuffers[i])
			{
				UpdateChannel(i, writeLen, cycles);
         }
		}
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAAudioSystem::GetAudioFlags
//
//  PURPOSE:	Get the flags register to be reported to the V6Z80P
//
//  INPUTS:		void
//
//  OUTPUT:		BYTE						Flags register (see bits below)
//												chan: 	43214321
//												means:   llllpppp
//												l = looped, p = playing
//
///////////////////////////////////////////////////////////////////////
BYTE __fastcall TOSCAAudioSystem::GetAudioFlags(void)
{
	return LoopFlags;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAAudioSystem::ClearLoopFlag
//
//  PURPOSE:	Clear the channel looped flag for specified channel
//
//  INPUTS:		BYTE						Channel number (0-based)
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAAudioSystem::ClearLoopFlag(BYTE channel)
{
	if ((DiagForm) && (DiagForm->IsActive(DIAG_AUDIO)))
	{
		DiagForm->AddMessage(DIAG_AUDIO, UnicodeString::Format(
			L"Clear Audio Loop on Channel: %d",
			OPENARRAY(TVarRec, (channel))
		));
	}

	LoopFlags &= ~(1 << (4 + channel));
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAAudioSystem::SetPlaybackFlags
//
//  PURPOSE:	Change the playback flags register (which will cause
//					channels to start and/or stop)
//
//  INPUTS:		BYTE						Flags value (only LS-Nybble is
//												relevant, for chans 4321 resp.)
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAAudioSystem::SetPlaybackFlags(BYTE flags)
{
	for (int i = 0; i < 4; i++)
	{
		if (flags & (1 << i))
		{
			if (!ControlRegs[i].Playing)
			{
				if ((DiagForm) && (DiagForm->IsActive(DIAG_AUDIO)))
				{
					DiagForm->AddMessage(DIAG_AUDIO, UnicodeString::Format(
						L"Start Channel: %d",
						OPENARRAY(TVarRec, (i))
					));
				}

				StartChannel(i);
			}
		}
		else
		{
				if ((DiagForm) && (DiagForm->IsActive(DIAG_AUDIO)))
				{
					DiagForm->AddMessage(DIAG_AUDIO, UnicodeString::Format(
						L"Stop Channel: %d",
						OPENARRAY(TVarRec, (i))
					));
				}

			ControlRegs[i].Playing = false;
		}
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAAudioSystem::WriteAudioPort
//
//  PURPOSE:	Handle a write to the audio registers by the emulated
// 				CPU core
//
//  INPUTS:		BYTE						Address to be written
//					WORD						Value to be written
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAAudioSystem::WriteAudioPort(BYTE addr, WORD value)
{
	BYTE chan = (addr & 0x0c) >> 2;
	BYTE cntrl = (addr & 0x03);

	switch (cntrl)
	{
		case 0:	// Location (words from start of sample memory)
			if (ControlRegs[chan].Location != value)
			{
				ControlRegs[chan].Location = value;
				if (ControlRegs[chan].Playing)
				{
					//ControlRegs[chan].Looping = false;
				}

				if ((DiagForm) && (DiagForm->IsActive(DIAG_AUDIO)))
				{
					DiagForm->AddMessage(DIAG_AUDIO, UnicodeString::Format(
						L"Channel %d Location set to 0x%4.4x (0x%8.8x)",
						OPENARRAY(TVarRec, (chan, ControlRegs[chan].Location,
						ControlRegs[chan].RealLocation))
					));
				}
			}
			break;
		case 1:	// Length (in words)
			if (ControlRegs[chan].Length != value)
			{
				ControlRegs[chan].Length = value;
				if (ControlRegs[chan].Playing)
				{
					//ControlRegs[chan].Looping = false;
				}

				if ((DiagForm) && (DiagForm->IsActive(DIAG_AUDIO)))
				{
					DiagForm->AddMessage(DIAG_AUDIO, UnicodeString::Format(
						L"Channel %d Duration set to 0x%4.4x",
						OPENARRAY(TVarRec, (chan, ControlRegs[chan].Length))
					));
				}
			}
			break;
		case 2:	// Rate (system clocks between samples)
			if (ControlRegs[chan].SampleRate != value)
			{
				ControlRegs[chan].SampleRate = value;

				if (value < 512)
				{
					ControlRegs[chan].RealSampRate = 31250;
				}
				else
				{
					ControlRegs[chan].RealSampRate = 16000000 / value;
				}
				fSamplePrescaler[chan] = (double)48000 / (double)ControlRegs[chan].RealSampRate;

				if ((DiagForm) && (DiagForm->IsActive(DIAG_AUDIO)))
				{
					DiagForm->AddMessage(DIAG_AUDIO, UnicodeString::Format(
						L"Channel %d SampRate set to 0x%4.4x (%d Hz)",
						OPENARRAY(TVarRec, (chan,
						ControlRegs[chan].SampleRate, ControlRegs[chan].RealSampRate))
					));
				}
			}
			break;
		case 3:	// Volume
			if (ControlRegs[chan].Volume != (value & 0xff))
			{
				ControlRegs[chan].Volume = (value & 0xff);
				if (ControlRegs[chan].Volume > 0x40)
				{
					ControlRegs[chan].Volume = 0x40;
				}

				if ((DiagForm) && (DiagForm->IsActive(DIAG_AUDIO)))
				{
					DiagForm->AddMessage(DIAG_AUDIO, UnicodeString::Format(
						L"Channel %d Volume set to 0x%2.2x",
						OPENARRAY(TVarRec, (chan, value & 0xff))
					));
				}
			}
			break;
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAAudioSystem::ReadAudioPort
//
//  PURPOSE:	Handle a read of the audio registers by the emulated
// 				CPU core
//
//  INPUTS:		BYTE						Address to be read
//
//  OUTPUT:		WORD						Value read
//
///////////////////////////////////////////////////////////////////////
WORD __fastcall TOSCAAudioSystem::ReadAudioPort(BYTE addr)
{
	BYTE chan = (addr & 0x0c) >> 2;
	BYTE cntrl = (addr & 0x03);
	WORD retv = 0;

	switch (cntrl)
	{
		case 0:	// Location (words from start of sample memory)
			retv = ControlRegs[chan].Location;

			if ((DiagForm) && (DiagForm->IsActive(DIAG_AUDIO)))
			{
				DiagForm->AddMessage(DIAG_AUDIO, UnicodeString::Format(
					L"Channel %d Location read",
					OPENARRAY(TVarRec, (chan))
				));
			}
			break;
		case 1:	// Length (in words)
			retv = ControlRegs[chan].Length;

			if ((DiagForm) && (DiagForm->IsActive(DIAG_AUDIO)))
			{
				DiagForm->AddMessage(DIAG_AUDIO, UnicodeString::Format(
					L"Channel %d Duration read",
					OPENARRAY(TVarRec, (chan))
				));
			}
			break;
		case 2:	// Rate (system clocks between samples)
			retv = ControlRegs[chan].SampleRate;

			if ((DiagForm) && (DiagForm->IsActive(DIAG_AUDIO)))
			{
				DiagForm->AddMessage(DIAG_AUDIO, UnicodeString::Format(
					L"Channel %d SampRate read",
					OPENARRAY(TVarRec, (chan))
				));
			}
			break;
		case 3:	// Volume
			retv = ControlRegs[chan].Volume;

			if ((DiagForm) && (DiagForm->IsActive(DIAG_AUDIO)))
			{
				DiagForm->AddMessage(DIAG_AUDIO, UnicodeString::Format(
					L"Channel %d Volume read",
					OPENARRAY(TVarRec, (chan))
				));
			}
			break;
	}

	return retv;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAAudioSystem::TOSCAAudioSystem [CONSTRUCTOR]
//
//  PURPOSE:	Constructor for the Audio System
//
//  INPUTS:		HWND                 Main window's handle
//					TOSCABoard *			Pointer to board class owning this
//												class (required for accessing sample
//												memory)
//
//	 OUTPUT:		none
//
///////////////////////////////////////////////////////////////////////
__fastcall TOSCAAudioSystem::TOSCAAudioSystem(HWND handle, TOSCABoard *owner) :
	m_hHandle(handle), m_pOwner(owner)
{
	for (int i = 0; i < 4; i++)
	{
		ControlRegs[i].Location			= 0;
		ControlRegs[i].Volume			= 0;
		ControlRegs[i].Playing			= false;
		ControlRegs[i].Looping			= true;
		ControlRegs[i].Length			= 0;
		ControlRegs[i].SampleRate		= 0;
		ControlRegs[i].RealLength		= 0;
		ControlRegs[i].RealLocation	= 0x20000;
		ControlRegs[i].RealSampRate	= 31250;
		LoopFlags							= 0x00;
		fSamplePrescaler[i] 				= 1.0f;
		m_hChannels[i]						= NULL;
		m_nWritePos[i]						= 0;
		fOutputPos[i]						= 0x20000;
	}

	InitSound();
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAAudioSystem::~TOSCAAudioSystem [DESTRUCTOR]
//
//  PURPOSE:	Destructor for the Audio System
//
//  INPUTS:		none
//
//	 OUTPUT:		none
//
///////////////////////////////////////////////////////////////////////
__fastcall TOSCAAudioSystem::~TOSCAAudioSystem()
{
	BASS_Stop();
}

//---------------------------------------------------------------------------

#pragma package(smart_init)

