diff options
author | Takamichi Horikawa <takamichiho@gmail.com> | 2017-07-11 22:19:49 +0900 |
---|---|---|
committer | Takamichi Horikawa <takamichiho@gmail.com> | 2017-07-11 22:19:49 +0900 |
commit | 5fac17c7f0531236c32160a72147a9bf7dbec434 (patch) | |
tree | f52361f9c07969848f05cdddc01420b562faaf54 | |
parent | 5eeba166f080de01ab95e4365bfd36ab8454ab04 (diff) |
libopna: partially bit perfect with actual opna chip
-rw-r--r-- | libopna/opnafm.c | 253 | ||||
-rw-r--r-- | libopna/opnafm.h | 15 | ||||
-rw-r--r-- | libopna/opnatables.h | 13 |
3 files changed, 179 insertions, 102 deletions
diff --git a/libopna/opnafm.c b/libopna/opnafm.c index abc36fa..4e86b80 100644 --- a/libopna/opnafm.c +++ b/libopna/opnafm.c @@ -3,8 +3,11 @@ #include "opnatables.h" -#define LIBOPNA_ENABLE_HIRES_SIN -#define LIBOPNA_ENABLE_HIRES_ENV +//#include <stdio.h> +#define printf(...) + +//#define LIBOPNA_ENABLE_HIRES_SIN +//#define LIBOPNA_ENABLE_HIRES_ENV enum { ENV_MAX_HIRES = LIBOPNA_FM_ENV_MAX * 4 @@ -17,24 +20,9 @@ enum { }; static void opna_fm_slot_reset(struct opna_fm_slot *slot) { - slot->phase = 0; slot->env = LIBOPNA_FM_ENV_MAX; slot->env_hires = ENV_MAX_HIRES; - slot->env_count = 0; slot->env_state = ENV_RELEASE; - slot->rate_shifter = 0; - slot->rate_selector = 0; - slot->rate_mul = 0; - slot->tl = 0; - slot->sl = 0; - slot->ar = 0; - slot->dr = 0; - slot->sr = 0; - slot->rr = 0; - slot->mul = 0; - slot->det = 0; - slot->ks = 0; - slot->keyon = false; } @@ -42,18 +30,10 @@ void opna_fm_chan_reset(struct opna_fm_channel *chan) { for (int i = 0; i < 4; i++) { opna_fm_slot_reset(&chan->slot[i]); } - - chan->fbmem1 = 0; - chan->fbmem2 = 0; - chan->alg_mem = 0; - - chan->alg = 0; - chan->fb = 0; - chan->fnum = 0; - chan->blk = 0; } void opna_fm_reset(struct opna_fm *fm) { + *fm = (struct opna_fm) {0}; for (int i = 0; i < 6; i++) { opna_fm_chan_reset(&fm->channel[i]); fm->lselect[i] = true; @@ -90,11 +70,13 @@ static int16_t opna_fm_slotout(struct opna_fm_slot *slot, int16_t modulation) { int logout = logsintable[pind]; #endif // LIBOPNA_ENABLE_HIRES_SIN +// if (slot->env == LIBOPNA_FM_ENV_MAX) { #ifdef LIBOPNA_ENABLE_HIRES_ENV logout += slot->env_hires; #else logout += (slot->env << 2); #endif +// } logout += (slot->tl << 5); int selector = logout & ((1<<EXPTABLEBIT)-1); @@ -103,6 +85,7 @@ static int16_t opna_fm_slotout(struct opna_fm_slot *slot, int16_t modulation) { int16_t out = (exptable[selector] << 2) >> shifter; if (minus) out = -out; + slot->prevout = out; return out; } @@ -142,73 +125,104 @@ void opna_fm_chan_phase(struct opna_fm_channel *chan) { static void opna_fm_chan_phase_se(struct opna_fm_channel *chan, struct opna_fm *fm) { unsigned freq; - freq = blkfnum2freq(fm->ch3.blk[2], fm->ch3.fnum[1]); - opna_fm_slot_phase(&chan->slot[0], freq); freq = blkfnum2freq(fm->ch3.blk[0], fm->ch3.fnum[0]); - opna_fm_slot_phase(&chan->slot[2], freq); - freq = blkfnum2freq(fm->ch3.blk[1], fm->ch3.fnum[2]); + opna_fm_slot_phase(&chan->slot[0], freq); + freq = blkfnum2freq(fm->ch3.blk[1], fm->ch3.fnum[1]); opna_fm_slot_phase(&chan->slot[1], freq); + freq = blkfnum2freq(fm->ch3.blk[2], fm->ch3.fnum[2]); + opna_fm_slot_phase(&chan->slot[2], freq); freq = blkfnum2freq(chan->blk, chan->fnum); opna_fm_slot_phase(&chan->slot[3], freq); } -int16_t opna_fm_chanout(struct opna_fm_channel *chan) { - int16_t fb = chan->fbmem1 + chan->fbmem2; - int16_t slot0 = chan->fbmem1; - chan->fbmem1 = chan->fbmem2; +struct opna_fm_frame opna_fm_chanout(struct opna_fm_channel *chan) { + int16_t slot0 = chan->slot[0].prevout; + int16_t slot1 = chan->slot[1].prevout; + int16_t slot2 = chan->slot[2].prevout; + int16_t slot3 = chan->slot[3].prevout; + int16_t fb = chan->fbmem + chan->slot[0].prevout; + chan->fbmem = slot0; if (!chan->fb) fb = 0; - chan->fbmem2 = opna_fm_slotout(&chan->slot[0], fb >> (9 - chan->fb)); - - int16_t slot2; - int16_t out = 0; + opna_fm_slotout(&chan->slot[0], fb >> (9 - chan->fb)); + int16_t prev_alg_mem = chan->alg_mem; + struct opna_fm_frame ret; switch (chan->alg) { + // this looks ugly, but is verified with actual YMF288 and YM2608 case 0: - slot2 = opna_fm_slotout(&chan->slot[2], chan->alg_mem); - chan->alg_mem = opna_fm_slotout(&chan->slot[1], slot0); - out = opna_fm_slotout(&chan->slot[3], slot2); + opna_fm_slotout(&chan->slot[1], chan->slot[0].prevout); + opna_fm_slotout(&chan->slot[2], slot1); + opna_fm_slotout(&chan->slot[3], slot2); + ret.data[0] = ret.data[1] = chan->slot[3].prevout >> 1; break; case 1: - slot2 = opna_fm_slotout(&chan->slot[2], chan->alg_mem); - chan->alg_mem = slot0; - chan->alg_mem += opna_fm_slotout(&chan->slot[1], 0); - out = opna_fm_slotout(&chan->slot[3], slot2); + opna_fm_slotout(&chan->slot[1], 0); + opna_fm_slotout(&chan->slot[2], prev_alg_mem); + opna_fm_slotout(&chan->slot[3], slot2); + chan->alg_mem = chan->slot[0].prevout; + chan->alg_mem += chan->slot[1].prevout; + chan->alg_mem &= ~1; + ret.data[0] = ret.data[1] = chan->slot[3].prevout >> 1; break; case 2: - slot2 = opna_fm_slotout(&chan->slot[2], chan->alg_mem); - chan->alg_mem = opna_fm_slotout(&chan->slot[1], 0); - out = opna_fm_slotout(&chan->slot[3], slot0 + slot2); + opna_fm_slotout(&chan->slot[1], 0); + opna_fm_slotout(&chan->slot[2], slot1); + opna_fm_slotout(&chan->slot[3], slot0 + slot2); + ret.data[0] = ret.data[1] = chan->slot[3].prevout >> 1; break; case 3: - slot2 = opna_fm_slotout(&chan->slot[2], 0); - out = opna_fm_slotout(&chan->slot[3], slot2 + chan->alg_mem); - chan->alg_mem = opna_fm_slotout(&chan->slot[1], slot0); + opna_fm_slotout(&chan->slot[1], chan->slot[0].prevout); + opna_fm_slotout(&chan->slot[2], 0); + opna_fm_slotout(&chan->slot[3], slot2 + prev_alg_mem); + chan->alg_mem = slot1; + ret.data[0] = ret.data[1] = chan->slot[3].prevout >> 1; break; case 4: - out = opna_fm_slotout(&chan->slot[1], slot0); - slot2 = opna_fm_slotout(&chan->slot[2], 0); - out += opna_fm_slotout(&chan->slot[3], slot2); + opna_fm_slotout(&chan->slot[1], slot0); + opna_fm_slotout(&chan->slot[2], 0); + opna_fm_slotout(&chan->slot[3], chan->slot[2].prevout); + ret.data[0] = ret.data[1] = slot3 >> 1; + ret.data[0] += chan->slot[1].prevout >> 1; + ret.data[1] += slot1 >> 1; break; case 5: - out = opna_fm_slotout(&chan->slot[2], chan->alg_mem); - chan->alg_mem = slot0; - out += opna_fm_slotout(&chan->slot[1], slot0); - out += opna_fm_slotout(&chan->slot[3], slot0); + opna_fm_slotout(&chan->slot[1], slot0); + opna_fm_slotout(&chan->slot[2], slot0); + opna_fm_slotout(&chan->slot[3], slot0); + chan->alg_mem = slot2; + chan->alg_mem &= ~1; + ret.data[0] = ret.data[1] = slot3 >> 1; + ret.data[0] += (chan->slot[1].prevout >> 1) + (slot2 >> 1); + ret.data[1] += (slot1 >> 1) + (prev_alg_mem >> 1); break; case 6: - out = opna_fm_slotout(&chan->slot[1], slot0); - out += opna_fm_slotout(&chan->slot[2], 0); - out += opna_fm_slotout(&chan->slot[3], 0); + opna_fm_slotout(&chan->slot[1], slot0); + opna_fm_slotout(&chan->slot[2], 0); + opna_fm_slotout(&chan->slot[3], 0); + chan->alg_mem = slot2; + chan->alg_mem &= ~1; + ret.data[0] = ret.data[1] = slot3 >> 1; + ret.data[0] += (chan->slot[1].prevout >> 1) + (slot2 >> 1); + ret.data[1] += (slot1 >> 1) + (prev_alg_mem >> 1); break; case 7: - out = slot0; - out += opna_fm_slotout(&chan->slot[1], 0); - out += opna_fm_slotout(&chan->slot[2], 0); - out += opna_fm_slotout(&chan->slot[3], 0); + opna_fm_slotout(&chan->slot[1], 0); + opna_fm_slotout(&chan->slot[2], 0); + opna_fm_slotout(&chan->slot[3], 0); + chan->alg_mem = chan->slot[1].prevout + chan->slot[2].prevout; + chan->alg_mem &= ~1; + ret.data[0] = ret.data[1] = + (chan->slot[0].prevout >> 1) + (chan->slot[3].prevout >> 1); + ret.data[0] += chan->alg_mem >> 1; + ret.data[1] += prev_alg_mem >> 1; + // when int = 32bit, this is implementation-defined, not UB + ret.data[0] <<= 1; + ret.data[0] >>= 1; + ret.data[1] <<= 1; + ret.data[1] >>= 1; break; } - - return out; + return ret; } static void opna_fm_slot_setrate(struct opna_fm_slot *slot, int status) { @@ -240,6 +254,8 @@ static void opna_fm_slot_setrate(struct opna_fm_slot *slot, int status) { int rate = 2*r + (slot->keycode >> (3 - slot->ks)); if (rate > 63) rate = 63; + printf("rate: %d\n", rate); + if (status == ENV_ATTACK && rate >= 62) rate += 4; #ifdef LIBOPNA_ENABLE_HIRES_ENV rate += 8; #endif @@ -253,11 +269,15 @@ static void opna_fm_slot_setrate(struct opna_fm_slot *slot, int status) { slot->rate_mul = 1; slot->rate_shifter = rate_shifter; } + printf("status: %d\n", status); + printf("rate_selector: %d\n", slot->rate_selector); + printf("rate_mul: %d\n", slot->rate_mul); + printf("rate_shifter: %d\n\n", slot->rate_shifter); } static void opna_fm_slot_env(struct opna_fm_slot *slot) { - slot->env_count++; - if (!(slot->env_count & ((1<<slot->rate_shifter)-1))) { +// if (!(slot->env_count & ((1<<slot->rate_shifter)-1))) { + if ((slot->env_count & ((1<<slot->rate_shifter)-1)) == ((1<<slot->rate_shifter)-1)) { int rate_index = (slot->env_count >> slot->rate_shifter) & 7; int env_inc = rateinctable[slot->rate_selector][rate_index]; env_inc *= slot->rate_mul; @@ -303,15 +323,6 @@ static void opna_fm_slot_env(struct opna_fm_slot *slot) { int newenv; int sl; case ENV_ATTACK: - newenv = slot->env_hires + (((-slot->env-1) * env_inc) >> 6); - if (newenv <= 0) { - slot->env = 0; - slot->env_hires = 0; - slot->env_state = ENV_DECAY; - opna_fm_slot_setrate(slot, ENV_DECAY); - } else { - slot->env_hires = newenv; - } newenv = slot->env + (((-slot->env-1) * env_inc) >> 4); if (newenv <= 0) { slot->env = 0; @@ -344,16 +355,19 @@ static void opna_fm_slot_env(struct opna_fm_slot *slot) { } #endif } + slot->env_count++; } void opna_fm_slot_key(struct opna_fm_channel *chan, int slotnum, bool keyon) { struct opna_fm_slot *slot = &chan->slot[slotnum]; + //printf("%d: %d\n", slotnum, keyon); if (keyon) { if (!slot->keyon) { slot->keyon = true; slot->env_state = ENV_ATTACK; slot->env_count = 0; slot->phase = 0; + slot->prevout = 0; opna_fm_slot_setrate(slot, ENV_ATTACK); } } else { @@ -456,16 +470,34 @@ void opna_fm_writereg(struct opna_fm *fm, unsigned reg, unsigned val) { // printf("0x27\n"); // printf(" mode = %d\n", mode); fm->ch3.mode = mode; + for (int c = 0; c < 2; c++) { + unsigned blk, fnum; + if (fm->ch3.mode == CH3_MODE_NORMAL) { + blk = fm->channel[2].blk; + fnum = fm->channel[2].fnum; + } else { + blk = fm->ch3.blk[c]; + fnum = fm->ch3.fnum[c]; + } + fm->channel[2].slot[c].keycode = blkfnum2keycode(blk, fnum); + opna_fm_slot_setrate(&fm->channel[2].slot[c], + fm->channel[2].slot[c].env_state); + } } } return; case 0x28: { +// printf("%02x\n", val); int c = val & 0x3; if (c == 3) return; if (val & 0x4) c += 3; for (int i = 0; i < 4; i++) { - opna_fm_slot_key(&fm->channel[c], i, (val & (1<<(4+i)))); + bool keyon = val & (1<<(4+i)); + fm->channel[c].slot[i].keyon_ext = keyon; + if (!keyon) { + opna_fm_slot_key(&fm->channel[c], i, false); + } } } return; @@ -505,12 +537,26 @@ void opna_fm_writereg(struct opna_fm *fm, unsigned reg, unsigned val) { unsigned fnum = ((fm->blkfnum_h & 0x7) << 8) | (val & 0xff); switch (reg & 0xc) { case 0x0: - opna_fm_chan_set_blkfnum(chan, blk, fnum); + if (c != 2 || fm->ch3.mode == CH3_MODE_NORMAL) { +// printf("fnum: ch%d, mode: %d\n", c, fm->ch3.mode); + opna_fm_chan_set_blkfnum(chan, blk, fnum); + } else { +// printf("fnum: ch2, slot3\n"); + chan->blk = blk; + chan->fnum = fnum; + chan->slot[3].keycode = blkfnum2keycode(blk, fnum); + opna_fm_slot_setrate(&chan->slot[3], chan->slot[3].env_state); + } break; case 0x8: - c %= 3; + c = (c + 2) % 3; fm->ch3.blk[c] = blk; fm->ch3.fnum[c] = fnum; + if (fm->ch3.mode != CH3_MODE_NORMAL) { + fm->channel[2].slot[c].keycode = blkfnum2keycode(blk, fnum); + opna_fm_slot_setrate(&fm->channel[2].slot[c], + fm->channel[2].slot[c].env_state); + } break; case 0x4: case 0xc: @@ -534,12 +580,6 @@ void opna_fm_writereg(struct opna_fm *fm, unsigned reg, unsigned val) { } } -void opna_fm_chan_env(struct opna_fm_channel *chan) { - for (int i = 0; i < 4; i++) { - opna_fm_slot_env(&chan->slot[i]); - } -} - static int gcd(int a, int b) { if (a < b) { int t = a; @@ -583,28 +623,32 @@ void opna_fm_mix(struct opna_fm *fm, int16_t *buf, unsigned samples, for (unsigned i = 0; i < samples; i++) { if (!fm->env_div3) { for (int c = 0; c < 6; c++) { - opna_fm_chan_env(&fm->channel[c]); + for (int s = 0; s < 4; s++) { + if (fm->channel[c].slot[s].keyon_ext) { + opna_fm_slot_key(&fm->channel[c], s, true); + opna_fm_slot_env(&fm->channel[c].slot[s]); + } + //opna_fm_slot_env(&fm->channel[c].slot[s]); + } } - fm->env_div3 = 3; + //printf("e %04d\n", fm->channel[0].slot[3].env); } - fm->env_div3--; int32_t lo = buf[i*2+0]; int32_t ro = buf[i*2+1]; for (int c = 0; c < 6; c++) { - int16_t o = opna_fm_chanout(&fm->channel[c]); - if (oscillo) oscillo[c].buf[offset+i] = o*2; + struct opna_fm_frame o = opna_fm_chanout(&fm->channel[c]); + if (oscillo) oscillo[c].buf[offset+i] = o.data[0] + o.data[1]; // TODO: CSM if (c == 2 && fm->ch3.mode != CH3_MODE_NORMAL) { opna_fm_chan_phase_se(&fm->channel[c], fm); } else { opna_fm_chan_phase(&fm->channel[c]); } - o >>= 1; if (fm->mask & (1<<c)) continue; - if (fm->lselect[c]) lo += o; - if (fm->rselect[c]) ro += o; + if (fm->lselect[c]) lo += o.data[1]; + if (fm->rselect[c]) ro += o.data[0]; } if (lo < INT16_MIN) lo = INT16_MIN; @@ -613,5 +657,24 @@ void opna_fm_mix(struct opna_fm *fm, int16_t *buf, unsigned samples, if (ro > INT16_MAX) ro = INT16_MAX; buf[i*2+0] = lo; buf[i*2+1] = ro; + if (lo == 1 || lo == 3 || lo == 5) { + //if (fm->channel[0].slot[3].env == 0511) { + //printf("l:%6d %4d %4d\n", lo, fm->channel[0].slot[3].phase >> 10, fm->channel[0].slot[3].env); + //} + } + if (!fm->env_div3) { + for (int c = 0; c < 6; c++) { + for (int s = 0; s < 4; s++) { + if (fm->channel[c].slot[s].keyon_ext) { + fm->channel[c].slot[s].keyon_ext = false; + } else { + opna_fm_slot_env(&fm->channel[c].slot[s]); + } + //opna_fm_slot_env(&fm->channel[c].slot[s]); + } + } + fm->env_div3 = 3; + } + fm->env_div3--; } } diff --git a/libopna/opnafm.h b/libopna/opnafm.h index b62556e..9da143f 100644 --- a/libopna/opnafm.h +++ b/libopna/opnafm.h @@ -44,15 +44,19 @@ struct opna_fm_slot { uint8_t keycode; + // set with opna_write + bool keyon_ext; + // synchronized with env update (once per 3 samples) bool keyon; + // set when opna_fm_slotout called + int16_t prevout; }; struct opna_fm_channel { struct opna_fm_slot slot[4]; // save 2 samples for slot 1 feedback - uint16_t fbmem1; - uint16_t fbmem2; + uint16_t fbmem; // save sample for long (>2) chain of slots uint16_t alg_mem; @@ -97,7 +101,12 @@ void opna_fm_chan_reset(struct opna_fm_channel *chan); void opna_fm_chan_phase(struct opna_fm_channel *chan); void opna_fm_chan_env(struct opna_fm_channel *chan); void opna_fm_chan_set_blkfnum(struct opna_fm_channel *chan, unsigned blk, unsigned fnum); -int16_t opna_fm_chanout(struct opna_fm_channel *chan); + +struct opna_fm_frame { + int16_t data[2]; +}; + +struct opna_fm_frame opna_fm_chanout(struct opna_fm_channel *chan); void opna_fm_slot_key(struct opna_fm_channel *chan, int slotnum, bool keyon); void opna_fm_chan_set_alg(struct opna_fm_channel *chan, unsigned alg); diff --git a/libopna/opnatables.h b/libopna/opnatables.h index 30140bf..825e390 100644 --- a/libopna/opnatables.h +++ b/libopna/opnatables.h @@ -208,10 +208,15 @@ static const uint16_t exptable[EXPTABLELEN] = { static const uint8_t rateinctable[4*2][8] = { // rates 0 - 47 - {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}, +// TODO: not bit perfect +// {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}, + {1, 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}, // rates 48 - {1, 1, 1, 1, 1, 1, 1, 1}, |