/*  This file is part of BKBTL.
    BKBTL is free software: you can redistribute it and/or modify it under the terms
of the GNU Lesser General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
    BKBTL 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 Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License along with
BKBTL. If not, see <http://www.gnu.org/licenses/>. */

// SoundGen.cpp
//

#include "StdAfx.h"
#include "BKSound.h"
#include "BKErrMsgSys.h"
#include "Globals.h"

CBkSound::CBkSound(CWnd *pWnd)
	: m_bSoundGenInitialized(FALSE)
	, m_dSampleL(0.0)
	, m_dSampleR(0.0)
	, m_dFeedbackL(0.0)
	, m_dFeedbackR(0.0)
	, m_nWaveCurrentBlock(0)
	, m_nBufCurPos(0)
	, m_pWaveBlocks(NULL)
{
	m_nBufSize = (g_Config.m_nSoundSampleRate * 3 / 4 / CPU_FRAMERATE) * SAMPLE_LENGTH;
	m_nBufSizeW = m_nBufSize / 2;
	m_mBufferST = new SAMPLE_TYPE[m_nBufSizeW];
	SoundGen_Initialize(0x7fff);
}

CBkSound::~CBkSound()
{
	SoundGen_Finalize();
	SAFE_DELETE_ARRAY(m_mBufferST);
}


void CALLBACK CBkSound::WaveCallback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
	int *freeBlockCounter = reinterpret_cast<int *>(dwInstance);

	if (uMsg != WOM_DONE)
	{
		return;
	}

	EnterCriticalSection(&m_csWaveCriticalSection);
	(*freeBlockCounter)++;
	LeaveCriticalSection(&m_csWaveCriticalSection);
}

void CBkSound::SoundGen_Initialize(WORD volume)
{
	if (m_bSoundGenInitialized)
	{
		return;
	}

	BYTE *mbuffer;
	DWORD totalBufferSize = (m_nBufSize + sizeof(WAVEHDR)) * BLOCK_COUNT;
	mbuffer = (BYTE *)HeapAlloc(
	              GetProcessHeap(),
	              HEAP_ZERO_MEMORY,
	              totalBufferSize);

	if (mbuffer == NULL)
	{
		CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
		return;
	}

	m_pWaveBlocks = reinterpret_cast<WAVEHDR *>(mbuffer);
	mbuffer += sizeof(WAVEHDR) * BLOCK_COUNT;

	for (int i = 0; i < BLOCK_COUNT; ++i)
	{
		m_pWaveBlocks[i].dwBufferLength = m_nBufSize;
		m_pWaveBlocks[i].lpData = reinterpret_cast<LPSTR >(mbuffer);
		mbuffer += m_nBufSize;
	}

	m_nWaveFreeBlockCount   = BLOCK_COUNT;
	m_nWaveCurrentBlock     = 0;
	m_wfx.cbSize            = 0; //extra size. sizeof (WAVEFORMATEX);
	m_wfx.wFormatTag        = WAVE_FORMAT_PCM;
	m_wfx.nSamplesPerSec    = g_Config.m_nSoundSampleRate;
	m_wfx.wBitsPerSample    = BUFFER_BPS;
	m_wfx.nChannels         = BUFFER_CHANNELS;
	m_wfx.nBlockAlign       = (m_wfx.wBitsPerSample >> 3) * m_wfx.nChannels;
	m_wfx.nAvgBytesPerSec   = m_wfx.nSamplesPerSec * m_wfx.nBlockAlign;
	MMRESULT result = waveOutOpen(
	                      &m_hWaveOut, WAVE_MAPPER, &m_wfx,
	                      reinterpret_cast<DWORD_PTR >(WaveCallback),
	                      reinterpret_cast<DWORD_PTR >(&m_nWaveFreeBlockCount),
	                      CALLBACK_FUNCTION);

	if (result != MMSYSERR_NOERROR)
	{
		return;
	}

	waveOutSetVolume(m_hWaveOut, MAKELONG(volume, volume));
	InitializeCriticalSection(&m_csWaveCriticalSection);
	m_nBufCurPos = 0;
	m_bSoundGenInitialized = TRUE;
}

void CBkSound::SoundGen_Finalize()
{
	if (!m_bSoundGenInitialized)
	{
		return;
	}

	while (m_nWaveFreeBlockCount < BLOCK_COUNT)
	{
		Sleep(10);
	}

	for (int i = 0; i < m_nWaveFreeBlockCount; ++i)
	{
		if (m_pWaveBlocks[i].dwFlags & WHDR_PREPARED)
		{
			waveOutUnprepareHeader(m_hWaveOut, &m_pWaveBlocks[i], sizeof(WAVEHDR));
		}
	}

	DeleteCriticalSection(&m_csWaveCriticalSection);
	waveOutClose(m_hWaveOut);
	HeapFree(GetProcessHeap(), 0, m_pWaveBlocks);
	m_pWaveBlocks = NULL;
	m_bSoundGenInitialized = FALSE;
}

void CBkSound::SoundGen_SetVolume(WORD volume)
{
	if (!m_bSoundGenInitialized)
	{
		return;
	}

	waveOutSetVolume(m_hWaveOut, MAKELONG(volume, volume));
}

WORD CBkSound::SoundGen_GetVolume()
{
	if (!m_bSoundGenInitialized)
	{
		return 0;
	}

	DWORD vol = 0;
	waveOutGetVolume(m_hWaveOut, &vol);
	return max(LOWORD(vol), HIWORD(vol));
}

void CBkSound::SoundGen_FeedDAC(SAMPLE_TYPE L, SAMPLE_TYPE R)
{
	if (!m_bSoundGenInitialized)
	{
		return;
	}

	m_mBufferST[m_nBufCurPos++] = L;
	m_mBufferST[m_nBufCurPos++] = R;

	if (m_nBufCurPos >= m_nBufSizeW)
	{
		m_nBufCurPos = 0;
		WAVEHDR *current = &m_pWaveBlocks[m_nWaveCurrentBlock++];

		if (m_nWaveCurrentBlock >= BLOCK_COUNT)
		{
			m_nWaveCurrentBlock = 0;
		}

		if (current->dwFlags & WHDR_PREPARED)
		{
			waveOutUnprepareHeader(m_hWaveOut, current, sizeof(WAVEHDR));
		}

		memcpy(current->lpData, m_mBufferST, m_nBufSize);
		current->dwBufferLength = m_nBufSize;
		waveOutPrepareHeader(m_hWaveOut, current, sizeof(WAVEHDR));
		waveOutWrite(m_hWaveOut, current, sizeof(WAVEHDR));
		EnterCriticalSection(&m_csWaveCriticalSection);
		m_nWaveFreeBlockCount--;
		LeaveCriticalSection(&m_csWaveCriticalSection);

		while (!m_nWaveFreeBlockCount)
		{
			Sleep(1);
		}
	}
}


void CBkSound::SoundGen_FeedDAC_Mixer(SAMPLE_TYPE &L, SAMPLE_TYPE &R)
{
	if (!m_bSoundGenInitialized)
	{
		return;
	}

	L = (SAMPLE_TYPE)(m_dSampleL * FLOAT_BASE);
	R = (SAMPLE_TYPE)(m_dSampleR * FLOAT_BASE);
	m_mBufferST[m_nBufCurPos++] = L;
	m_mBufferST[m_nBufCurPos++] = R;

	if (m_nBufCurPos >= m_nBufSizeW)
	{
		m_nBufCurPos = 0;
		WAVEHDR *current = &m_pWaveBlocks[m_nWaveCurrentBlock++];

		if (m_nWaveCurrentBlock >= BLOCK_COUNT)
		{
			m_nWaveCurrentBlock = 0;
		}

		if (current->dwFlags & WHDR_PREPARED)
		{
			waveOutUnprepareHeader(m_hWaveOut, current, sizeof(WAVEHDR));
		}

		memcpy(current->lpData, m_mBufferST, m_nBufSize);
		current->dwBufferLength = m_nBufSize;
		waveOutPrepareHeader(m_hWaveOut, current, sizeof(WAVEHDR));
		waveOutWrite(m_hWaveOut, current, sizeof(WAVEHDR));
		EnterCriticalSection(&m_csWaveCriticalSection);
		m_nWaveFreeBlockCount--;
		LeaveCriticalSection(&m_csWaveCriticalSection);

		while (!m_nWaveFreeBlockCount)
		{
			Sleep(1);
		}
	}
}

void CBkSound::SoundGen_SetSample(double &L, double &R)
{
	m_dSampleL = L;
	m_dSampleR = R;
}

void CBkSound::SoundGen_MixSample(double &L, double &R)
{
	m_dSampleL = L + m_dSampleL - L * m_dSampleL;
	m_dSampleR = R + m_dSampleR - R * m_dSampleR;
}

