#include "pch.h"
#include "AZBK.h"

#include "ima.h"

/////////////////////////////////////////////////////////////////////////////////
//
// AZ DMA Sound
//
/////////////////////////////////////////////////////////////////////////////////

void CAZBK::SetDMAWork()
{
	if (m_AZRegs.nDMACSR & (1 << 2))
	{
		StopDMAThread();
	}
	else if (m_AZRegs.nDMACSR & (1 << 0))
	{
		StartDMAThread();
	}
}

void CAZBK::StopDMAThread()
{
	if (m_lockDMAThr.IsLocked()) // если поток запущен
	{
		m_lockDMA.Lock();

		do
		{
			SleepEx(10, FALSE);
		}
		while (m_lockDMAThr.IsLocked());
	}
}

bool CAZBK::StartDMAThread()
{
	if (!m_lockDMAThr.IsLocked()) // если поток ещё не запущен
	{
		m_DMAThread = std::thread(&CAZBK::DMAThreadFunc, this);

		if (m_DMAThread.joinable())
		{
			m_DMAThread.detach();
			return true;
		}
	}

	return false;
}

void CAZBK::DMAThreadFunc() const
{
	m_lockDMAThr.Lock();
	const int nSSRnum = (m_AZRegs.nDMACSR >> 6) & 7;
	int nSSR = 0;

	switch (nSSRnum)
	{
		default:
		case 0:
			nSSR = 44100;
			break;
	}

	m_pAZBKSound->ReInit(nSSR);
	const int nMode = (m_AZRegs.nDMACSR >> 9) & 7; // режим интерпретации
	const uint32_t nBufSizePCM = m_pAZBKSound->getBufSizeAZ();
	// для моно, буфер надо ещё в 2 раза уменьшить, т.к. делается неявное преобразование 
	// моно в два канала, и чтобы не было переполнения выходного буфера, данных
	// должно быть в 2 раза меньше
	const uint32_t nBufSizeIma = (nMode == 4) ? nBufSizePCM / 8 : nBufSizePCM / 4;

	auto pBufPCM = std::vector<uint8_t>(nBufSizePCM);
	auto pBufPCMdata = reinterpret_cast<SAMPLE_IO *>(pBufPCM.data());
	auto pBufIMA = std::vector<uint8_t>(nBufSizeIma);
	ima_state state[2];
	memset(&state, 0, sizeof(state));
	uint32_t nSndPtr = 0;
	uint32_t nImaPtr = 0;
	uint32_t nPCMPtr = 0;
	uint32_t nDataPtr = 0;
	m_AZRegs.nDMACurrentPage = m_AZRegs.nDMAStartPage;
	auto pBufData = GetMainMemory() + AZ_BANK_TO_OFFSET(size_t(m_AZRegs.nDMACurrentPage));

	if (nMode == 0)
	{
		for (;;)
		{
			if (m_lockDMA.IsLocked())   //событие остановки
			{
				break;
			}

			pBufPCM[nPCMPtr++] = pBufData[nDataPtr++]; nSndPtr++; // берём очередной байт
			pBufPCM[nPCMPtr++] = pBufData[nDataPtr++]; nSndPtr++; // одно слово

			if (nPCMPtr >= nBufSizePCM)
			{
				// пришло время отправить буфер звучать
				m_pAZBKSound->SoundGen_FeedDAC_MixerAZ(pBufPCMdata);
			}

			if (nDataPtr >= 010000)
			{
				// пришло время переходить к следующему сегменту
				nDataPtr = 0;

				if (nSndPtr >= m_AZRegs.nDMAFileSize * 2)
				{
					nSndPtr = 0;
					m_AZRegs.nDMACurrentPage = m_AZRegs.nDMAStartPage;
					const bool bLoop = !(m_AZRegs.nDMACSR & (1 << 3)) ? !(m_AZRegs.nDMACSR & (1 << 1)) : false;

					if (!bLoop)
					{
						m_AZRegs.nDMACSR |= (1 << 3);
						break;
					}
				}
				else
				{
					m_AZRegs.nDMACurrentPage++;
				}

				if (!(m_AZRegs.nDMACSR & (1 << 0)))
				{
					break;
				}

				pBufData = GetMainMemory() + AZ_BANK_TO_OFFSET(size_t(m_AZRegs.nDMACurrentPage));
			}
		}
	}
	else
	{
		for (;;)
		{
			if (m_lockDMA.IsLocked())   //событие остановки
			{
				break;
			}

			pBufIMA[nImaPtr++] = pBufData[nDataPtr++]; nSndPtr++; // берём очередной байт

			if (nImaPtr >= nBufSizeIma)
			{
				nImaPtr = 0;

				// пришло время декодировать буфер и отправить его звучать
				switch (nMode)
				{
					case 4:
						// !!! эта функция подготавливает моно дорожку, а мы ей пдсовываем стерео буфер
						ima_decode_mono2s(state, pBufPCMdata, pBufIMA.data(), nBufSizeIma);
						break;

					case 5:
						ima_decode_stereo(state, pBufPCMdata, pBufIMA.data(), nBufSizeIma);
						break;

					default:
						memset(pBufPCM.data(), 0, nBufSizePCM);
						break;
				}

				m_pAZBKSound->SoundGen_FeedDAC_MixerAZ(pBufPCMdata);
			}

			if (nDataPtr >= 010000)
			{
				// пришло время переходить к следующему сегменту
				nDataPtr = 0;

				if (nSndPtr >= m_AZRegs.nDMAFileSize * 2)
				{
					nSndPtr = 0;
					m_AZRegs.nDMACurrentPage = m_AZRegs.nDMAStartPage;
					const bool bLoop = !(m_AZRegs.nDMACSR & (1 << 3)) ? !(m_AZRegs.nDMACSR & (1 << 1)) : false;

					if (!bLoop)
					{
						m_AZRegs.nDMACSR |= (1 << 3);
						break;
					}

					if (!(m_AZRegs.nDMACSR & (1 << 4)))
					{
						memset(&state, 0, sizeof(state));
					}
				}
				else
				{
					m_AZRegs.nDMACurrentPage++;
				}

				if (!(m_AZRegs.nDMACSR & (1 << 0)))
				{
					break;
				}

				pBufData = GetMainMemory() + AZ_BANK_TO_OFFSET(size_t(m_AZRegs.nDMACurrentPage));
			}
		}
	}

	m_lockDMA.UnLock(); //выставляем событие, что таймер остановился, и можно продолжать остальные действия по остановке
	m_lockDMAThr.UnLock();
}


