shithub: riscv

Download patch

ref: ad9047ab2cdbdbc5897d799fecacdda98f6cd707
parent: f82e3e8657a880cd0bbad9dd370da5069d8fdf04
author: aiju <devnull@localhost>
date: Sun Feb 23 16:46:16 EST 2014

games/nes: basic audio support, battery backup, bug fixes

--- /dev/null
+++ b/sys/src/games/nes/apu.c
@@ -1,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,
+};
--- a/sys/src/games/nes/dat.h
+++ b/sys/src/games/nes/dat.h
@@ -10,8 +10,10 @@
 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);
 
 enum {
@@ -29,6 +31,8 @@
 	PPUMASK = 0x2001,
 	PPUSTATUS = 0x2002,
 	PPUSCROLL = 0x2005,
+	APUSTATUS = 0x4015,
+	APUFRAME = 0x4017,
 
 	PPUNMI = 1<<7,
 	BIGSPRITE = 1<<5,
@@ -71,6 +75,8 @@
 	FREQ = 21477272,
 	MILLION = 1000000,
 	BILLION = 1000000000,
+	APUDIV = 89490,
+	SAVEFREQ = FREQ/5,
 };
 
 enum {
@@ -86,4 +92,10 @@
 	SAVE = -2,
 	RSTR = -3,
 	SCAN = -4,
+};
+
+enum {
+	IRQFRAME = 1,
+	IRQDMC = 2,
+	IRQMMC = 4,
 };
--- a/sys/src/games/nes/fns.h
+++ b/sys/src/games/nes/fns.h
@@ -9,3 +9,5 @@
 void	message(char *, ...);
 void	put8(u8int);
 int	get8(void);
+void	apustep(void);
+void	initaudio(void);
--- a/sys/src/games/nes/mem.c
+++ b/sys/src/games/nes/mem.c
@@ -158,7 +158,7 @@
 			else
 				n--;
 			if(n == 0 && en)
-				irq |= 2;
+				irq |= IRQMMC;
 			return;
 		case SAVE:
 			put8(m);
@@ -204,7 +204,7 @@
 		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 @@
 memread(u16int p)
 {
 	u8int v;
+	int i;
 
 	if(p < 0x2000){
 		p &= 0x7FF;
@@ -305,6 +306,16 @@
 			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 @@
 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 @@
 			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;
--- a/sys/src/games/nes/mkfile
+++ b/sys/src/games/nes/mkfile
@@ -8,6 +8,7 @@
 	nes.$O\
 	ppu.$O\
 	state.$O\
+	apu.$O\
 	
 HFILES=dat.h fns.h
 
--- a/sys/src/games/nes/nes.c
+++ b/sys/src/games/nes/nes.c
@@ -13,9 +13,9 @@
 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 @@
 }
 
 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 @@
 		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 @@
 		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 @@
 		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 @@
 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 @@
 		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 @@
 	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,6 +249,7 @@
 		t = step() * 12;
 		clock += t;
 		ppuclock += t;
+		apuclock += t;
 		syncclock += t;
 		checkclock += t;
 		while(ppuclock >= 4){
@@ -217,6 +256,10 @@
 			ppustep();
 			ppuclock -= 4;
 		}
+		if(apuclock >= APUDIV){
+			apustep();
+			apuclock -= APUDIV;
+		}
 		if(syncclock >= syncfreq){
 			sleep(10);
 			sleeps++;
@@ -242,6 +285,11 @@
 				draw(screen, screen->r, bg, nil, ZP);
 				msgclock = 0;
 			}
+		}
+		if(saveclock > 0){
+			saveclock -= t;
+			if(saveclock <= 0)
+				flushram();
 		}
 	}
 }
--- a/sys/src/games/nes/ppu.c
+++ b/sys/src/games/nes/ppu.c
@@ -269,10 +269,13 @@
 			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
--- a/sys/src/games/nes/state.c
+++ b/sys/src/games/nes/state.c
@@ -92,6 +92,9 @@
 	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 @@
 	put8(vrambuf);
 	put32(clock);
 	put32(ppuclock);
+	put32(apuclock);
+	put8(apuseq);
+	write(fd, apuctr, sizeof(apuctr));
 	mapper[map](SAVE, 0);
 	close(fd);
 }