]> git.lizzy.rs Git - plan9front.git/commitdiff
games/nes: basic audio support, battery backup, bug fixes
authoraiju <devnull@localhost>
Sun, 23 Feb 2014 20:46:16 +0000 (21:46 +0100)
committeraiju <devnull@localhost>
Sun, 23 Feb 2014 20:46:16 +0000 (21:46 +0100)
sys/src/games/nes/apu.c [new file with mode: 0644]
sys/src/games/nes/dat.h
sys/src/games/nes/fns.h
sys/src/games/nes/mem.c
sys/src/games/nes/mkfile
sys/src/games/nes/nes.c
sys/src/games/nes/ppu.c
sys/src/games/nes/state.c

diff --git a/sys/src/games/nes/apu.c b/sys/src/games/nes/apu.c
new file mode 100644 (file)
index 0000000..f347045
--- /dev/null
@@ -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,
+};
index 04e2c504254b2483dedea110216753de119c5bf6..3e56023e880081cbdff4c6eb2184a9cae9c88d2d 100644 (file)
@@ -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,
+};
index 7467f09918b2ddc22f8ab2e08d6b1395d1aebca9..0dab3df5fb61e5f8711d308214e1eceed34f2b9e 100644 (file)
@@ -9,3 +9,5 @@ void    savestate(char *);
 void   message(char *, ...);
 void   put8(u8int);
 int    get8(void);
+void   apustep(void);
+void   initaudio(void);
index 5b983ea32fa56e7f5f5ac4c95f026b40b9dbfd62..c7c8b15ad3379d76072f08c672add229aaac957e 100644 (file)
@@ -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;
index 8b34fa02f3a53b2cd16322924fd3a33f9c32630e..98c8a14e0e327484c86f893af0513f3c6aca6c40 100644 (file)
@@ -8,6 +8,7 @@ OFILES=\
        nes.$O\
        ppu.$O\
        state.$O\
+       apu.$O\
        
 HFILES=dat.h fns.h
 
index 15bee498f7f16adaa50319938c10396c6688affb..a50a4908a337a504129b7a6155352ca7be52bcc9 100644 (file)
@@ -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();
+               }
        }
 }
index 14926a2cfa103ca9d08b584e5479502e950acf36..fd788d56108e58c43783d7a7ded5498829683f3d 100644 (file)
@@ -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
index 563492570e51c2aefd105016d2ce2ce125d89ec3..00d0450f43a5e15084107f45f44bfc453bab94eb 100644 (file)
@@ -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);
 }