view src/vtx/ay8912.c @ 2284:d19b53359b24

cleaned up the sndfile wav plugin, currently limiting it ONLY TO WAV PLAYBACK. if somebody is more experienced with it and wants to restore the other formats, go ahead (maybe change the name of the plugin too?).
author mf0102 <0102@gmx.at>
date Wed, 09 Jan 2008 15:41:22 +0100
parents 77dbd83ea6e4
children
line wrap: on
line source

/* AY/YM emulator implementation. */

#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", 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;
}