diff options
author | Alexey Yakovenko <wakeroid@gmail.com> | 2009-12-18 23:54:30 +0100 |
---|---|---|
committer | Alexey Yakovenko <wakeroid@gmail.com> | 2009-12-18 23:54:30 +0100 |
commit | 8674104981f0fc91ce504d8702572b8cd8b1fc25 (patch) | |
tree | d1e9ced526c40f51b2db20c0d96e58a3e531f65e /plugins/vtx/ay8912.c | |
parent | 01740cc3f9dcfc8e95aae03003757e2b085ef70f (diff) |
vxt plugin WIP
Diffstat (limited to 'plugins/vtx/ay8912.c')
-rw-r--r-- | plugins/vtx/ay8912.c | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/plugins/vtx/ay8912.c b/plugins/vtx/ay8912.c new file mode 100644 index 00000000..bf79f95c --- /dev/null +++ b/plugins/vtx/ay8912.c @@ -0,0 +1,494 @@ +/* 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; +} + + +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, ayemu_ay_reg_frame_t 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; +} |