/****************************************************************************

emu2149.c -- YM2149/AY-3-8910 emulator by Mitsutaka Okazaki 2001

2001 04-28 : Version 1.00beta -- 1st Beta Release.
2001 08-14 : Version 1.10
2001 10-03 : Version 1.11     -- Added PSG_set_quality().

References:
psg.vhd        -- 2000 written by Kazuhiro Tsujikawa.
s_fme7.c       -- 1999,2000 written by Mamiya (NEZplug).
ay8910.c       -- 1998-2001 Author unknown (MAME).
MSX-Datapack   -- 1991 ASCII Corp.
AY-3-8910 data sheet

теперь уже мало что осталось от оригинала, добавлены более качественные
алгоритмы из ZX Speccy

*****************************************************************************/
#include "stdafx.h"
#include "emu2149.h"
#include "Defines.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#define new DEBUG_NEW
#endif

#define EMU2149_VOL_DEFAULT 1
#define EMU2149_VOL_YM2149 0
#define EMU2149_VOL_AY_3_8910 1

#define GETA_BITS 24

#define CHAN_A 0
#define CHAN_B 1
#define CHAN_C 2

//#define EMU_SOUNCHIP_FREQUENCY 1774400 //zx spectrum
#define EMU_SOUNCHIP_FREQUENCY 3428572 //BK: (12Mhz / 7) * 2
//#define EMU_SOUNCHIP_FREQUENCY 3579545 //непонятно что, но звук такой, как надо.

const int CEMU2149::voltbl[2][32] =
{
	{
		0x0000, 0x0000, 0x0340, 0x0340, 0x04C0, 0x04C0, 0x06F2, 0x06F2, 0x0A44, 0x0A44, 0x0F13, 0x0F13, 0x1510, 0x1510, 0x227E, 0x227E,
		0x289F, 0x289F, 0x414E, 0x414E, 0x5B21, 0x5B21, 0x7258, 0x7258, 0x905E, 0x905E, 0xB550, 0xB550, 0xD7A0, 0xD7A0, 0xFFFF, 0xFFFF
	},
	{
		0x0000, 0x0000, 0x00EF, 0x01D0, 0x0290, 0x032A, 0x03EE, 0x04D2, 0x0611, 0x0782, 0x0912, 0x0A36, 0x0C31, 0x0EB6, 0x1130, 0x13A0,
		0x1751, 0x1BF5, 0x20E2, 0x2594, 0x2CA1, 0x357F, 0x3E45, 0x475E, 0x5502, 0x6620, 0x7730, 0x8844, 0xA1D2, 0xC102, 0xE0A2, 0xFFFF
	}
};

const int CEMU2149::pantbl[6] = { 100, 10, 66, 66, 10, 100 };

CEMU2149::CEMU2149()
{
	m_nSynthReg = 0xff;
	PSG_init(EMU_SOUNCHIP_FREQUENCY, BUFFER_FREQUENCY);
	PSG_reset(); //использует значения, заданные в PSG_init
	PSG_setVolumeMode(EMU2149_VOL_DEFAULT);
}

CEMU2149::~CEMU2149()
{
}

void CEMU2149::PSG_init(int c, int r)
{
	m_nClock = c;
	m_nRate = r;
	m_nRealStep = (int)(2147483648.0 / (double) m_nRate);        //2147483648.0 == (1<<31)
	m_nPSGStep = (int)(2147483648.0 / (double)(m_nClock >> 4));
	PSG_set_quality(TRUE);
}

void CEMU2149::PSG_set_quality(BOOL q)
{
	m_bHQ = q;

	if (q)
	{
		m_nBaseIncrement = 1 << GETA_BITS;
	}
	else if (m_nRate)
	{
		m_nBaseIncrement = (int)((double) m_nClock * (double)(1 << GETA_BITS) / (double)(m_nRate << 4));
	}
	else
	{
		m_nBaseIncrement = (int)((double) m_nClock * (double)(1 << GETA_BITS) / (double)(1 << 4));
	}
}

void CEMU2149::set_volumes(double global_vol, const int *voltab, const int *stereo)
{
	for (int j = 0; j < 6; j++)
		for (int i = 0; i < 32; i++)
		{
			vols[j][i] = (((global_vol * voltab[i] * stereo[j]) / (FLOAT_BASE * 100.0 * 3.0))) / FLOAT_BASE;
		}
}

void CEMU2149::PSG_setVolumeMode(int type)
{
	const int *pvoltbl; //таблица громкостей

	switch (type)
	{
		case 1:
			pvoltbl = voltbl[EMU2149_VOL_YM2149];
			break;

		case 2:
			pvoltbl = voltbl[EMU2149_VOL_AY_3_8910];
			break;

		default:
			pvoltbl = voltbl[EMU2149_VOL_DEFAULT];
			break;
	}

	set_volumes(EMU_GLOBAL_VOLUME, pvoltbl, pantbl);
}

void CEMU2149::PSG_reset()
{
	m_nBaseCount = 0;
	m_nPSGTime = 0;
	m_nAddr = 0;

	for (int i = 0; i < 16; i++)
	{
		reg[i] = 0;
	}

	for (int i = 0; i < 3; i++)
	{
		count[i] = 0;
		freq[i] = 0;
		volume[i] = 0;
		tone_toggle[i] = 0;
		env[i] = 0;
		nmask[i] = -1;
		tmask[i] = -1;
	}

	m_nNoiseSeed = 0xffff;
	m_nNoiseCount = 0;
	m_nNoiseFreq = 0;
	m_nEnvPtr = 0;
	m_nDEnv = 0;
	m_nEnvFreq = 0;
	m_nEnvCount = 0;
	m_bEnvAttack = FALSE;
	m_bEnvLValue = FALSE;
	m_bEnvHold = FALSE;
	m_bEnvAlt = FALSE;
}

BYTE CEMU2149::PSG_readIO()
{
	return (BYTE)(reg[m_nAddr]);
}

BYTE CEMU2149::PSG_readReg(int r)
{
	return (BYTE)(reg[r & 0x0F]);
}

void CEMU2149::PSG_writeIO(DWORD nAddr, BYTE val)
{
	if (nAddr & 1)   //если адрес регистра нечётный
	{
		PSG_writeReg(m_nAddr, val);    //то запись значения по адресу, определяемому 4-мя мл. битами
	}
	else
	{
		m_nAddr = val & 0x0f;    //а иначе - значение и есть адрес, который потом используется в PSG_readIO
	}

	//это наверное какой-то алгоритм, используемый в Z80
}

void CEMU2149::PSG_writeReg(int rg, BYTE val)
{
	rg &= 0x0F;
	int t;

	if ((1 << rg) & ((1 << 1) | (1 << 3) | (1 << 5) | (1 << 13)))
	{
		val &= 0x0F;
	}

	if ((1 << rg) & ((1 << 6) | (1 << 8) | (1 << 9) | (1 << 10)))
	{
		val &= 0x1F;
	}

	reg[rg] = val;

	switch (rg)
	{
		//0,2,4 - младшие 8 бит частоты каналов A,B,C
		//1,3,5 - старшие 4 бита частоты каналов A,B,C
		case 0:
		case 1:
			freq[CHAN_A] = r.fA;
			break;

		case 2:
		case 3:
			freq[CHAN_B] = r.fB;
			break;

		case 4:
		case 5:
			freq[CHAN_C] = r.fC;
			break;

		//регистр генератор шума
		case 6:
			m_nNoiseFreq = (val << 1);   //0..3e
			break;

		//регистр управления смесителем, 0 - разрешено, 1 - запрещено
		//биты 3..1 - флаг разрешения тона каналов C,B,A
		//биты 6..4 - флаг разрешения шума каналов C,B,A
		//биты 8..7 - флаг ввода/вывода в порты B,A: 0 - in, 1 - out
		/*
		    на AY8910 если регистром-миксером отключить тон и шум то при записи
		    потоковых данных в регистр громкости AY превращается в 4-х битный
		    ЦАП. таким образом на нем проигрывают оцифрованные записи.
		    Если в регистре громкости выставить в 1 пятый бит, то на выход
		    попрет форма огибающей, чем успешно пользуются музыканты.
		    т.е. если в регисте громкости 020, то огибающая будет звучать
		    независимо от того, включен ли канал в миксере.
		    В AY8930 эту особенность судя по всему не учли.
		*/
		case 7:
			tmask[CHAN_A] = 0 - ((val >> 0) & 1); //тон 0 - включен, -1 - выключен
			tmask[CHAN_B] = 0 - ((val >> 1) & 1);
			tmask[CHAN_C] = 0 - ((val >> 2) & 1);
			nmask[CHAN_A] = 0 - ((val >> 3) & 1); //шум 0 - включен, -1 - выключен
			nmask[CHAN_B] = 0 - ((val >> 4) & 1);
			nmask[CHAN_C] = 0 - ((val >> 5) & 1);
			break;

		//амплитуда каналов A,B,C, мл 4 бита - амплитуда, бит 5 - V - флаг выбора волнового пакета,
		case 8:
		case 9:
		case 10:
			t = rg - 8;

			if (val & 0x10)
			{
				env[t] = -1;
			}
			else
			{
				env[t] = 0;
			}

			volume[t] = (((val & 0x0f) << 1) + 1) & ~env[t];
			break;

		//11 - младшие 8 бит периода волнового пакета
		//12 - старшие 8 бит периода волнового пакета
		case 11:
		case 12:
			m_nEnvFreq = (DWORD) r.envT;
			break;

		/*форма волнового пакета
		    бит 4 - флаг цикличности, 1 - действует в зависимости от бита 1, один цикл или бесконечно ,
		        0 - в конце цикла счётчик сбросится в 0 и зафиксирует это значение
		    бит 3 - атака, 1 - возрастание, счёт от 0000 дл 1111, 0 - затухание, счёт от 1111 до 0000
		    бит 2 - alternate, 1 - счётчик огибающей меняет направление счёта каждый цикл.
		    бит 1 - удержание, 1 - ограничивается одним циклом, зафиксировав последнее значение счётчика
		    огибающей, 0000 или 1111, зависит от того, какой был отсчёт, по восходящей или по нисходящей
		*/
		case 13:
			if (val < 4)
			{
				val = 9;
			}
			else if (val < 8)
			{
				val = 15;
			}

			m_nEnvCount = 0;
			m_bEnvHold = ((val & 1) != 0);
			m_bEnvAlt = ((val & 2) != 0);
			m_bEnvAttack = ((val & 4) != 0);
			m_bEnvLValue = m_bEnvAttack ^ m_bEnvAlt;
			m_bEnvAlt = m_bEnvAlt && !m_bEnvHold;

			if (m_bEnvAttack)
			{
				m_nEnvPtr = 0;
				m_nDEnv = 1;
			}
			else
			{
				m_nEnvPtr = 0x1f;
				m_nDEnv = -1;
			}

			break;

		case 14:
		case 15:
		default:
			break;
	}

	return;
}

void CEMU2149::calc()
{
	DWORD noise_toggle, en;
	m_nBaseCount += m_nBaseIncrement;
	int incr = (m_nBaseCount >> GETA_BITS);
	m_nBaseCount &= (1 << GETA_BITS) - 1;
	/* Считаем огибающую волнового пакета*/
	m_nEnvCount += incr;

	if (m_nEnvCount >= m_nEnvFreq)
	{
		m_nEnvCount = 0;
		m_nEnvPtr += m_nDEnv;

		if (m_nEnvPtr & ~0x1f) //Цикл кончился?
		{
			if (m_bEnvAlt)  //если бит 2
			{
				//меняем направление счёта
				m_nDEnv = -m_nDEnv;
				m_nEnvPtr += m_nDEnv;
			}
			else
			{
				//или начинаем заново
				m_nEnvPtr &= 0x1f;
			}

			if (m_bEnvHold) //если бит 1 - то прекращаем счёт
			{
				m_nDEnv = 0;

				if (m_bEnvLValue)
				{
					m_nEnvPtr = 0x1f;
				}
				else
				{
					m_nEnvPtr = 0;
				}
			}
		}
	}

	/* Считаем шум */
	m_nNoiseCount += incr;

	if (m_nNoiseCount >= m_nNoiseFreq)
	{
		m_nNoiseCount = 0;

		if (m_nNoiseSeed & 1)
		{
			m_nNoiseSeed ^= 0x24000;
		}

		m_nNoiseSeed >>= 1;
	}

	noise_toggle = 0 - (m_nNoiseSeed & 1);
	/* Tone A*/
	count[CHAN_A] += incr;

	if (count[CHAN_A] >= freq[CHAN_A])
	{
		count[CHAN_A] = 0;

		if (freq[CHAN_A] > 1)
		{
			tone_toggle[CHAN_A] ^= -1;
		}
	}

	DWORD isON = ((tone_toggle[CHAN_A] | tmask[CHAN_A]) & (noise_toggle | nmask[CHAN_A]));
	en = ((env[CHAN_A] & m_nEnvPtr) | volume[CHAN_A]) & isON;
	m_fMixL = vols[0][en];
	m_fMixR = vols[1][en];
	/* Tone B*/
	count[CHAN_B] += incr;

	if (count[CHAN_B] >= freq[CHAN_B])
	{
		count[CHAN_B] = 0;

		if (freq[CHAN_B] > 1)
		{
			tone_toggle[CHAN_B] ^= -1;
		}
	}

	isON = ((tone_toggle[CHAN_B] | tmask[CHAN_B]) & (noise_toggle | nmask[CHAN_B]));
	en = ((env[CHAN_B] & m_nEnvPtr) | volume[CHAN_B]) & isON;
	m_fMixL += vols[2][en];
	m_fMixR += vols[3][en];
	/* Tone C*/
	count[CHAN_C] += incr;

	if (count[CHAN_C] >= freq[CHAN_C])
	{
		count[CHAN_C] = 0;

		if (freq[CHAN_C] > 1)
		{
			tone_toggle[CHAN_C] ^= -1;
		}
	}

	isON = ((tone_toggle[CHAN_C] | tmask[CHAN_C]) & (noise_toggle | nmask[CHAN_C]));
	en = ((env[CHAN_C] & m_nEnvPtr) | volume[CHAN_C]) & isON;
	m_fMixL += vols[4][en];
	m_fMixR += vols[5][en];
}

void CEMU2149::PSG_calc(SAMPLE_TYPE &L, SAMPLE_TYPE &R)
{
	if (!m_bHQ)
	{
		calc();
		L = (SAMPLE_TYPE)(m_fMixL * FLOAT_BASE);
		R = (SAMPLE_TYPE)(m_fMixR * FLOAT_BASE);
		return;
	}

	double f_mixL1 = 0.0;
	double f_mixR1 = 0.0;
	int n = 0;

	/* Simple m_nRate converter */
	do
	{
		calc();
		f_mixL1 += m_fMixL;
		f_mixR1 += m_fMixR;
		n++;
		m_nPSGTime += m_nPSGStep;
	}
	while (m_nRealStep > m_nPSGTime);

	m_nPSGTime -= m_nRealStep;
	L = (SAMPLE_TYPE)((f_mixL1 / n) * FLOAT_BASE); //если выбирать среднее арифметическое, то шум становится мягким
	R = (SAMPLE_TYPE)((f_mixR1 / n) * FLOAT_BASE);
//  L = (SAMPLE_TYPE)(m_fMixL * FLOAT_BASE); //а если выбирать отдельные сэмплы, качество на слух не страдает,
//  R = (SAMPLE_TYPE)(m_fMixR * FLOAT_BASE); //но шумовые эффекты гораздо резче выражены
}

void CEMU2149::NextTick()
{
	SAMPLE_TYPE L, R;
	PSG_calc(L, R);
	CDevice::NextTick();
}

/*
 АУшка использует только верхний полупериод сэмпла
*/
void CEMU2149::GetSample(double &sampleL, double &sampleR)
{
	SAMPLE_TYPE L, R;
	double fL, fR;

	if (m_bEnableSound)
	{
		PSG_calc(L, R);
		fL = (double) L / FLOAT_BASE;
		fR = (double) R / FLOAT_BASE;
//      для чистоты огибающей это лучше не включать.
		fL = AutoGain(fL, m_fAvgL, m_pdBufferL, m_nBufferPosL);
		fR = AutoGain(fR, m_fAvgR, m_pdBufferR, m_nBufferPosR);

		//простенький фильтр звука.
		if (m_bFilter)
		{
			m_feedbackL = (fL + m_feedbackL) / 2;
			fL = m_feedbackL;
			m_feedbackR = (fR + m_feedbackR) / 2;
			fR = m_feedbackR;
		}
	}
	else
	{
		fL = AutoGain(0.0, m_fAvgL, m_pdBufferL, m_nBufferPosL);
		fR = AutoGain(0.0, m_fAvgR, m_pdBufferR, m_nBufferPosR);
	}

	sampleL = fL;
	sampleR = fR;
}
