aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakamichi Horikawa <takamichiho@gmail.com>2017-07-11 22:19:49 +0900
committerTakamichi Horikawa <takamichiho@gmail.com>2017-07-11 22:19:49 +0900
commit5fac17c7f0531236c32160a72147a9bf7dbec434 (patch)
treef52361f9c07969848f05cdddc01420b562faaf54
parent5eeba166f080de01ab95e4365bfd36ab8454ab04 (diff)
libopna: partially bit perfect with actual opna chip
-rw-r--r--libopna/opnafm.c253
-rw-r--r--libopna/opnafm.h15
-rw-r--r--libopna/opnatables.h13
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},