#include "pch.h"
#include "MediaProc.h"
#include "Config.h"
#include "Platform.h"
#include "BK001001.h"
#include "BKMessageBox.h"


CMediaProc::CMediaProc()
#if (MED_THREADED_MODEL)
	: m_bThreadTerminate(false)
	, m_bNotify(false)
#endif
{
}


CMediaProc::~CMediaProc()
{}

void CMediaProc::init()
{
	m_sMTV.init();
	fCpuTickTime = 0.0;
	fMedia_Mod = 0.0;
	fMediaTicks = 0.0;
#if (MED_THREADED_MODEL)
	StartThread();
#endif
}


void CMediaProc::clear()
{
	fMediaTicks = 0.0;
}

void CMediaProc::uninit()
{
#if (MED_THREADED_MODEL)
	StopThread();
#endif
	SAFE_DELETE_ARRAY(m_sMTV.pSoundBuffer);
}

void CMediaProc::InitEmulator()
{
	// инициализация текущих настроек
	m_Speaker.EnableSound(g_Config.m_bSpeaker);
	m_Speaker.SetFilter(g_Config.m_bSpeakerFilter);
	m_Speaker.SetDCOffsetCorrect(g_Config.m_bSpeakerDCOffset);
	m_Covox.EnableSound(g_Config.m_bCovox);
	m_Covox.SetFilter(g_Config.m_bCovoxFilter);
	m_Covox.SetStereo(g_Config.m_bStereoCovox);
	m_Covox.SetDCOffsetCorrect(g_Config.m_bCovoxDCOffset);
	m_Menestrel.EnableSound(g_Config.m_bMenestrel);
	m_Menestrel.SetFilter(g_Config.m_bMenestrelFilter);
	m_Menestrel.SetDCOffsetCorrect(g_Config.m_bMenestrelDCOffset);
	m_Menestrel.SetStereo(true); // всегда стерео
	m_AY8910.EnableSound(g_Config.m_bAY8910);
	m_AY8910.SetFilter(g_Config.m_bAY8910Filter);
	m_AY8910.SetDCOffsetCorrect(g_Config.m_bAY8910DCOffset);
	m_AY8910.SetStereo(true); // всегда стерео
	m_Tape.SetWaveParam(g_Config.m_nSoundSampleRate, BUFFER_CHANNELS);
	m_sMTV.nMediaTickCount = 0;
	m_sMTV.nBufPos = 0;
}

int CMediaProc::InitObjects()
{
	int nMtc = m_Sound.ReInit();   // пересоздаём звук с новыми параметрами, на выходе - длина медиафрейма в сэмплах
	m_Speaker.ReInit(); // ещё надо переинициализировать устройства, там
	m_Speaker.ConfigureTapeBuffer(nMtc);    // переопределяем буферы в зависимости от текущей частоты дискретизации
	m_Covox.ReInit();   // есть вещи, зависящие от частоты дискретизации,
	m_Menestrel.ReInit();
	m_AY8910.ReInit();  // которая теперь величина переменная. Но требует перезапуска конфигурации.
	// переменные для медиа системы
	m_sMTV.nMediaTicksPerFrame = nMtc;
	SAFE_DELETE_ARRAY(m_sMTV.pSoundBuffer);
	m_sMTV.pSoundBuffer = new SAMPLE_INT[m_sMTV.nMediaTicksPerFrame * BUFFER_CHANNELS];

	if (!m_sMTV.pSoundBuffer)
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}

	return nMtc;
}

bool CMediaProc::StartPlayTape(const CString &strPath)
{
	CString strExt = ::GetFileExt(strPath);
	TAPE_FILE_INFO tfi;
	memset(&tfi, 255, sizeof(TAPE_FILE_INFO));

	if (!strExt.CompareNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_WAVE))))
	{
		m_Tape.LoadWaveFile(strPath);
	}
	else if (!strExt.CompareNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_TAPE))))
	{
		m_Tape.LoadMSFFile(strPath);
	}
	else if (!strExt.CompareNoCase(CString(MAKEINTRESOURCE(IDS_FILEEXT_BINARY))))
	{
		m_Tape.LoadBinFile(strPath, &tfi);
	}
	else
	{
		return false;
	}

	m_Tape.GetWaveFile(&tfi); // вычисляем и заполняем внутренние переменные
	m_Tape.SetWaveLoaded(true);
	return true;
}

void CMediaProc::MediaTick()
{
	// обработка медиатактов
	if (m_sMTV.nMediaTickCount == 0)
	{
		// Получаем сохранённые 1/CPU_FRAMERATE секунды звуковые данные
		// Берём данные с ленты -> m_pSoundBuffer, а если нечего брать, очищаем буфер
		m_Tape.PlayWaveGetBuffer(m_sMTV.pSoundBuffer, m_sMTV.nMediaTicksPerFrame);
		// Посылаем данные с ленты в 177716 <- m_pSoundBuffer
		m_Speaker.ReceiveTapeBuffer(m_sMTV.pSoundBuffer, m_sMTV.nMediaTicksPerFrame);
		m_sMTV.nBufPos = 0;
	}

	// осуществляем приём данных с магнитофона, так он будет работать даже если спикер выключен
	register uint16_t &r177716in = g_Platform.GetBoardPtr()->m_reg177716in;

	if (m_Speaker.GetTapeSample())
	{
		r177716in |= 040;
	}
	else
	{
		r177716in &= ~040;
	}

	register SAMPLE_INT fL = 0.0, fR = 0.0;
	m_Sound.SoundGen_ResetSample(m_sMTV.pSoundBuffer[m_sMTV.nBufPos], m_sMTV.pSoundBuffer[m_sMTV.nBufPos + 1]); // задаём начальные уровни для микширования

	// Берём звуки со спикера БК
	if (m_Speaker.IsSoundEnabled())
	{
		m_Speaker.GetSample(fL, fR);
		m_Sound.SoundGen_MixSample(fL, fR); // микшируем сэмплы внутри m_Sound
	}

	// Берём данные с ковокса
	if (m_Covox.IsSoundEnabled())
	{
		m_Covox.GetSample(fL, fR);
		m_Sound.SoundGen_MixSample(fL, fR);
	}

	if (m_Menestrel.IsSoundEnabled())
	{
		m_Menestrel.GetSample(fL, fR);
		m_Sound.SoundGen_MixSample(fL, fR);
	}

	// Берём данные с ay8910
	if (m_AY8910.IsSoundEnabled())
	{
		m_AY8910.GetSample(fL, fR);
		m_Sound.SoundGen_MixSample(fL, fR);
	}

	m_Sound.SoundGen_FeedDAC_Mixer(fL, fR); // получаем сюда смикшированное, а там - осуществляем звучание
	m_sMTV.pSoundBuffer[m_sMTV.nBufPos++] = fL; // и помещаем в m_pSoundBuffer, это нужно для осциллографа
	m_sMTV.pSoundBuffer[m_sMTV.nBufPos++] = fR; // и для сохранения результата звучания в wav

	if (++m_sMTV.nMediaTickCount >= m_sMTV.nMediaTicksPerFrame)
	{
		m_sMTV.nMediaTickCount = 0;
		// Отображаем буфер на осциллографе
		// сперва синхронно копируем текущий буфер в осциллограф, чтобы он его там отображал
		g_Platform.GetMainPtr()->SendMessage(WM_OSC_FILLBUFFER, 0, reinterpret_cast<LPARAM>(m_sMTV.pSoundBuffer));
		// и посылаем его асинхронно отображаться
		g_Platform.GetMainPtr()->PostMessage(WM_OSC_DRAW);
		// Отправляем их на ленту <- m_pSoundBuffer
		m_Tape.RecordWaveGetBuffer(m_sMTV.pSoundBuffer, m_sMTV.nMediaTicksPerFrame);
	}
}



void CMediaProc::clock()
{
#if (MED_THREADED_MODEL)
	{
		std::lock_guard<std::mutex> lk(m_mutex);
		m_bNotify = true;
	}
	m_cv.notify_one();
#else
	m_Speaker.RCFilterLF(fCpuTickTime); // эмуляция конденсатора на выходе линейного входа.

	if (--fMediaTicks <= 0.0)
	{
		do
		{
			MediaTick(); // тут делается звучание всех устройств и обработка прочих устройств
			fMediaTicks += fMedia_Mod;
		}
		while (fMediaTicks < 1.0);
	}

#endif
}

#if (MED_THREADED_MODEL)

void CMediaProc::StopThread()
{
	{
		std::lock_guard<std::mutex> lk(m_mutex);
		m_bThreadTerminate = true;
		m_bNotify = true;
	}
	m_cv.notify_one();
	// вот такая странная ситуация: после подачи notify_one, поток не успевает
	// пробудиться и захватить мутекс, и деструктор успевает убить объект CHDD.
	// а поток остаётся в памяти. Потому что мутекс захватывается тут, а потом
	// сразу освобождается. Поэтому приходится добавлять ещё один мутекс,
	// который залочен всегда, пока поток существует, и разлочивается при его
	// уничтожении, а тут мы ждём, пока сможем залочить этот второй мутекс
	std::lock_guard<std::mutex> lk(m_mutThr);
}

bool CMediaProc::StartThread()
{
	m_ExecuteThread = std::thread(&CMediaProc::ExecuteThreadFunc, this);

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

	return false;
}

void CMediaProc::ExecuteThreadFunc()
{
	std::lock_guard<std::mutex> lk(m_mutThr);

	for (;;)
	{
		std::unique_lock<std::mutex> lk(m_mutex);
		m_cv.wait(lk, [&]()
		{
			return m_bNotify;
		});
		m_bNotify = false;
		lk.unlock();

		if (m_bThreadTerminate) // сигнал к остановке потока получен?
		{
			break;   // и выходим
		}

		m_Speaker.RCFilter(fCpuTickTime); // эмуляция конденсатора на выходе линейного входа.

		if (--fMediaTicks <= 0.0)
		{
			do
			{
				MediaTick(); // тут делается звучание всех устройств и обработка прочих устройств
				fMediaTicks += fMedia_Mod;
			}
			while (fMediaTicks < 1.0);
		}
	}
}

#endif
