diff options
author | aiju <devnull@localhost> | 2014-02-23 21:46:16 +0100 |
---|---|---|
committer | aiju <devnull@localhost> | 2014-02-23 21:46:16 +0100 |
commit | ad9047ab2cdbdbc5897d799fecacdda98f6cd707 (patch) | |
tree | ea9824f6fe1bf317cb0c51f3cab372f78930e558 /sys/src/games | |
parent | f82e3e8657a880cd0bbad9dd370da5069d8fdf04 (diff) |
games/nes: basic audio support, battery backup, bug fixes
Diffstat (limited to 'sys/src/games')
-rw-r--r-- | sys/src/games/nes/apu.c | 248 | ||||
-rw-r--r-- | sys/src/games/nes/dat.h | 14 | ||||
-rw-r--r-- | sys/src/games/nes/fns.h | 2 | ||||
-rw-r--r-- | sys/src/games/nes/mem.c | 53 | ||||
-rw-r--r-- | sys/src/games/nes/mkfile | 1 | ||||
-rw-r--r-- | sys/src/games/nes/nes.c | 72 | ||||
-rw-r--r-- | sys/src/games/nes/ppu.c | 7 | ||||
-rw-r--r-- | sys/src/games/nes/state.c | 6 |
8 files changed, 385 insertions, 18 deletions
diff --git a/sys/src/games/nes/apu.c b/sys/src/games/nes/apu.c new file mode 100644 index 000000000..f34704506 --- /dev/null +++ b/sys/src/games/nes/apu.c @@ -0,0 +1,248 @@ +#include <u.h> +#include <libc.h> +#include <thread.h> +#include <draw.h> +#include "dat.h" +#include "fns.h" + +u8int apuseq, apuctr[10]; +static int fd; + +enum { RATE = 44100 }; + +int +targperiod(int i) +{ + int m, p, t; + + m = mem[0x4001 + i * 4]; + p = mem[0x4002 + i * 4]; + p |= (mem[0x4003 + i * 4] & 7) << 8; + t = p >> (m & 7); + if((m & 8) != 0){ + if(i == 0 && t != 0) + t--; + t = p - t; + }else + t += p; + return t; +} + +static void +declen(void) +{ + int i, m, p; + u8int *a; + + for(i = 0; i < 4; i++){ + m = mem[0x4000 + i * 4]; + if(i == 2) + m >>= 2; + if((m & 0x20) != 0) + continue; + if(apuctr[i] != 0) + apuctr[i]--; + } + for(i = 0, a = apuctr + 8; i < 2; i++, a++){ + m = mem[0x4001 + i * 4]; + if((m & 0x80) != 0 && (m & 0x07) != 0 && (*a & 7) == 0){ + p = targperiod(i); + if(p <= 0x7ff){ + mem[0x4002 + i * 4] = p; + mem[0x4003 + i * 4] = p >> 8; + } + } + if((*a & 0x80) != 0 || (*a & 7) == 0 && (m & 0x80) != 0) + *a = (m & 0x70) >> 4; + else if(*a != 0) + (*a)--; + } +} + +static void +doenv(void) +{ + int i, m; + u8int *a; + + for(i = 0, a = apuctr + 4; i < 4; i++, a++){ + if(i == 2) + continue; + m = mem[0x4000 + 4 * i]; + if((*a & 0x80) != 0) + *a = *a & 0x70 | 0x0f; + else if(*a == 0){ + if((m & 0x20) != 0) + *a |= 0x0f; + }else + (*a)--; + } + a = apuctr + 6; + if((*a & 0x80) != 0) + *a = mem[0x4008]; + else if(*a != 0) + (*a)--; +} + +void +apustep(void) +{ + int mode, len, env; + + mode = mem[APUFRAME]; + if((mode & 0x80) != 0){ + if(apuseq >= 4){ + env = len = 0; + apuseq = 0; + }else{ + env = 1; + len = (apuseq & 1) == 0; + apuseq++; + } + }else{ + env = 1; + len = (apuseq & 1) != 0; + if(apuseq >= 3){ + if((mode & 0x40) == 0) + irq |= IRQFRAME; + apuseq = 0; + }else + apuseq++; + } + if(len) + declen(); + if(env) + doenv(); +} + +static int +freq(int i) +{ + int f; + + f = mem[0x4002 + 4 * i]; + f |= (mem[0x4003 + 4 * i] & 0x7) << 8; + return f; +} + +static int +pulse(int i) +{ + static int c[2]; + int m, s, f; + + f = freq(i); + if(f < 8 || targperiod(i) > 0x7ff) + f = -1; + else + f = muldiv(16 * (f + 1), RATE, FREQ/12); + if(c[i] >= f) + c[i] = 0; + else + c[i]++; + m = mem[0x4000 + 4 * i]; + if((m & 0x10) != 0) + s = m; + else + s = apuctr[i+4]; + s &= 0x0f; + if(c[i] >= f/2 || apuctr[i] == 0) + s = 0; + return s; +} + +static int +tri(void) +{ + static int c; + int f, i; + + f = freq(2); + if(f <= 2) + return 7; + f = muldiv(32 * (f + 1), RATE, FREQ / 12); + if(c >= f) + c = 0; + else + c++; + i = 32 * c / f; + i ^= (i < 16) ? 0xf : 0x10; + if(apuctr[2] == 0 || (apuctr[6] & 0x7f) == 0) + return 0; + return i; +} + +static int +noise(void) +{ + static int c, r=1; + int m, f; + static int per[] = { + 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, + 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4, + }; + + m = mem[0x400E]; + f = muldiv(per[m & 0x0f], RATE * 1000, FREQ/24); + c += 1000; + while(c >= f){ + r |= ((r ^ (r >> ((m & 0x80) != 0 ? 6 : 1))) & 1) << 15; + r >>= 1; + c -= f; + } + if(apuctr[3] == 0 || (r & 1) != 0) + return 0; + m = mem[0x400C]; + if((m & 0x10) != 0) + return m & 0xf; + return apuctr[7] & 0xf; +} + +static int +dmc(void) +{ + return 0; +} + +static void +sample(short *s) +{ + double d; + + d = 95.88 / (8128.0 / (0.01 + pulse(0) + pulse(1)) + 100); + d += 159.79 / (1.0 / (0.01 + tri()/8227.0 + noise()/12241.0 + dmc()/22638.0) + 100.0); + *s++ = d * 20000; + *s = d * 20000; +} + +static void +audioproc(void *) +{ + static short samples[500 * 2]; + int i; + + for(;;){ + if(paused) + memset(samples, 0, sizeof samples); + else + for(i = 0; i < sizeof samples/4; i++) + sample(samples + 2 * i); + write(fd, samples, sizeof samples); + } +} + +void +initaudio(void) +{ + fd = open("/dev/audio", OWRITE); + if(fd < 0) + return; + proccreate(audioproc, nil, 8192); +} + +u8int apulen[32] = { + 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, + 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, + 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, + 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E, +}; diff --git a/sys/src/games/nes/dat.h b/sys/src/games/nes/dat.h index 04e2c5042..3e56023e8 100644 --- a/sys/src/games/nes/dat.h +++ b/sys/src/games/nes/dat.h @@ -10,7 +10,9 @@ extern int map, scale, mmc3hack, oflag; extern uchar *prg, *chr; extern int nprg, nchr, map, chrram; -extern int keys, clock, ppuclock; +extern u8int apuseq, apuctr[10]; + +extern int keys, clock, ppuclock, apuclock, saveclock, paused; extern void (*mapper[])(int, u8int); @@ -29,6 +31,8 @@ enum { PPUMASK = 0x2001, PPUSTATUS = 0x2002, PPUSCROLL = 0x2005, + APUSTATUS = 0x4015, + APUFRAME = 0x4017, PPUNMI = 1<<7, BIGSPRITE = 1<<5, @@ -71,6 +75,8 @@ enum { FREQ = 21477272, MILLION = 1000000, BILLION = 1000000000, + APUDIV = 89490, + SAVEFREQ = FREQ/5, }; enum { @@ -87,3 +93,9 @@ enum { RSTR = -3, SCAN = -4, }; + +enum { + IRQFRAME = 1, + IRQDMC = 2, + IRQMMC = 4, +}; diff --git a/sys/src/games/nes/fns.h b/sys/src/games/nes/fns.h index 7467f0991..0dab3df5f 100644 --- a/sys/src/games/nes/fns.h +++ b/sys/src/games/nes/fns.h @@ -9,3 +9,5 @@ void savestate(char *); void message(char *, ...); void put8(u8int); int get8(void); +void apustep(void); +void initaudio(void); diff --git a/sys/src/games/nes/mem.c b/sys/src/games/nes/mem.c index 5b983ea32..c7c8b15ad 100644 --- a/sys/src/games/nes/mem.c +++ b/sys/src/games/nes/mem.c @@ -158,7 +158,7 @@ mmc3(int p, u8int v) else n--; if(n == 0 && en) - irq |= 2; + irq |= IRQMMC; return; case SAVE: put8(m); @@ -204,7 +204,7 @@ mmc3(int p, u8int v) break; case 0xC000: l = v; break; case 0xC001: n = 0; break; - case 0xE000: en = 0; irq &= ~2; break; + case 0xE000: en = 0; irq &= ~IRQMMC; break; case 0xE001: en = 1; break; } return; @@ -281,6 +281,7 @@ u8int memread(u16int p) { u8int v; + int i; if(p < 0x2000){ p &= 0x7FF; @@ -305,6 +306,16 @@ memread(u16int p) vrambuf = ppuread(ppuv); incvram(); return vrambuf; + case APUSTATUS: + v = (irq & 3) << 6; + for(i = 0; i < 4; i++){ + if(apuctr[i] != 0) + v |= (1<<i); + } + if(mem[0x4013] != 0) + v |= (1<<4); + irq &= ~IRQFRAME; + return v; case 0x4016: if((mem[p] & 1) != 0) return keys & 1; @@ -325,6 +336,9 @@ memread(u16int p) void memwrite(u16int p, u8int v) { + extern u8int apulen[32]; + int i; + if(p < 0x2000){ p &= 0x7FF; }else if(p < 0x6000){ @@ -365,15 +379,48 @@ memwrite(u16int p, u8int v) ppuwrite(ppuv, v); incvram(); return; + case 0x4001: + case 0x4005: + i = (p & 0xC) >> 2; + apuctr[i+8] |= 0x80; + break; + case 0x4003: + case 0x4007: + case 0x400B: + case 0x400F: + i = (p & 0xC) >> 2; + if((mem[APUSTATUS] & (1<<i)) != 0){ + apuctr[i] = apulen[v >> 3]; + apuctr[i+4] |= 0x80; + } + break; case 0x4014: memcpy(oam, mem + (v<<8), sizeof(oam)); return; + case APUSTATUS: + for(i = 0; i < 4; i++) + if((v & (1<<i)) == 0) + apuctr[i] = 0; + irq &= ~IRQDMC; + break; case 0x4016: if((mem[p] & 1) != 0 && (v & 1) == 0) keylatch = keys; break; + case APUFRAME: + apuseq = 0; + if((v & 0x80) != 0) + apuclock = APUDIV; + else + apuclock = 0; + if((v & 0x40) != 0) + irq &= ~IRQFRAME; + break; } - }else if(p >= 0x8000){ + }else if(p < 0x8000){ + if(saveclock == 0) + saveclock = SAVEFREQ; + }else{ if(mapper[map] != nil) mapper[map](p, v); return; diff --git a/sys/src/games/nes/mkfile b/sys/src/games/nes/mkfile index 8b34fa02f..98c8a14e0 100644 --- a/sys/src/games/nes/mkfile +++ b/sys/src/games/nes/mkfile @@ -8,6 +8,7 @@ OFILES=\ nes.$O\ ppu.$O\ state.$O\ + apu.$O\ HFILES=dat.h fns.h diff --git a/sys/src/games/nes/nes.c b/sys/src/games/nes/nes.c index 15bee498f..a50a4908a 100644 --- a/sys/src/games/nes/nes.c +++ b/sys/src/games/nes/nes.c @@ -13,9 +13,9 @@ uchar *prg, *chr; int scale; Rectangle picr; Image *tmp, *bg; -int clock, ppuclock, syncclock, syncfreq, checkclock, msgclock, sleeps; +int clock, ppuclock, apuclock, syncclock, syncfreq, checkclock, msgclock, saveclock, sleeps; Mousectl *mc; -int keys, paused, savereq, loadreq, oflag; +int keys, paused, savereq, loadreq, oflag, savefd = -1; int mirr; QLock pauselock; @@ -33,12 +33,22 @@ message(char *fmt, ...) } void -loadrom(char *file) +flushram(void) +{ + if(savefd >= 0) + pwrite(savefd, mem + 0x6000, 0x2000, 0); + saveclock = 0; +} + +void +loadrom(char *file, int sflag) { int fd; int nes20; + char *s; static uchar header[16]; static u32int flags; + static char buf[512]; fd = open(file, OREAD); if(fd < 0) @@ -94,7 +104,22 @@ loadrom(char *file) mirr = MVERT; else mirr = MHORZ; - mapper[map](-1, 0); + if(sflag){ + strncpy(buf, file, sizeof buf - 5); + s = buf + strlen(buf) - 4; + if(s < buf || strcmp(s, ".nes") != 0) + s += 4; + strcpy(s, ".sav"); + savefd = create(buf, ORDWR | OEXCL, 0666); + if(savefd < 0) + savefd = open(buf, ORDWR); + if(savefd < 0) + message("open: %r"); + else + readn(savefd, mem + 0x6000, 0x2000); + atexit(flushram); + } + mapper[map](INIT, 0); } extern int trace; @@ -114,8 +139,10 @@ keyproc(void *) if(read(fd, buf, sizeof(buf) - 1) <= 0) sysfatal("read /dev/kbd: %r"); if(buf[0] == 'c'){ - if(utfrune(buf, Kdel)) + if(utfrune(buf, Kdel)){ + close(fd); threadexitsall(nil); + } if(utfrune(buf, KF|5)) savereq = 1; if(utfrune(buf, KF|6)) @@ -130,7 +157,7 @@ keyproc(void *) while(*s != 0){ s += chartorune(&r, s); switch(r){ - case Kdel: threadexitsall(nil); + case Kdel: close(fd); threadexitsall(nil); case 'x': k |= 1<<0; break; case 'z': k |= 1<<1; break; case Kshift: k |= 1<<2; break; @@ -155,13 +182,17 @@ keyproc(void *) void threadmain(int argc, char **argv) { - int t, h; + int t, h, sflag; Point p; uvlong old, new, diff; scale = 1; h = 240; + sflag = 0; ARGBEGIN { + case 'a': + initaudio(); + break; case '2': scale = 2; break; @@ -172,11 +203,19 @@ threadmain(int argc, char **argv) oflag = 1; h -= 16; break; + case 's': + sflag = 1; + break; + default: + goto usage; } ARGEND; - if(argc < 1) - sysfatal("missing argument"); - loadrom(argv[0]); + if(argc != 1){ + usage: + fprint(2, "usage: %s [-23aos] rom\n", argv0); + threadexitsall("usage"); + } + loadrom(argv[0], sflag); if(initdraw(nil, nil, nil) < 0) sysfatal("initdraw: %r"); mc = initmouse(nil, screen); @@ -186,8 +225,7 @@ threadmain(int argc, char **argv) originwindow(screen, Pt(0, 0), screen->r.min); p = divpt(addpt(screen->r.min, screen->r.max), 2); picr = (Rectangle){subpt(p, Pt(scale * 128, scale * h/2)), addpt(p, Pt(scale * 128, scale * h/2))}; - if(screen->chan != XRGB32) - tmp = allocimage(display, Rect(0, 0, scale * 256, scale * h), XRGB32, 0, 0); + tmp = allocimage(display, Rect(0, 0, scale * 256, scale * h), XRGB32, 0, 0); bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF); draw(screen, screen->r, bg, nil, ZP); @@ -211,12 +249,17 @@ threadmain(int argc, char **argv) t = step() * 12; clock += t; ppuclock += t; + apuclock += t; syncclock += t; checkclock += t; while(ppuclock >= 4){ ppustep(); ppuclock -= 4; } + if(apuclock >= APUDIV){ + apustep(); + apuclock -= APUDIV; + } if(syncclock >= syncfreq){ sleep(10); sleeps++; @@ -243,5 +286,10 @@ threadmain(int argc, char **argv) msgclock = 0; } } + if(saveclock > 0){ + saveclock -= t; + if(saveclock <= 0) + flushram(); + } } } diff --git a/sys/src/games/nes/ppu.c b/sys/src/games/nes/ppu.c index 14926a2cf..fd788d561 100644 --- a/sys/src/games/nes/ppu.c +++ b/sys/src/games/nes/ppu.c @@ -269,10 +269,13 @@ flush(void) sysfatal("resize failed: %r"); p = divpt(addpt(screen->r.min, screen->r.max), 2); picr = (Rectangle){subpt(p, Pt(scale * 128, scale * h/2)), addpt(p, Pt(scale * 128, scale * h/2))}; - bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF); + if(bg->chan != screen->chan){ + freeimage(bg); + bg = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF); + } draw(screen, screen->r, bg, nil, ZP); } - if(tmp){ + if(screen->chan != tmp->chan || !rectinrect(picr, screen->r)){ loadimage(tmp, tmp->r, pic + oflag*8*256*4*scale*scale, 256*h*4*scale*scale); draw(screen, picr, tmp, nil, ZP); }else diff --git a/sys/src/games/nes/state.c b/sys/src/games/nes/state.c index 563492570..00d0450f4 100644 --- a/sys/src/games/nes/state.c +++ b/sys/src/games/nes/state.c @@ -92,6 +92,9 @@ loadstate(char *file) vrambuf = get8(); clock = get32(); ppuclock = get32(); + apuclock = get32(); + apuseq = get8(); + read(fd, apuctr, sizeof(apuctr)); mapper[map](RSTR, 0); close(fd); } @@ -128,6 +131,9 @@ savestate(char *file) put8(vrambuf); put32(clock); put32(ppuclock); + put32(apuclock); + put8(apuseq); + write(fd, apuctr, sizeof(apuctr)); mapper[map](SAVE, 0); close(fd); } |