games/gb: improve sound emulation by modelling analog behaviour

This commit is contained in:
aiju 2015-04-12 14:55:25 +02:00
parent d009b0013d
commit e8221d07d8
4 changed files with 187 additions and 146 deletions

View file

@ -4,8 +4,10 @@
#include "dat.h" #include "dat.h"
#include "fns.h" #include "fns.h"
double TAU = 25000;
Event evsamp; Event evsamp;
extern Event evenv, evwave; extern Event evenv;
s16int sbuf[2*4000], *sbufp; s16int sbuf[2*4000], *sbufp;
enum { enum {
Freq = 44100, Freq = 44100,
@ -18,29 +20,31 @@ u8int sweepen, sweepcalc, sweepctr;
u16int sweepfreq; u16int sweepfreq;
typedef struct chan chan; typedef struct chan chan;
struct chan { struct chan {
u8int n, ectr;
u16int len;
u8int *env, *freq; u8int *env, *freq;
u16int fctr, fthr; int per;
u32int finc; u16int len;
u8int vol; u8int n, ectr;
u8int vol, ctr, samp;
}; };
u8int wpos; u8int wpos;
u16int lfsr; u16int lfsr;
u8int apustatus; u8int apustatus;
ulong waveclock; ulong waveclock;
u8int wavebuf; u8int wavebuf;
double samp[2];
chan sndch[4] = { chan sndch[4] = {
{ {
.n = 0, .n = 0,
.env = reg + NR12, .env = reg + NR12,
.freq = reg + NR14, .freq = reg + NR14,
.per = 8 * 2048,
}, },
{ {
.n = 1, .n = 1,
.env = reg + NR22, .env = reg + NR22,
.freq = reg + NR24, .freq = reg + NR24,
.per = 8 * 2048,
}, },
{ {
.n = 2, .n = 2,
@ -49,114 +53,171 @@ chan sndch[4] = {
.n = 3, .n = 3,
.env = reg + NR42, .env = reg + NR42,
.freq = reg + NR44, .freq = reg + NR44,
.per = 32
} }
}; };
Event chev[4] = {
{.aux = &sndch[0]},
{.aux = &sndch[1]},
{.aux = &sndch[2]},
{.aux = &sndch[3]}
};
Var apuvars[] = { Var apuvars[] = {
VAR(apustatus), VAR(envmod), VAR(sweepen), VAR(sweepcalc), VAR(apustatus), VAR(envmod), VAR(sweepen), VAR(sweepcalc),
VAR(sweepctr), VAR(sweepfreq), VAR(wpos), VAR(lfsr), VAR(waveclock), VAR(wavebuf), 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[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].fctr), VAR(sndch[1].fthr), VAR(sndch[1].finc), VAR(sndch[1].vol), 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].fctr), VAR(sndch[2].fthr), VAR(sndch[2].finc), VAR(sndch[2].vol), 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].fctr), VAR(sndch[3].fthr), VAR(sndch[3].finc), VAR(sndch[3].vol), VAR(sndch[3].ectr), VAR(sndch[3].len), VAR(sndch[3].per), VAR(sndch[3].vol), VAR(sndch[3].samp),
{nil, 0, 0}, {nil, 0, 0},
}; };
void static void
rate(int i, u16int v) rate(chan *c, u16int v)
{ {
switch(i){ switch(c->n){
case 0: case 1: case 0: case 1:
sndch[i].finc = 131072ULL * 65536 / (Freq * (2048 - (v & 0x7ff))); c->per = 8 * (2048 - (v & 0x7ff));
break; break;
case 2: case 2:
sndch[2].finc = 4 * (2048 - (v & 0x7ff)); c->per = 4 * (2048 - (v & 0x7ff));
break; break;
case 3: case 3:
sndch[3].finc = 524288ULL * 65536 / Freq; c->per = 32;
if((v & 7) != 0) if((v & 7) != 0)
sndch[3].finc /= v & 7; c->per *= v & 7;
else else
sndch[3].finc <<= 1; c->per >>= 1;
sndch[3].finc >>= (v >> 4 & 15) + 1; c->per <<= (v >> 4 & 15);
} }
} }
void static void
env(chan *c) filter(int t)
{ {
if((envmod & 1) == 0 && c->len > 0 && (*c->freq & 1<<6) != 0) static int ov0, ov1;
if(--c->len == 0){ static u32int oclock;
apustatus &= ~(1<<c->n); double e;
c->vol = 0; u8int cntl, cnth;
return; int i, v;
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);
}
static void
chansamp(chan *c, int t)
{
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;
} }
if((apustatus & 1<<c->n) == 0 || (envmod & 7) != 7 || c->ectr == 0 || --c->ectr != 0) break;
return; case 2:
c->ectr = *c->env & 7; if((apustatus & 1<<4) == 0){
if((*c->env & 1<<3) != 0){ c->samp = 0;
if(c->vol < 15) break;
c->vol++; }
}else c->samp = wavebuf;
if(c->vol > 0) if((wpos & 1) == 0)
c->vol--; 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);
} }
void void
wavetick(void *) chantick(void *vc)
{ {
addevent(&evwave, sndch[2].finc); chan *c;
wpos = wpos + 1 & 31;
wavebuf = reg[WAVE + (wpos >> 1)];
waveclock = clock;
}
s8int
wavesamp(void)
{
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;
}
s8int
lfsrsamp(void)
{
int v;
u16int l; u16int l;
sndch[3].fctr = v = sndch[3].fctr + sndch[3].finc; c = vc;
for(;;){ 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; l = lfsr;
v -= 0x10000;
if(v < 0)
break;
lfsr >>= 1; lfsr >>= 1;
if(((l ^ lfsr) & 1) != 0) if(((l ^ lfsr) & 1) != 0)
if((reg[NR43] & 1<<3) != 0) if((reg[NR43] & 1<<3) != 0)
lfsr |= 0x40; lfsr |= 0x40;
else else
lfsr |= 0x4000; lfsr |= 0x4000;
break;
} }
if((l & 1) != 0) chansamp(c, chev[c->n].time);
return 0; addevent(&chev[c->n], c->per);
else
return sndch[3].vol;
} }
void static void
sweep(int wb) 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; u16int fr;
int d; int d;
@ -171,14 +232,15 @@ sweep(int wb)
if(fr > 2047){ if(fr > 2047){
sndch[0].len = 0; sndch[0].len = 0;
sndch[0].vol = 0; sndch[0].vol = 0;
chansamp(&sndch[0], t);
apustatus &= ~1; apustatus &= ~1;
sweepen = 0; sweepen = 0;
}else if(wb && (cnt & 7) != 0){ }else if(wb && (cnt & 7) != 0){
sweepfreq = fr; sweepfreq = fr;
reg[NR13] = fr; reg[NR13] = fr;
reg[NR14] = reg[NR14] & 0xf8 | fr >> 8; reg[NR14] = reg[NR14] & 0xf8 | fr >> 8;
rate(0, fr); rate(&sndch[0], fr);
sweep(0); sweep(0, t);
} }
} }
@ -187,6 +249,7 @@ sndstart(chan *c, u8int v)
{ {
u8int cnt; u8int cnt;
filter(0);
c->vol = *c->env >> 4; c->vol = *c->env >> 4;
c->ectr = *c->env & 7; c->ectr = *c->env & 7;
if(c->len == 0) if(c->len == 0)
@ -200,80 +263,46 @@ sndstart(chan *c, u8int v)
sweepfreq = v << 8 & 0x700 | reg[NR13]; sweepfreq = v << 8 & 0x700 | reg[NR13];
sweepcalc = 0; sweepcalc = 0;
if((cnt & 0x07) != 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){ if((*c->freq & 0x40) == 0 && (v & 0x40) != 0 && (envmod & 1) != 0 && --c->len == 0 || (*c->env & 0xf8) == 0){
apustatus &= ~(1<<c->n); apustatus &= ~(1<<c->n);
c->vol = 0; c->vol = 0;
} }
chansamp(c, 0);
} }
void void
envtick(void *) envtick(void *)
{ {
addevent(&evenv, FREQ / 512); env(&sndch[0], evenv.time);
env(&sndch[1], evenv.time);
env(&sndch[0]);
env(&sndch[1]);
if((envmod & 1) == 0 && sndch[2].len > 0 && (reg[NR34] & 0x40) != 0) if((envmod & 1) == 0 && sndch[2].len > 0 && (reg[NR34] & 0x40) != 0)
if(--sndch[2].len == 0){ if(--sndch[2].len == 0){
apustatus &= ~4; apustatus &= ~4;
delevent(&evwave); delevent(&chev[2]);
} }
env(&sndch[3]); env(&sndch[3], evenv.time);
if((envmod & 3) == 2 && sweepen && --sweepctr == 0){ if((envmod & 3) == 2 && sweepen && --sweepctr == 0){
sweepctr = reg[NR10] >> 4 & 7; sweepctr = reg[NR10] >> 4 & 7;
sweepctr += sweepctr - 1 & 8; sweepctr += sweepctr - 1 & 8;
if((reg[NR10] & 0x70) != 0) if((reg[NR10] & 0x70) != 0)
sweep(1); sweep(1, evenv.time);
} }
envmod++; envmod++;
addevent(&evenv, FREQ / 512);
} }
void void
sampletick(void *) sampletick(void *)
{ {
u8int cntl, cnth; filter(evsamp.time);
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);
if(sbufp < sbuf + nelem(sbuf)){ if(sbufp < sbuf + nelem(sbuf)){
sbufp[0] = s[0] * 30; sbufp[0] = samp[0] * 30;
sbufp[1] = s[1] * 30; sbufp[1] = samp[1] * 30;
sbufp += 2; sbufp += 2;
} }
addevent(&evsamp, SRATEDIV);
} }
void void
@ -299,7 +328,6 @@ sndwrite(u8int a, u8int v)
} }
break; break;
case NR11: case NR11:
sndch[0].fthr = thr[v >> 6 & 3];
sndch[0].len = 64 - (v & 63); sndch[0].len = 64 - (v & 63);
break; break;
case NR12: case NR12:
@ -309,15 +337,14 @@ sndwrite(u8int a, u8int v)
} }
break; break;
case NR13: case NR13:
rate(0, reg[NR14] << 8 & 0x700 | v); rate(&sndch[0], reg[NR14] << 8 & 0x700 | v);
break; break;
case NR14: case NR14:
rate(0, v << 8 & 0x700 | reg[NR13]); rate(&sndch[0], v << 8 & 0x700 | reg[NR13]);
if((v & 1<<7) != 0) if((v & 1<<7) != 0)
sndstart(&sndch[0], v); sndstart(&sndch[0], v);
break; break;
case NR21: case NR21:
sndch[1].fthr = thr[v >> 6 & 3];
sndch[1].len = 64 - (v & 63); sndch[1].len = 64 - (v & 63);
break; break;
case NR22: case NR22:
@ -327,35 +354,35 @@ sndwrite(u8int a, u8int v)
} }
break; break;
case NR23: case NR23:
rate(1, reg[NR24] << 8 & 0x700 | v); rate(&sndch[1], reg[NR24] << 8 & 0x700 | v);
break; break;
case NR24: case NR24:
rate(1, v << 8 & 0x700 | reg[NR23]); rate(&sndch[1], v << 8 & 0x700 | reg[NR23]);
if((v & 1<<7) != 0) if((v & 1<<7) != 0)
sndstart(&sndch[1], v); sndstart(&sndch[1], v);
break; break;
case NR30: case NR30:
if((v & 0x80) == 0){ if((v & 0x80) == 0){
apustatus &= ~4; apustatus &= ~4;
delevent(&evwave); delevent(&chev[2]);
} }
break; break;
case NR31: case NR31:
sndch[2].len = 256 - (v & 0xff); sndch[2].len = 256 - (v & 0xff);
break; break;
case NR33: case NR33:
rate(2, reg[NR34] << 8 & 0x700 | v); rate(&sndch[2], reg[NR34] << 8 & 0x700 | v);
break; break;
case NR34: case NR34:
rate(2, v << 8 & 0x700 | reg[NR33]); rate(&sndch[2], v << 8 & 0x700 | reg[NR33]);
if((v & 0x80) != 0){ if((v & 0x80) != 0){
if(sndch[2].len == 0) if(sndch[2].len == 0)
sndch[2].len = 256; sndch[2].len = 256;
wpos = 0; wpos = 0;
if((reg[NR30] & 0x80) != 0){ if((reg[NR30] & 0x80) != 0){
apustatus |= 4; apustatus |= 4;
delevent(&evwave); delevent(&chev[2]);
addevent(&evwave, sndch[2].finc); addevent(&chev[2], sndch[2].per);
} }
} }
break; break;
@ -369,7 +396,7 @@ sndwrite(u8int a, u8int v)
} }
break; break;
case NR43: case NR43:
rate(3, v); rate(&sndch[3], v);
break; break;
case NR44: case NR44:
if((v & 1<<7) != 0){ if((v & 1<<7) != 0){
@ -380,6 +407,9 @@ sndwrite(u8int a, u8int v)
sndstart(&sndch[3], v); sndstart(&sndch[3], v);
} }
break; break;
case NR50: case NR51:
filter(0);
break;
case NR52: case NR52:
apustatus = v & 0xf0 | apustatus & 0x0f; apustatus = v & 0xf0 | apustatus & 0x0f;
if((v & 0x80) == 0){ if((v & 0x80) == 0){
@ -390,15 +420,14 @@ sndwrite(u8int a, u8int v)
sndch[2].len = 0; sndch[2].len = 0;
sndch[3].len = 0; sndch[3].len = 0;
apustatus = 0; apustatus = 0;
delevent(&evwave); delevent(&chev[2]);
} }
}else if((reg[NR52] & 0x80) == 0){ }else if((reg[NR52] & 0x80) == 0){
envmod = 0; envmod = 0;
delevent(&evenv); delevent(&evenv);
addevent(&evenv, FREQ / 512); addevent(&evenv, FREQ / 512);
sndch[0].fctr = 0; sndch[0].ctr = 0;
sndch[1].fctr = 0; sndch[1].ctr = 0;
sndch[3].fctr = 0;
} }
} }
reg[a] = v; reg[a] = v;
@ -430,6 +459,9 @@ audioinit(void)
sbufp = sbuf; sbufp = sbuf;
evsamp.f = sampletick; evsamp.f = sampletick;
addevent(&evsamp, SRATEDIV); addevent(&evsamp, SRATEDIV);
addevent(&chev[0], 8 * 2048);
addevent(&chev[1], 8 * 2048);
addevent(&chev[3], 8 * 2048);
} }
int int

View file

@ -155,6 +155,6 @@ struct Var {
}; };
#define VAR(a) {&a, sizeof(a), 1} #define VAR(a) {&a, sizeof(a), 1}
#define ARR(a) {a, sizeof(*a), nelem(a)} #define ARR(a) {a, sizeof(*a), nelem(a)}
enum { NEVENT = 5 }; enum { NEVENT = 8 };
extern int (*mapper)(int, int); extern int (*mapper)(int, int);
extern u32int moncols[4]; extern u32int moncols[4];

View file

@ -4,9 +4,9 @@
#include "dat.h" #include "dat.h"
#include "fns.h" #include "fns.h"
Event evhblank, evtimer, evenv, evwave; Event evhblank, evtimer, evenv;
extern Event evsamp; extern Event evsamp, chev[4];
Event *events[NEVENT] = {&evhblank, &evtimer, &evenv, &evsamp, &evwave}; Event *events[NEVENT] = {&evhblank, &evtimer, &evenv, &evsamp, &chev[0], &chev[1], &chev[2], &chev[3]};
Event *elist; Event *elist;
static int timshtab[4] = {12, 4, 6, 8}, timsh; static int timshtab[4] = {12, 4, 6, 8}, timsh;
ulong timclock; ulong timclock;
@ -109,11 +109,15 @@ eventinit(void)
extern void hblanktick(void *); extern void hblanktick(void *);
extern void envtick(void *); extern void envtick(void *);
extern void wavetick(void *); extern void wavetick(void *);
extern void chantick(void *);
evhblank.f = hblanktick; evhblank.f = hblanktick;
addevent(&evhblank, 240*4); addevent(&evhblank, 240*4);
evtimer.f = timertick; evtimer.f = timertick;
evenv.f = envtick; evenv.f = envtick;
addevent(&evenv, FREQ / 512); addevent(&evenv, FREQ / 512);
evwave.f = wavetick; chev[0].f = chantick;
chev[1].f = chantick;
chev[2].f = chantick;
chev[3].f = chantick;
} }

View file

@ -222,6 +222,7 @@ keyproc(void *)
static char buf[256]; static char buf[256];
char *s; char *s;
Rune r; Rune r;
extern double TAU;
fd = open("/dev/kbd", OREAD); fd = open("/dev/kbd", OREAD);
if(fd < 0) if(fd < 0)
@ -240,6 +241,10 @@ keyproc(void *)
} }
if(utfrune(buf, 't')) if(utfrune(buf, 't'))
trace = !trace; trace = !trace;
if(utfrune(buf, KF|9))
TAU += 5000;
if(utfrune(buf, KF|10))
TAU -= 5000;
} }
if(buf[0] != 'k' && buf[0] != 'K') if(buf[0] != 'k' && buf[0] != 'K')
continue; continue;