// BKSoundDevice.cpp: implementation of the CBKSoundDevice class.
//


#include "pch.h"
#include "SafeReleaseDefines.h"
#include "BKSoundDevice.h"

// #include <cmath>
// #if defined TARGET_WINXP
// #define _USE_MATH_DEFINES
// #include <math.h>
// #else
// #include <corecrt_math_defines.h>
// #endif
// #include <utility> // там есть #include <algorithm>
/////////////////////////////////////////////////////////////////////////////
// Construction/Destruction

CBKSoundDevice::CBKSoundDevice()
	: m_dAvgL(0.0)
	, m_dAvgR(0.0)
	, m_dAccL(0.0)
	, m_dAccR(0.0)
	, m_nDCBufPosL(0)
	, m_nDCBufPosR(0)
	, m_pH(nullptr)
	, m_nFirLength(FIR_LENGTH)
	, m_pdFBufL(nullptr)
	, m_pdFBufR(nullptr)
	, m_nFBufPosL(0)
	, m_nFBufPosR(0)
	, m_RCFVal(1.0)
	, m_bEnableSound(true)
	, m_bFilter(true)
	, m_bDCOffset(false)
	, m_bStereo(false)
{
	m_pdDCBufL = std::make_unique<double[]>(DCOFFSET_BUFFER_LEN);
	m_pdDCBufR = std::make_unique<double[]>(DCOFFSET_BUFFER_LEN);

	if (m_pdDCBufL && m_pdDCBufR)
	{
		for (int i = 0; i < DCOFFSET_BUFFER_LEN; ++i)
		{
			m_pdDCBufL[i] = m_pdDCBufR[i] = 0.0;
		}
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}
}

CBKSoundDevice::~CBKSoundDevice()
{
}

// автоматическое выравнивание уровня, очень полезно для ковокса, для AY - так себе,
double CBKSoundDevice::DCOffset(const double sample, double *pBuf, int &nBufPos, double &fAvg) const
{
	/*
	идея такая:
	avg0 = (buf[0] + buf[1] + ... + buf[buf_len-1]) / buf_len

	avg1 = avg0 - buf[buf_len-1] / buf_len + sample / buf_len

	не нужно сдвигать содержимое буфера, выкидывая последнее значение и добавляя в начало текущее.
	и не нужно каждый раз суммировать всё содержимое буфера, для расчёта среднего значения.
	имеем указатель на позицию в буфере, прочитанное значение из буфера - это выкидываемый последний сэмпл
	а занесение нового значения - запись текущего сэмпла в буфер на заданную позицию.
	*/
	fAvg += (sample - pBuf[nBufPos]) * (1.0 / double(DCOFFSET_BUFFER_LEN));
	pBuf[nBufPos] = sample;
	++nBufPos &= DCOFFSET_BUFFER_MASK;
	// устраняем клиппинг, может появляться из-за резкой смены знака амплитуды
	const double t = (sample - fAvg) * 2.0; // распространяем амплитуду на оба полупериода
	constexpr double b = double(MAX_SAMPLE) / FLOAT_BASE;

	if (t > b)
	{
		return b;
	}

	if (t < -b)
	{
		return -b;
	}

	return t;
}

/*На входе:
sample - новый, поступающий сэмпл.
pBuf - кольцевой буфер сэмплов, длиной m_nFirLength
nBufPos - позиция в буфере, pBuf, куда помещать сэмпл.
*/
double CBKSoundDevice::FIRFilter(const double sample, double *pBuf, int &nBufPos) const
{
	// поместим новый сэмпл в буфер.
	pBuf[nBufPos] = sample;

	if (++nBufPos >= m_nFirLength)
	{
		nBufPos = 0;
	}

	if (m_bFilter)
	{
		// теперь nBufPos указывает на начало, откуда брать сэмплы для фильтрации
		double dAcc = 0.0;

		for (int i = 0, j = nBufPos; i < m_nFirLength; ++i)
		{
			dAcc += m_pH[i] * pBuf[j];

			if (++j >= m_nFirLength)
			{
				j = 0;
			}
		}

		// ограничение амплитуды
		constexpr double b = double(MAX_SAMPLE) / FLOAT_BASE;

		if (dAcc > b)
		{
			return b;
		}

		if (dAcc < -b)
		{
			return -b;
		}

		return dAcc;
	}

	return sample;
}

// хрень какая-то а не компрессор. Очень искажает сигнал.
// надо искать другие алгоритмы.
double CBKSoundDevice::Compressor(const double inputSignal)
{
	// Compression threshold in decibels (dB)
	constexpr double compressionThreshold = -20.0;

	// Compression ratio
	constexpr double compressionRatio = 2.0;

	// Convert the input signal to decibels (dB)
	double inputSignal_dB = 20 * std::log10(std::abs(inputSignal));

	// Check if the signal exceeds the compression threshold
	if (inputSignal_dB > compressionThreshold)
	{
		// Calculate the compression gain reduction
		double gainReduction = (inputSignal_dB - compressionThreshold) / compressionRatio;

		// Apply the gain reduction to the signal
		double compressedSignal = inputSignal * std::pow(10, (-gainReduction / 20));
		return compressedSignal;
	}

	return inputSignal;
}

bool CBKSoundDevice::CreateFIRBuffers(int nLen)
{
	const int nLen1 = nLen + 1;

	if (m_pH)
	{
		m_pH.reset();
	}

	m_pH = std::make_unique<double[]>(nLen1);

	if (m_pdFBufL)
	{
		m_pdFBufL.reset();
	}

	m_pdFBufL = std::make_unique<double[]>(nLen1);

	if (m_pdFBufR)
	{
		m_pdFBufR.reset();
	}

	m_pdFBufR = std::make_unique<double[]>(nLen1);

	if (m_pH && m_pdFBufL && m_pdFBufR)
	{
		memset(m_pH.get(),      0, nLen1 * sizeof(double));
		memset(m_pdFBufL.get(), 0, nLen1 * sizeof(double));
		memset(m_pdFBufR.get(), 0, nLen1 * sizeof(double));
		m_nFirLength = nLen;
		return true;
	}

	return false;
}


// почти работает.
// в реальности там получается разное сопротивление для разных уровней,
// но и так работает почти как в оригинале.
void CBKSoundDevice::RCFilter(sRCFparam &p, const double fAcc, const double fTime) const
{
	if (fAcc > p.fUi_prev)
	{
		p.fUi_prev = fAcc;
		p.fminvol = RCFilterCalc(p);    // от этого уровня начинаем
		p.fmaxvol = fAcc;               // до этого уровня постараемся дойти
		p.bRCProc = true;               // надо заряжать

		if (p.fmaxvol < p.fminvol)  // так не бывает, но всё же, если конденсатор был заряжен сильнее, чем сейчас уровень
		{
			std::swap(p.fmaxvol, p.fminvol); // то надо разряжать
			p.bRCProc = false;
		}

		p.fdeltavol = p.fmaxvol - p.fminvol; // дельта - величина, насколько подскочило напряжение относительно заряда конденсатора
		p.ft = fTime;
	}
	else if (fAcc < p.fUi_prev)
	{
		p.fUi_prev = fAcc;
		p.fmaxvol = RCFilterCalc(p);    // от этого уровня начинаем
		p.fminvol = fAcc;               // к этому уровню постараемся дойти
		p.bRCProc = false;              // надо разряжать

		if (p.fmaxvol < p.fminvol)  // если конденсатор был заряжен меньше, чем сейчас уровень
		{
			std::swap(p.fmaxvol, p.fminvol); // то надо заряжать
			p.bRCProc = true;
		}

		p.fdeltavol = p.fmaxvol - p.fminvol;  // дельта - величина, насколько изменилось напряжение относительно заряда конденсатора
		p.ft = fTime;
	}
	else
	{
		p.ft += fTime;  // если напряжение держится одного уровня - просто продолжаем процесс
	}
}

SAMPLE_INT CBKSoundDevice::RCFilterCalc(sRCFparam &p) const
{
	// return m_fminvol + m_fdeltavol * (1 - exp(-(m_ft / (6.8e-9 * 8200))));
	// ниже - эта же функция с раскрытыми скобками, на одно действие меньше.
	const double v = p.fdeltavol * exp(-(p.ft / m_RCFVal));

	if (p.bRCProc)
	{
		// зарядка
		return p.fmaxvol - v;
	}

	// разрядка
	return p.fminvol + v;
}

