/* AY/YM emulator implementation. */

#include <stdio.h>
#include <inttypes.h>
#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", (void *) 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;
}


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, int32_t 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;
  }
}


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 sound_bufsize)
{
  int mix_l, mix_r;
  int tmpvol;
  int m;
  int snd_numcount;
  unsigned char *sound_buf = buff;

  if (!check_magic(ay))
    return 0;

  prepare_generation(ay);

  snd_numcount = sound_bufsize / (ay->sndfmt.channels * (ay->sndfmt.bpc >> 3));
  while (snd_numcount-- > 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;
      *sound_buf++ = mix_l;
      if (ay->sndfmt.channels != 1)
	*sound_buf++ = mix_r;
    } else {
      *sound_buf++ = mix_l & 0x00FF; /* 16 bit sound */
      *sound_buf++ = (mix_l >> 8);
      if (ay->sndfmt.channels != 1) {
	*sound_buf++ = mix_r & 0x00FF;
	*sound_buf++ = (mix_r >> 8);
      }
    }
  }
  return sound_buf;
}

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