text: #pragma once
  
  #include <stddef.h>
  
  typedef unsigned char ayemu_ay_reg_frame_t[14];
  
  
  /** Types of stereo.
      The codes of stereo types used for generage sound. */
  typedef enum
  {
    AYEMU_MONO = 0,
    AYEMU_ABC,
    AYEMU_ACB,
    AYEMU_BAC,
    AYEMU_BCA,
    AYEMU_CAB,
    AYEMU_CBA,
    AYEMU_STEREO_CUSTOM = 255
  } ayemu_stereo_t;
  
  /** Sound chip type.
      Constant for identify used chip for emulation */
  typedef enum {
    AYEMU_AY,			/**< default AY chip (lion17 for now) */
    AYEMU_YM,			/**< default YM chip (lion17 for now) */
    AYEMU_AY_LION17,		/**< emulate AY with Lion17 table */
    AYEMU_YM_LION17,		/**< emulate YM with Lion17 table */
    AYEMU_AY_KAY,			/**< emulate AY with HACKER KAY table */
    AYEMU_YM_KAY,			/**< emulate YM with HACKER KAY table */
    AYEMU_AY_LOG,			/**< emulate AY with logariphmic table */
    AYEMU_YM_LOG,			/**< emulate YM with logariphmic table */
    AYEMU_AY_CUSTOM,		/**< use AY with custom table. */
    AYEMU_YM_CUSTOM		/**< use YM with custom table. */
  } ayemu_chip_t;
  
  /** parsed by #ayemu_set_regs() AY registers data \internal */
  typedef struct
  {
    int tone_a;           /**< R0, R1 */
    int tone_b;		/**< R2, R3 */	
    int tone_c;		/**< R4, R5 */
    int noise;		/**< R6 */
    int R7_tone_a;	/**< R7 bit 0 */
    int R7_tone_b;	/**< R7 bit 1 */
    int R7_tone_c;	/**< R7 bit 2 */
    int R7_noise_a;	/**< R7 bit 3 */
    int R7_noise_b;	/**< R7 bit 4 */
    int R7_noise_c;	/**< R7 bit 5 */
    int vol_a;		/**< R8 bits 3-0 */
    int vol_b;		/**< R9 bits 3-0 */
    int vol_c;		/**< R10 bits 3-0 */
    int env_a;		/**< R8 bit 4 */
    int env_b;		/**< R9 bit 4 */
    int env_c;		/**< R10 bit 4 */
    int env_freq;		/**< R11, R12 */
    int env_style;	/**< R13 */
  }
  ayemu_regdata_t;
  
  
  /** Output sound format \internal */
  typedef struct
  {
    int freq;			/**< sound freq */
    int channels;			/**< channels (1-mono, 2-stereo) */
    int bpc;			/**< bits (8 or 16) */
  }
  ayemu_sndfmt_t;
  
  
  /** Data structure for sound chip emulation \internal
   * 
   */
  typedef struct
  {
    /* emulator settings */
    int table[32];		/**< table of volumes for chip */
    ayemu_chip_t type;		/**< general chip type (\b AYEMU_AY or \b AYEMU_YM) */
    int ChipFreq;			/**< chip emulator frequency */
    int eq[6];			/**< volumes for channels.
  				   Array contains 6 elements: 
  				   A left, A right, B left, B right, C left and C right;
  				   range -100...100 */
    ayemu_regdata_t regs;		/**< parsed registers data */
    ayemu_sndfmt_t sndfmt;	/**< output sound format */
  
    /* flags */
    int magic;			/**< structure initialized flag */
    int default_chip_flag;	/**< =1 after init, resets in #ayemu_set_chip_type() */
    int default_stereo_flag;	/**< =1 after init, resets in #ayemu_set_stereo() */
    int default_sound_format_flag; /**< =1 after init, resets in #ayemu_set_sound_format() */
    int dirty;			/**< dirty flag. Sets if any emulator properties changed */
    int verbose;			/**< output warnings to stderr it not null */
  
    int bit_a;			/**< state of channel A generator */
    int bit_b;			/**< state of channel B generator */
    int bit_c;			/**< state of channel C generator */
    int bit_n;			/**< current generator state */
    int cnt_a;			/**< back counter of A */
    int cnt_b;			/**< back counter of B */
    int cnt_c;			/**< back counter of C */
    int cnt_n;			/**< back counter of noise generator */
    int cnt_e;			/**< back counter of envelop generator */
    int ChipTacts_per_outcount;   /**< chip's counts per one sound signal count */
    int Amp_Global;		/**< scale factor for amplitude */
    int vols[6][32];              /**< stereo type (channel volumes) and chip table.
  				   This cache calculated by #table and #eq  */
    int EnvNum;		        /**< number of current envilopment (0...15) */
    int env_pos;			/**< current position in envelop (0...127) */
    int Cur_Seed;		        /**< random numbers counter */
  }
  ayemu_ay_t;
  
  
  static void ayemu_init(ayemu_ay_t *ay);
  
  static void ayemu_reset(ayemu_ay_t *ay);
  
  static int ayemu_set_chip_type(ayemu_ay_t *ay, ayemu_chip_t chip, int *custom_table);
  
  static void ayemu_set_chip_freq(ayemu_ay_t *ay, int chipfreq);
  
  static int ayemu_set_stereo(ayemu_ay_t *ay, ayemu_stereo_t stereo, int *custom_eq);
  
  static int ayemu_set_sound_format (ayemu_ay_t *ay, int freq, int chans, int bits);
  
  static void ayemu_set_regs (ayemu_ay_t *ay, ayemu_ay_reg_frame_t regs);
  
  static void* ayemu_gen_sound (ayemu_ay_t *ay, void *buf, size_t bufsize);
  
  static char *ayemu_err;
  static const int MAGIC1 = 0xcdef;	/* for check ayemu_t structure inited */
  
  enum {
    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 to uninitialized ayemu_ay_t structure\n");
    return 0;
  }
  
  
  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.
   *
  */
  static 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->verbose = 0;
    ay->magic = MAGIC1;
  
    ayemu_reset(ay);
  }
  
  /** Reset AY/YM chip.
   *
   * \arg \c ay - pointer to ayemu_ay_t structure.
   * \return none.
   */
  static 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. */
  static 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 = (char *)"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;
    case AYEMU_AY_LOG:
    case AYEMU_YM_LOG:
    default:
      ayemu_err = (char *)"Incorrect chip type";
      return 0;
    }
  
    ay->default_chip_flag = 0;
    ay->dirty = 1;
    return 1;
  }
  
  
  /** Set chip frequency. */
  static 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
   */
  static 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 = (char *)"Incorrect bits value";
      return 0;
    }
    else if (!(chans == 1 || chans == 2)) {
      ayemu_err = (char *)"Incorrect number of channels";
      return 0;
    }
    else if (freq < 50) {
      ayemu_err = (char *)"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.
   */
  static 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 = (char *)"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 = (char *)"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
   */
  static void ayemu_set_regs(ayemu_ay_t *ay, ayemu_ay_reg_frame_t regs)
  {
    if (!check_magic(ay)) return;
  
    if (ay->verbose) {
      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;
  
    {  
      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);
      }
    }
  
    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.
   */
  static 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;
  }
  
  