ref: e8221d07d8c9932393f76758363327c07bdbc2dd
parent: d009b0013d90c1b3ecd0b3f35443f811ccee2791
author: aiju <[email protected]>
date: Sun Apr 12 10:55:25 EDT 2015
games/gb: improve sound emulation by modelling analog behaviour
--- a/sys/src/games/gb/apu.c
+++ b/sys/src/games/gb/apu.c
@@ -4,8 +4,10 @@
#include "dat.h"
#include "fns.h"
+double TAU = 25000;
+
Event evsamp;
-extern Event evenv, evwave;
+extern Event evenv;
s16int sbuf[2*4000], *sbufp;
enum {
Freq = 44100,
@@ -18,12 +20,11 @@
u16int sweepfreq;
typedef struct chan chan;
struct chan {
- u8int n, ectr;
- u16int len;
u8int *env, *freq;
- u16int fctr, fthr;
- u32int finc;
- u8int vol;
+ int per;
+ u16int len;
+ u8int n, ectr;
+ u8int vol, ctr, samp;
};
u8int wpos;
u16int lfsr;
@@ -30,6 +31,7 @@
u8int apustatus;
ulong waveclock;
u8int wavebuf;
+double samp[2];
chan sndch[4] = {
{
@@ -36,11 +38,13 @@
.n = 0,
.env = reg + NR12,
.freq = reg + NR14,
+ .per = 8 * 2048,
},
{
.n = 1,
.env = reg + NR22,
.freq = reg + NR24,
+ .per = 8 * 2048,
},
{
.n = 2,
@@ -49,99 +53,132 @@
.n = 3,
.env = reg + NR42,
.freq = reg + NR44,
+ .per = 32
}
};
+Event chev[4] = {
+ {.aux = &sndch[0]},
+ {.aux = &sndch[1]},
+ {.aux = &sndch[2]},
+ {.aux = &sndch[3]}
+};
Var apuvars[] = {
VAR(apustatus), VAR(envmod), VAR(sweepen), VAR(sweepcalc),
VAR(sweepctr), VAR(sweepfreq), VAR(wpos), VAR(lfsr), VAR(waveclock), VAR(wavebuf),
- VAR(sndch[0].ectr), VAR(sndch[0].len), VAR(sndch[0].fctr), VAR(sndch[0].fthr), VAR(sndch[0].finc), VAR(sndch[0].vol),
- VAR(sndch[1].ectr), VAR(sndch[1].len), VAR(sndch[1].fctr), VAR(sndch[1].fthr), VAR(sndch[1].finc), VAR(sndch[1].vol),
- VAR(sndch[2].ectr), VAR(sndch[2].len), VAR(sndch[2].fctr), VAR(sndch[2].fthr), VAR(sndch[2].finc), VAR(sndch[2].vol),
- VAR(sndch[3].ectr), VAR(sndch[3].len), VAR(sndch[3].fctr), VAR(sndch[3].fthr), VAR(sndch[3].finc), VAR(sndch[3].vol),
+ VAR(sndch[0].ectr), VAR(sndch[0].len), VAR(sndch[0].per), VAR(sndch[0].ctr), VAR(sndch[0].vol), VAR(sndch[0].samp),
+ VAR(sndch[1].ectr), VAR(sndch[1].len), VAR(sndch[1].per), VAR(sndch[1].ctr), VAR(sndch[1].vol), VAR(sndch[1].samp),
+ VAR(sndch[2].ectr), VAR(sndch[2].len), VAR(sndch[2].per), VAR(sndch[2].vol), VAR(sndch[2].samp),
+ VAR(sndch[3].ectr), VAR(sndch[3].len), VAR(sndch[3].per), VAR(sndch[3].vol), VAR(sndch[3].samp),
{nil, 0, 0},
};
-void
-rate(int i, u16int v)
+static void
+rate(chan *c, u16int v)
{
- switch(i){
+ switch(c->n){
case 0: case 1:
- sndch[i].finc = 131072ULL * 65536 / (Freq * (2048 - (v & 0x7ff)));
+ c->per = 8 * (2048 - (v & 0x7ff));
break;
case 2:
- sndch[2].finc = 4 * (2048 - (v & 0x7ff));
+ c->per = 4 * (2048 - (v & 0x7ff));
break;
case 3:
- sndch[3].finc = 524288ULL * 65536 / Freq;
+ c->per = 32;
if((v & 7) != 0)
- sndch[3].finc /= v & 7;
+ c->per *= v & 7;
else
- sndch[3].finc <<= 1;
- sndch[3].finc >>= (v >> 4 & 15) + 1;
+ c->per >>= 1;
+ c->per <<= (v >> 4 & 15);
}
}
-void
-env(chan *c)
+static void
+filter(int t)
{
- if((envmod & 1) == 0 && c->len > 0 && (*c->freq & 1<<6) != 0)
- if(--c->len == 0){
- apustatus &= ~(1<<c->n);
- c->vol = 0;
- return;
- }
- if((apustatus & 1<<c->n) == 0 || (envmod & 7) != 7 || c->ectr == 0 || --c->ectr != 0)
- return;
- c->ectr = *c->env & 7;
- if((*c->env & 1<<3) != 0){
- if(c->vol < 15)
- c->vol++;
- }else
- if(c->vol > 0)
- c->vol--;
-}
+ static int ov0, ov1;
+ static u32int oclock;
+ double e;
+ u8int cntl, cnth;
+ int i, v;
-void
-wavetick(void *)
-{
- addevent(&evwave, sndch[2].finc);
- wpos = wpos + 1 & 31;
- wavebuf = reg[WAVE + (wpos >> 1)];
- waveclock = clock;
+ e = exp((clock + t - oclock) * -(TAU / FREQ));
+ samp[0] = e * samp[0] + (1 - e) * ov0;
+ samp[1] = e * samp[1] + (1 - e) * ov1;
+ oclock = clock + t;
+ cntl = reg[NR50];
+ cnth = reg[NR51];
+ ov0 = 0;
+ ov1 = 0;
+ for(i = 0; i < 4; i++){
+ if(i == 2 ? ((reg[NR30] & 0x80) == 0) : ((*sndch[i].env & 0xf8) == 0))
+ continue;
+ v = sndch[i].samp * 2 - 15;
+ if((cnth & 1<<i) != 0)
+ ov0 += v;
+ if((cnth & 1<<4<<i) != 0)
+ ov1 += v;
+ }
+ ov0 *= 1 + (cntl & 7);
+ ov1 *= 1 + (cntl >> 4 & 7);
}
-s8int
-wavesamp(void)
+static void
+chansamp(chan *c, int t)
{
- u8int x;
-
- if((apustatus & 1<<4) == 0)
- return 0;
- x = wavebuf;
- if((wpos & 1) == 0)
- x >>= 4;
- else
- x &= 0xf;
- if((reg[NR32] & 3<<5) == 0)
- x = 0;
- else
- x = x >> (reg[NR32] >> 5 & 3) - 1;
- return x;
+ u8int ov;
+
+ ov = c->samp;
+ switch(c->n){
+ case 0: case 1:
+ c->samp = c->vol;
+ switch(reg[NR21] >> 6){
+ case 0: if(c->ctr < 7) c->samp = 0; break;
+ case 1: if(c->ctr < 6) c->samp = 0; break;
+ case 2: if(c->ctr < 4) c->samp = 0; break;
+ case 3: if(c->ctr >= 6) c->samp = 0; break;
+ }
+ break;
+ case 2:
+ if((apustatus & 1<<4) == 0){
+ c->samp = 0;
+ break;
+ }
+ c->samp = wavebuf;
+ if((wpos & 1) == 0)
+ c->samp >>= 4;
+ else
+ c->samp &= 0xf;
+ if((reg[NR32] & 3<<5) == 0)
+ c->samp = 0;
+ else
+ c->samp = c->samp >> (reg[NR32] >> 5 & 3) - 1;
+ break;
+ case 3:
+ c->samp = (lfsr & 1) != 0 ? 0 : c->vol;
+ }
+ if(ov != c->samp)
+ filter(t);
}
-s8int
-lfsrsamp(void)
+void
+chantick(void *vc)
{
- int v;
+ chan *c;
u16int l;
-
- sndch[3].fctr = v = sndch[3].fctr + sndch[3].finc;
- for(;;){
+
+ c = vc;
+ switch(c->n){
+ case 0: case 1:
+ c->ctr = c->ctr - 1 & 7;
+ break;
+ case 2:
+ wpos = wpos + 1 & 31;
+ wavebuf = reg[WAVE + (wpos >> 1)];
+ waveclock = clock;
+ break;
+ case 3:
l = lfsr;
- v -= 0x10000;
- if(v < 0)
- break;
lfsr >>= 1;
if(((l ^ lfsr) & 1) != 0)
if((reg[NR43] & 1<<3) != 0)
@@ -148,16 +185,40 @@
lfsr |= 0x40;
else
lfsr |= 0x4000;
+ break;
}
- if((l & 1) != 0)
- return 0;
- else
- return sndch[3].vol;
+ chansamp(c, chev[c->n].time);
+ addevent(&chev[c->n], c->per);
}
-void
-sweep(int wb)
+static void
+env(chan *c, int t)
{
+ if((envmod & 1) == 0 && c->len > 0 && (*c->freq & 1<<6) != 0)
+ if(--c->len == 0){
+ apustatus &= ~(1<<c->n);
+ c->vol = 0;
+ chansamp(c, t);
+ return;
+ }
+ if((apustatus & 1<<c->n) == 0 || (envmod & 7) != 7 || c->ectr == 0 || --c->ectr != 0)
+ return;
+ c->ectr = *c->env & 7;
+ if((*c->env & 1<<3) != 0){
+ if(c->vol < 15){
+ c->vol++;
+ chansamp(c, t);
+ }
+ }else
+ if(c->vol > 0){
+ c->vol--;
+ chansamp(c, t);
+ }
+}
+
+static void
+sweep(int wb, int t)
+{
u16int fr;
int d;
u16int cnt;
@@ -171,6 +232,7 @@
if(fr > 2047){
sndch[0].len = 0;
sndch[0].vol = 0;
+ chansamp(&sndch[0], t);
apustatus &= ~1;
sweepen = 0;
}else if(wb && (cnt & 7) != 0){
@@ -177,8 +239,8 @@
sweepfreq = fr;
reg[NR13] = fr;
reg[NR14] = reg[NR14] & 0xf8 | fr >> 8;
- rate(0, fr);
- sweep(0);
+ rate(&sndch[0], fr);
+ sweep(0, t);
}
}
@@ -187,6 +249,7 @@
{
u8int cnt;
+ filter(0);
c->vol = *c->env >> 4;
c->ectr = *c->env & 7;
if(c->len == 0)
@@ -200,80 +263,46 @@
sweepfreq = v << 8 & 0x700 | reg[NR13];
sweepcalc = 0;
if((cnt & 0x07) != 0)
- sweep(0);
+ sweep(0, 0);
}
if((*c->freq & 0x40) == 0 && (v & 0x40) != 0 && (envmod & 1) != 0 && --c->len == 0 || (*c->env & 0xf8) == 0){
apustatus &= ~(1<<c->n);
c->vol = 0;
}
+ chansamp(c, 0);
}
void
envtick(void *)
{
- addevent(&evenv, FREQ / 512);
-
- env(&sndch[0]);
- env(&sndch[1]);
+ env(&sndch[0], evenv.time);
+ env(&sndch[1], evenv.time);
if((envmod & 1) == 0 && sndch[2].len > 0 && (reg[NR34] & 0x40) != 0)
if(--sndch[2].len == 0){
apustatus &= ~4;
- delevent(&evwave);
+ delevent(&chev[2]);
}
- env(&sndch[3]);
+ env(&sndch[3], evenv.time);
if((envmod & 3) == 2 && sweepen && --sweepctr == 0){
sweepctr = reg[NR10] >> 4 & 7;
sweepctr += sweepctr - 1 & 8;
if((reg[NR10] & 0x70) != 0)
- sweep(1);
+ sweep(1, evenv.time);
}
envmod++;
+ addevent(&evenv, FREQ / 512);
}
void
sampletick(void *)
{
- u8int cntl, cnth;
- s16int ch[4];
- s16int s[2];
- int i;
-
- addevent(&evsamp, SRATEDIV);
-
- sndch[0].fctr += sndch[0].finc;
- if(sndch[0].fctr >= sndch[0].fthr)
- ch[0] = sndch[0].vol;
- else
- ch[0] = 0;
- sndch[1].fctr += sndch[1].finc;
- if(sndch[1].fctr >= sndch[1].fthr)
- ch[1] = sndch[1].vol;
- else
- ch[1] = 0;
- ch[2] = wavesamp();
- ch[3] = lfsrsamp();
-
- cntl = reg[NR50];
- cnth = reg[NR51];
- s[0] = 0;
- s[1] = 0;
- for(i = 0; i < 4; i++){
- if(i == 2 ? ((reg[NR30] & 0x80) == 0) : ((*sndch[i].env & 0xf8) == 0))
- continue;
- ch[i] = ch[i] * 2 - 15;
- if((cnth & 1<<i) != 0)
- s[0] += ch[i];
- if((cnth & 1<<4<<i) != 0)
- s[1] += ch[i];
- }
- s[0] *= 1 + (cntl & 7);
- s[1] *= 1 + (cntl >> 4 & 7);
-
+ filter(evsamp.time);
if(sbufp < sbuf + nelem(sbuf)){
- sbufp[0] = s[0] * 30;
- sbufp[1] = s[1] * 30;
+ sbufp[0] = samp[0] * 30;
+ sbufp[1] = samp[1] * 30;
sbufp += 2;
}
+ addevent(&evsamp, SRATEDIV);
}
void
@@ -299,7 +328,6 @@
}
break;
case NR11:
- sndch[0].fthr = thr[v >> 6 & 3];
sndch[0].len = 64 - (v & 63);
break;
case NR12:
@@ -309,15 +337,14 @@
}
break;
case NR13:
- rate(0, reg[NR14] << 8 & 0x700 | v);
+ rate(&sndch[0], reg[NR14] << 8 & 0x700 | v);
break;
case NR14:
- rate(0, v << 8 & 0x700 | reg[NR13]);
+ rate(&sndch[0], v << 8 & 0x700 | reg[NR13]);
if((v & 1<<7) != 0)
sndstart(&sndch[0], v);
break;
case NR21:
- sndch[1].fthr = thr[v >> 6 & 3];
sndch[1].len = 64 - (v & 63);
break;
case NR22:
@@ -327,10 +354,10 @@
}
break;
case NR23:
- rate(1, reg[NR24] << 8 & 0x700 | v);
+ rate(&sndch[1], reg[NR24] << 8 & 0x700 | v);
break;
case NR24:
- rate(1, v << 8 & 0x700 | reg[NR23]);
+ rate(&sndch[1], v << 8 & 0x700 | reg[NR23]);
if((v & 1<<7) != 0)
sndstart(&sndch[1], v);
break;
@@ -337,7 +364,7 @@
case NR30:
if((v & 0x80) == 0){
apustatus &= ~4;
- delevent(&evwave);
+ delevent(&chev[2]);
}
break;
case NR31:
@@ -344,10 +371,10 @@
sndch[2].len = 256 - (v & 0xff);
break;
case NR33:
- rate(2, reg[NR34] << 8 & 0x700 | v);
+ rate(&sndch[2], reg[NR34] << 8 & 0x700 | v);
break;
case NR34:
- rate(2, v << 8 & 0x700 | reg[NR33]);
+ rate(&sndch[2], v << 8 & 0x700 | reg[NR33]);
if((v & 0x80) != 0){
if(sndch[2].len == 0)
sndch[2].len = 256;
@@ -354,8 +381,8 @@
wpos = 0;
if((reg[NR30] & 0x80) != 0){
apustatus |= 4;
- delevent(&evwave);
- addevent(&evwave, sndch[2].finc);
+ delevent(&chev[2]);
+ addevent(&chev[2], sndch[2].per);
}
}
break;
@@ -369,7 +396,7 @@
}
break;
case NR43:
- rate(3, v);
+ rate(&sndch[3], v);
break;
case NR44:
if((v & 1<<7) != 0){
@@ -380,6 +407,9 @@
sndstart(&sndch[3], v);
}
break;
+ case NR50: case NR51:
+ filter(0);
+ break;
case NR52:
apustatus = v & 0xf0 | apustatus & 0x0f;
if((v & 0x80) == 0){
@@ -390,15 +420,14 @@
sndch[2].len = 0;
sndch[3].len = 0;
apustatus = 0;
- delevent(&evwave);
+ delevent(&chev[2]);
}
}else if((reg[NR52] & 0x80) == 0){
envmod = 0;
delevent(&evenv);
addevent(&evenv, FREQ / 512);
- sndch[0].fctr = 0;
- sndch[1].fctr = 0;
- sndch[3].fctr = 0;
+ sndch[0].ctr = 0;
+ sndch[1].ctr = 0;
}
}
reg[a] = v;
@@ -430,6 +459,9 @@
sbufp = sbuf;
evsamp.f = sampletick;
addevent(&evsamp, SRATEDIV);
+ addevent(&chev[0], 8 * 2048);
+ addevent(&chev[1], 8 * 2048);
+ addevent(&chev[3], 8 * 2048);
}
int
--- a/sys/src/games/gb/dat.h
+++ b/sys/src/games/gb/dat.h
@@ -155,6 +155,6 @@
};
#define VAR(a) {&a, sizeof(a), 1}
#define ARR(a) {a, sizeof(*a), nelem(a)}
-enum { NEVENT = 5 };
+enum { NEVENT = 8 };
extern int (*mapper)(int, int);
extern u32int moncols[4];
--- a/sys/src/games/gb/ev.c
+++ b/sys/src/games/gb/ev.c
@@ -4,9 +4,9 @@
#include "dat.h"
#include "fns.h"
-Event evhblank, evtimer, evenv, evwave;
-extern Event evsamp;
-Event *events[NEVENT] = {&evhblank, &evtimer, &evenv, &evsamp, &evwave};
+Event evhblank, evtimer, evenv;
+extern Event evsamp, chev[4];
+Event *events[NEVENT] = {&evhblank, &evtimer, &evenv, &evsamp, &chev[0], &chev[1], &chev[2], &chev[3]};
Event *elist;
static int timshtab[4] = {12, 4, 6, 8}, timsh;
ulong timclock;
@@ -109,6 +109,7 @@
extern void hblanktick(void *);
extern void envtick(void *);
extern void wavetick(void *);
+ extern void chantick(void *);
evhblank.f = hblanktick;
addevent(&evhblank, 240*4);
@@ -115,5 +116,8 @@
evtimer.f = timertick;
evenv.f = envtick;
addevent(&evenv, FREQ / 512);
- evwave.f = wavetick;
+ chev[0].f = chantick;
+ chev[1].f = chantick;
+ chev[2].f = chantick;
+ chev[3].f = chantick;
}
--- a/sys/src/games/gb/gb.c
+++ b/sys/src/games/gb/gb.c
@@ -222,6 +222,7 @@
static char buf[256];
char *s;
Rune r;
+ extern double TAU;
fd = open("/dev/kbd", OREAD);
if(fd < 0)
@@ -240,6 +241,10 @@
}
if(utfrune(buf, 't'))
trace = !trace;
+ if(utfrune(buf, KF|9))
+ TAU += 5000;
+ if(utfrune(buf, KF|10))
+ TAU -= 5000;
}
if(buf[0] != 'k' && buf[0] != 'K')
continue;