summaryrefslogtreecommitdiff
path: root/plugins/vtx/ay8912.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/vtx/ay8912.c')
-rw-r--r--plugins/vtx/ay8912.c494
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;
+}