//
// https://github.com/gasman/libayemu  by Matt Westcott (gasman)
//
/* AY/YM emulator implementation. */

#include "ayemu.h"

#define debuglog stderr;

char *ayemu_err;

static const char VERSION[] = "libayemu 0.9";

const int MAGIC1 = 0xcdef;	/* for check ayemu_t structure inited */

enum {
	/* Max amplitude value for stereo signal for avoiding for possible
	folowwing SSRC for clipping */
	AYEMU_MAX_AMP = 24575,
	AYEMU_DEFAULT_CHIP_FREQ = 1773400
};

/* sound chip volume envelops (will calculated by gen_env()) */
static int bEnvGenInit = 0;
static int Envelope[16][128];


/* AY volume table (c) by V_Soft and Lion 17 */
static int Lion17_AY_table[16] =
{ 0, 513, 828, 1239, 1923, 3238, 4926, 9110,
10344, 17876, 24682, 30442, 38844, 47270, 56402, 65535 };

/* YM volume table (c) by V_Soft and Lion 17 */
static int Lion17_YM_table[32] =
{ 0, 0, 190, 286, 375, 470, 560, 664,
866, 1130, 1515, 1803, 2253, 2848, 3351, 3862,
4844, 6058, 7290, 8559, 10474, 12878, 15297, 17787,
21500, 26172, 30866, 35676, 42664, 50986, 58842, 65535 };

/* AY volume table (c) by Hacker KAY */
static int KAY_AY_table[16] =
{ 0, 836, 1212, 1773, 2619, 3875, 5397, 8823,
10392, 16706, 23339, 29292, 36969, 46421, 55195, 65535 };

/* YM volume table (c) by Hacker KAY */
static int KAY_YM_table[32] =
{ 0, 0, 248, 450, 670, 826, 1010, 1239,
1552, 1919, 2314, 2626, 3131, 3778, 4407, 5031,
5968, 7161, 8415, 9622, 11421, 13689, 15957, 18280,
21759, 26148, 30523, 34879, 41434, 49404, 57492, 65535 };

/* default equlaizer (layout) settings for AY and YM, 7 stereo types */
static const int default_layout[2][7][6] = {
	{
		/* A_l, A_r,  B_l, B_r,  C_l, C_r */

		/* for AY */
		{ 100, 100, 100, 100, 100, 100 },	// _MONO
		{ 100, 33, 70, 70, 33, 100 },	   // _ABC
		{ 100, 33, 33, 100, 70, 70 },	   // _ACB
		{ 70, 70, 100, 33, 33, 100 },	   // _BAC
		{ 33, 100, 100, 33, 70, 70 },	   // _BCA
		{ 70, 70, 33, 100, 100, 33 },	   // _CAB
		{ 33, 100, 70, 70, 100, 33 } },   // _CBA
		{
			/* for YM */
			{ 100, 100, 100, 100, 100, 100 },	// _MONO
			{ 100, 5, 70, 70, 5, 100 },	// _ABC
			{ 100, 5, 5, 100, 70, 70 },	// _ACB
			{ 70, 70, 100, 5, 5, 100 },	// _BAC
			{ 5, 100, 100, 5, 70, 70 },	// _BCA
			{ 70, 70, 5, 100, 100, 5 },	// _CAB
			{ 5, 100, 70, 70, 100, 5 } }   // _CBA
};


static int check_magic(ayemu_ay_t *ay)
{
	if (ay->magic == MAGIC1)
		return 1;
		//fprintf(stderr, "libayemu: passed pointer %p to uninitialized ayemu_ay_t structure\n", ay);
	return 0;
}


/* make chip hardware envelop tables.
Will execute once before first use. */
static void gen_env()
{
	int env;
	int pos;
	int hold;
	int dir;
	int vol;

	for (env = 0; env < 16; env++) {
		hold = 0;
		dir = (env & 4) ? 1 : -1;
		vol = (env & 4) ? -1 : 32;
		for (pos = 0; pos < 128; pos++) {
			if (!hold) {
				vol += dir;
				if (vol < 0 || vol >= 32) {
					if (env & 8) {
						if (env & 2) dir = -dir;
						vol = (dir > 0) ? 0 : 31;
						if (env & 1) {
							hold = 1;
							vol = (dir > 0) ? 31 : 0;
						}
					}
					else {
						vol = 0;
						hold = 1;
					}
				}
			}
			Envelope[env][pos] = vol;
		}
	}
	bEnvGenInit = 1;
}


/**
* \retval ayemu_init none.
*
*/
void ayemu_init(ayemu_ay_t *ay)
{
	ay->default_chip_flag = 1;
	ay->ChipFreq = AYEMU_DEFAULT_CHIP_FREQ;
	ay->default_stereo_flag = 1;
	ay->default_sound_format_flag = 1;
	ay->dirty = 1;
	ay->magic = MAGIC1;

	ayemu_reset(ay);
}

/** Reset AY/YM chip.
*
* \arg \c ay - pointer to ayemu_ay_t structure.
* \return none.
*/
void ayemu_reset(ayemu_ay_t *ay)
{
	if (!check_magic(ay)) return;

	ay->cnt_a = ay->cnt_b = ay->cnt_c = ay->cnt_n = ay->cnt_e = 0;
	ay->bit_a = ay->bit_b = ay->bit_c = ay->bit_n = 0;
	ay->env_pos = ay->EnvNum = 0;
	ay->Cur_Seed = 0xffff;

	ay->regs.tone_a = ay->regs.tone_b = ay->regs.tone_c = 0;
	ay->regs.noise = 0;
	ay->regs.R7_tone_a = ay->regs.R7_tone_b = ay->regs.R7_tone_c = 0;
	ay->regs.R7_noise_a = ay->regs.R7_noise_b = ay->regs.R7_noise_c = 0;
	ay->regs.vol_a = ay->regs.vol_b = ay->regs.vol_c = 0;
	ay->regs.env_a = ay->regs.env_b = ay->regs.env_c = 0;
	ay->regs.env_freq = ay->regs.env_style = 0;
}


static void set_table_ay(ayemu_ay_t *ay, int tbl[16])
{
	int n;
	for (n = 0; n < 32; n++)
		ay->table[n] = tbl[n / 2];
	ay->type = AYEMU_AY;
}

static void set_table_ym(ayemu_ay_t *ay, int tbl[32])
{
	int n;
	for (n = 0; n < 32; n++)
		ay->table[n] = tbl[n];
	ay->type = AYEMU_YM;
}


/** Set chip type. */
int ayemu_set_chip_type(ayemu_ay_t *ay, ayemu_chip_t type, int *custom_table)
{
	if (!check_magic(ay))
		return 0;

	if (!(type == AYEMU_AY_CUSTOM || type == AYEMU_YM_CUSTOM) && custom_table != NULL) {
		ayemu_err = "For non-custom chip type 'custom_table' param must be NULL";
		return 0;
	}

	switch (type) {
	case AYEMU_AY:
	case AYEMU_AY_LION17:
		set_table_ay(ay, Lion17_AY_table);
		break;
	case AYEMU_YM:
	case AYEMU_YM_LION17:
		set_table_ym(ay, Lion17_YM_table);
		break;
	case AYEMU_AY_KAY:
		set_table_ay(ay, KAY_AY_table);
		break;
	case AYEMU_YM_KAY:
		set_table_ym(ay, KAY_YM_table);
		break;
	case AYEMU_AY_CUSTOM:
		set_table_ay(ay, custom_table);
		break;
	case AYEMU_YM_CUSTOM:
		set_table_ym(ay, custom_table);
		break;
	default:
		ayemu_err = "Incorrect chip type";
		return 0;
	}

	ay->default_chip_flag = 0;
	ay->dirty = 1;
	return 1;
}


/** Set chip frequency. */
void ayemu_set_chip_freq(ayemu_ay_t *ay, int chipfreq)
{
	if (!check_magic(ay)) return;

	ay->ChipFreq = chipfreq;
	ay->dirty = 1;
}

/*! Set output sound format
* \arg \c ay - pointer to ayemu_t structure
* \arg \c freq - sound freq (44100 for example)
* \arg \c chans - number of channels (1-mono, 2-stereo)
* \arg \c bits - now supported only 16 and 8.
* \retval \b 1 on success, \b 0 if error occure
*/
int ayemu_set_sound_format(ayemu_ay_t *ay, int freq, int chans, int bits)
{
	if (!check_magic(ay))
		return 0;

	if (!(bits == 16 || bits == 8)) {
		ayemu_err = "Incorrect bits value";
		return 0;
	}
	else if (!(chans == 1 || chans == 2)) {
		ayemu_err = "Incorrect number of channels";
		return 0;
	}
	else if (freq < 50) {
		ayemu_err = "Incorrect output sound freq";
		return 0;
	}
	else {
		ay->sndfmt.freq = freq;
		ay->sndfmt.channels = chans;
		ay->sndfmt.bpc = bits;
	}

	ay->default_sound_format_flag = 0;
	ay->dirty = 1;
	return 1;
}


/*! Set amplitude factor for each of channels (A,B anc C, tone and noise).
* Factor's value must be from (-100) to 100.
* \arg ay - pointer to ayemu_t structure
* \arg stereo_type - type of stereo
* \arg custom_eq - NULL or pointer to custom table of mixer layout.
* \retval 1 if OK, 0 if error occures.
*/
int ayemu_set_stereo(ayemu_ay_t *ay, ayemu_stereo_t stereo_type, int *custom_eq)
{
	int i;
	int chip;

	if (!check_magic(ay))
		return 0;

	if (stereo_type != AYEMU_STEREO_CUSTOM && custom_eq != NULL) {
		ayemu_err = "Stereo type not custom, 'custom_eq' parametr must be NULL";
		return 0;
	}

	chip = (ay->type == AYEMU_AY) ? 0 : 1;

	switch (stereo_type) {
	case AYEMU_MONO:
	case AYEMU_ABC:
	case AYEMU_ACB:
	case AYEMU_BAC:
	case AYEMU_BCA:
	case AYEMU_CAB:
	case AYEMU_CBA:
		for (i = 0; i < 6; i++)
			ay->eq[i] = default_layout[chip][stereo_type][i];
		break;
	case AYEMU_STEREO_CUSTOM:
		for (i = 0; i < 6; i++)
			ay->eq[i] = custom_eq[i];
		break;
	default:
		ayemu_err = "Incorrect stereo type";
		return 0;
	}

	ay->default_stereo_flag = 0;
	ay->dirty = 1;
	return 1;
}


#define WARN_IF_REGISTER_GREAT_THAN(r,m) \
if (*(regs + r) > m) \
	 fprintf(stderr, "ayemu_set_regs: warning: possible bad register data- R%d > %d\n", r, m)


/** Assign values for AY registers.
*
* You must pass array of char [14] to this function
*/
void ayemu_set_regs(ayemu_ay_t *ay, unsigned char *regs)
{
	if (!check_magic(ay)) return;

//	WARN_IF_REGISTER_GREAT_THAN(1, 15);
//	WARN_IF_REGISTER_GREAT_THAN(3, 15);
//	WARN_IF_REGISTER_GREAT_THAN(5, 15);
//	WARN_IF_REGISTER_GREAT_THAN(8, 31);
//	WARN_IF_REGISTER_GREAT_THAN(9, 31);
//	WARN_IF_REGISTER_GREAT_THAN(10, 31);

	ay->regs.tone_a = regs[0] + ((regs[1] & 0x0f) << 8);
	ay->regs.tone_b = regs[2] + ((regs[3] & 0x0f) << 8);
	ay->regs.tone_c = regs[4] + ((regs[5] & 0x0f) << 8);

	ay->regs.noise = regs[6] & 0x1f;

	ay->regs.R7_tone_a = !(regs[7] & 0x01);
	ay->regs.R7_tone_b = !(regs[7] & 0x02);
	ay->regs.R7_tone_c = !(regs[7] & 0x04);

	ay->regs.R7_noise_a = !(regs[7] & 0x08);
	ay->regs.R7_noise_b = !(regs[7] & 0x10);
	ay->regs.R7_noise_c = !(regs[7] & 0x20);

	ay->regs.vol_a = regs[8] & 0x0f;
	ay->regs.vol_b = regs[9] & 0x0f;
	ay->regs.vol_c = regs[10] & 0x0f;
	ay->regs.env_a = regs[8] & 0x10;
	ay->regs.env_b = regs[9] & 0x10;
	ay->regs.env_c = regs[10] & 0x10;
	ay->regs.env_freq = regs[11] + (regs[12] << 8);

	if (regs[13] != 0xff) {                   /* R13 = 255 means continue curent envelop */
		ay->regs.env_style = regs[13] & 0x0f;
		ay->env_pos = ay->cnt_e = 0;
	}
}

/** Assign value for a single AY register.
*/
void ayemu_set_reg(ayemu_ay_t *ay, int reg, unsigned char value)
{
	if (!check_magic(ay)) return;

	switch (reg) {
	case 0:
		ay->regs.tone_a = (ay->regs.tone_a & 0x0f00) | value;
		break;
	case 1:
		ay->regs.tone_a = (ay->regs.tone_a & 0x00ff) | ((value & 0x0f) << 8);
		break;
	case 2:
		ay->regs.tone_b = (ay->regs.tone_b & 0x0f00) | value;
		break;
	case 3:
		ay->regs.tone_b = (ay->regs.tone_b & 0x00ff) | ((value & 0x0f) << 8);
		break;
	case 4:
		ay->regs.tone_c = (ay->regs.tone_c & 0x0f00) | value;
		break;
	case 5:
		ay->regs.tone_c = (ay->regs.tone_c & 0x00ff) | ((value & 0x0f) << 8);
		break;
	case 6:
		ay->regs.noise = value & 0x1f;
		break;
	case 7:
		ay->regs.R7_tone_a = !(value & 0x01);
		ay->regs.R7_tone_b = !(value & 0x02);
		ay->regs.R7_tone_c = !(value & 0x04);

		ay->regs.R7_noise_a = !(value & 0x08);
		ay->regs.R7_noise_b = !(value & 0x10);
		ay->regs.R7_noise_c = !(value & 0x20);

		break;
	case 8:
		ay->regs.vol_a = value & 0x0f;
		ay->regs.env_a = value & 0x10;
		break;
	case 9:
		ay->regs.vol_b = value & 0x0f;
		ay->regs.env_b = value & 0x10;
		break;
	case 10:
		ay->regs.vol_c = value & 0x0f;
		ay->regs.env_c = value & 0x10;
		break;
	case 11:
		ay->regs.env_freq = (ay->regs.env_freq & 0xff00) | value;
		break;
	case 12:
		ay->regs.env_freq = (ay->regs.env_freq & 0x00ff) | (value << 8);
		break;
	case 13:
		ay->regs.env_style = value & 0x0f;
		ay->env_pos = ay->cnt_e = 0;
		break;
	}
}


static void prepare_generation(ayemu_ay_t *ay)
{
	int vol, max_l, max_r;

	if (!ay->dirty) return;

	if (!bEnvGenInit) gen_env();

	if (ay->default_chip_flag) ayemu_set_chip_type(ay, AYEMU_AY, NULL);

	if (ay->default_stereo_flag) ayemu_set_stereo(ay, AYEMU_ABC, NULL);

	if (ay->default_sound_format_flag) ayemu_set_sound_format(ay, 44100, 2, 16);

	ay->ChipTacts_per_outcount = ay->ChipFreq / ay->sndfmt.freq / 8;

	{  /* GenVols */
		int n, m;
		int vol;
		for (n = 0; n < 32; n++) {
			vol = ay->table[n];
			for (m = 0; m < 6; m++)
				ay->vols[m][n] = (int)(((double)vol * ay->eq[m]) / 100);
		}
	}

	/* ???????????? ????????? ??????????? ???????????? ????????
	???????????????, ??? ? vols [x][31] ????? ????? ??????? ?????????
	TODO: ??????? ???????? ?? ??? ;-)
	*/
	max_l = ay->vols[0][31] + ay->vols[2][31] + ay->vols[3][31];
	max_r = ay->vols[1][31] + ay->vols[3][31] + ay->vols[5][31];
	vol = (max_l > max_r) ? max_l : max_r;  // =157283 on all defaults
	ay->Amp_Global = ay->ChipTacts_per_outcount *vol / AYEMU_MAX_AMP;

	ay->dirty = 0;
}


/*! Generate sound.
* Fill sound buffer with current register data
* Return value: pointer to next data in output sound buffer
* \retval \b 1 if OK, \b 0 if error occures.
*/
void *ayemu_gen_sound(ayemu_ay_t *ay, void *buff, size_t frame_count)
{
	int mix_l, mix_r;
	int tmpvol;
	int m;
	unsigned char *char_buf = (unsigned char *)buff;
	unsigned short *short_buf = (unsigned short *)buff;

	if (!check_magic(ay))
		return 0;

	prepare_generation(ay);

	while (frame_count-- > 0) {
		mix_l = mix_r = 0;

		for (m = 0; m < ay->ChipTacts_per_outcount; m++) {
			if (++ay->cnt_a >= ay->regs.tone_a) {
				ay->cnt_a = 0;
				ay->bit_a = !ay->bit_a;
			}
			if (++ay->cnt_b >= ay->regs.tone_b) {
				ay->cnt_b = 0;
				ay->bit_b = !ay->bit_b;
			}
			if (++ay->cnt_c >= ay->regs.tone_c) {
				ay->cnt_c = 0;
				ay->bit_c = !ay->bit_c;
			}

			/* GenNoise (c) Hacker KAY & Sergey Bulba */
			if (++ay->cnt_n >= (ay->regs.noise * 2)) {
				ay->cnt_n = 0;
				ay->Cur_Seed = (ay->Cur_Seed * 2 + 1) ^ \
					(((ay->Cur_Seed >> 16) ^ (ay->Cur_Seed >> 13)) & 1);
				ay->bit_n = ((ay->Cur_Seed >> 16) & 1);
			}

			if (++ay->cnt_e >= ay->regs.env_freq) {
				ay->cnt_e = 0;
				if (++ay->env_pos > 127)
					ay->env_pos = 64;
			}

#define ENVVOL Envelope [ay->regs.env_style][ay->env_pos]

			if ((ay->bit_a | !ay->regs.R7_tone_a) & (ay->bit_n | !ay->regs.R7_noise_a)) {
				tmpvol = (ay->regs.env_a) ? ENVVOL : ay->regs.vol_a * 2 + 1;
				mix_l += ay->vols[0][tmpvol];
				mix_r += ay->vols[1][tmpvol];
			}

			if ((ay->bit_b | !ay->regs.R7_tone_b) & (ay->bit_n | !ay->regs.R7_noise_b)) {
				tmpvol = (ay->regs.env_b) ? ENVVOL : ay->regs.vol_b * 2 + 1;
				mix_l += ay->vols[2][tmpvol];
				mix_r += ay->vols[3][tmpvol];
			}

			if ((ay->bit_c | !ay->regs.R7_tone_c) & (ay->bit_n | !ay->regs.R7_noise_c)) {
				tmpvol = (ay->regs.env_c) ? ENVVOL : ay->regs.vol_c * 2 + 1;
				mix_l += ay->vols[4][tmpvol];
				mix_r += ay->vols[5][tmpvol];
			}
		} /* end for (m=0; ...) */

		mix_l /= ay->Amp_Global;
		mix_r /= ay->Amp_Global;

		if (ay->sndfmt.bpc == 8) {
			mix_l = (mix_l >> 8) | 128; /* 8 bit sound */
			mix_r = (mix_r >> 8) | 128;
			*char_buf++ = mix_l;
			if (ay->sndfmt.channels != 1)
				*char_buf++ = mix_r;
		}
		else {
			*short_buf++ = mix_l; /* 16 bit sound */
			if (ay->sndfmt.channels != 1) {
				*short_buf++ = mix_r;
			}
		}
	}
	return (ay->sndfmt.bpc == 8 ? (void *)char_buf : (void *)short_buf);
}

/** Free all data allocated by emulator
*
* For now it do nothing.
*/
void ayemu_free(ayemu_ay_t *ay)
{
	/* nothing to do here */
	return;
}