Mercurial > repos > blastem
diff ym_common.c @ 2558:3f58fec775df
Initial work on YMF262 (aka OPL3) emulation
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Sun, 19 Jan 2025 00:31:16 -0800 |
parents | |
children | eb588f22ec76 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ym_common.c Sun Jan 19 00:31:16 2025 -0800 @@ -0,0 +1,193 @@ +#include <math.h> +#include "ym_common.h" + +#ifdef __ANDROID__ +#define log2(x) (log(x)/log(2)) +#endif + +//According to Nemesis, real hardware only uses a 256 entry quarter sine table; however, +//memory is cheap so using a half sine table will probably save some cycles +//a full sine table would be nice, but negative numbers don't get along with log2 +#define SINE_TABLE_SIZE 512 +static uint16_t sine_table[SINE_TABLE_SIZE]; +//Similar deal here with the power table for log -> linear conversion +//According to Nemesis, real hardware only uses a 256 entry table for the fractional part +//and uses the whole part as a shift amount. +#define POW_TABLE_SIZE (1 << 13) +static uint16_t pow_table[POW_TABLE_SIZE]; + +static uint16_t rate_table_base[] = { + //main portion + 0,1,0,1,0,1,0,1, + 0,1,0,1,1,1,0,1, + 0,1,1,1,0,1,1,1, + 0,1,1,1,1,1,1,1, + //top end + 1,1,1,1,1,1,1,1, + 1,1,1,2,1,1,1,2, + 1,2,1,2,1,2,1,2, + 1,2,2,2,1,2,2,2, +}; + +uint16_t rate_table[64*8]; + +static uint8_t lfo_pm_base[][8] = { + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 4, 4, 4, 4}, + {0, 0, 0, 4, 4, 4, 8, 8}, + {0, 0, 4, 4, 8, 8, 0xc, 0xc}, + {0, 0, 4, 8, 8, 8, 0xc,0x10}, + {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, + {0, 0,0x10,0x18,0x20,0x20,0x28,0x30}, + {0, 0,0x20,0x30,0x40,0x40,0x50,0x60} +}; +int16_t lfo_pm_table[128 * 32 * 8]; + +static uint16_t round_fixed_point(double value, int dec_bits) +{ + return value * (1 << dec_bits) + 0.5; +} + +void ym_init_tables(void) +{ + static uint8_t did_tbl_init; + if (did_tbl_init) { + return; + } + did_tbl_init = 1; + //populate sine table + for (int32_t i = 0; i < 512; i++) { + double sine = sin( ((double)(i*2+1) / SINE_TABLE_SIZE) * M_PI_2 ); + + //table stores 4.8 fixed pointed representation of the base 2 log + sine_table[i] = round_fixed_point(-log2(sine), 8); + } + //populate power table + for (int32_t i = 0; i < POW_TABLE_SIZE; i++) { + double linear = pow(2, -((double)((i & 0xFF)+1) / 256.0)); + int32_t tmp = round_fixed_point(linear, 11); + int32_t shift = (i >> 8) - 2; + if (shift < 0) { + tmp <<= 0-shift; + } else { + tmp >>= shift; + } + pow_table[i] = tmp; + } + //populate envelope generator rate table, from small base table + for (int rate = 0; rate < 64; rate++) { + for (int cycle = 0; cycle < 8; cycle++) { + uint16_t value; + if (rate < 2) { + value = 0; + } else if (rate >= 60) { + value = 8; + } else if (rate < 8) { + value = rate_table_base[((rate & 6) == 6 ? 16 : 0) + cycle]; + } else if (rate < 48) { + value = rate_table_base[(rate & 0x3) * 8 + cycle]; + } else { + value = rate_table_base[32 + (rate & 0x3) * 8 + cycle] << ((rate - 48) >> 2); + } + rate_table[rate * 8 + cycle] = value; + } + } + //populate LFO PM table from small base table + //seems like there must be a better way to derive this + for (int freq = 0; freq < 128; freq++) { + for (int pms = 0; pms < 8; pms++) { + for (int step = 0; step < 32; step++) { + int16_t value = 0; + for (int bit = 0x40, shift = 0; bit > 0; bit >>= 1, shift++) { + if (freq & bit) { + value += lfo_pm_base[pms][(step & 0x8) ? 7-step & 7 : step & 7] >> shift; + } + } + if (step & 0x10) { + value = -value; + } + lfo_pm_table[freq * 256 + pms * 32 + step] = value; + } + } + } +} + +int16_t ym_sine(uint16_t phase, int16_t mod, uint16_t env) +{ + phase += mod; + if (env > MAX_ENVELOPE) { + env = MAX_ENVELOPE; + } + int16_t output = pow_table[sine_table[phase & 0x1FF] + env]; + if (phase & 0x200) { + output = -output; + } + return output; +} + +int16_t ym_opl_wave(uint16_t phase, int16_t mod, uint16_t env, uint8_t waveform) +{ + if (env > MAX_OPL_ENVELOPE) { + env = MAX_OPL_ENVELOPE; + } + + int16_t output; + switch (waveform) + { + default: + case 0: + output = pow_table[sine_table[phase & 0x1FF] + env]; + if (phase & 0x200) { + output = -output; + } + break; + case 1: + if (phase & 0x200) { + output = 0; + } else { + output = pow_table[sine_table[phase & 0x1FF] + env]; + } + break; + case 2: + output = pow_table[sine_table[phase & 0x1FF] + env]; + break; + case 3: + if (phase & 0x100) { + output = 0; + } else { + output = pow_table[sine_table[phase & 0xFF] + env]; + } + break; + case 4: + if (phase & 0x200) { + output = 0; + } else { + output = pow_table[sine_table[(phase & 0xFF) << 1] + env]; + if (phase & 0x100) { + output = -output; + } + } + break; + case 5: + if (phase & 0x200) { + output = 0; + } else { + output = pow_table[sine_table[(phase & 0xFF) << 1] + env]; + } + break; + case 6: + output = pow_table[env]; + if (phase & 0x200) { + output = -output; + } + break; + case 7: + if (phase & 0x200) { + output = -pow_table[((~phase) & 0x1FF) << 3 + env]; + } else { + output = pow_table[(phase & 0x1FF) << 3 + env]; + } + break; + } + return output; +}