shithub: pplay

Download patch

ref: a10e87d9ff26a01039f9d92e62b0650881244a84
parent: fd7be1f5cae97cba175519e97fa5b0b60b93ea70
author: qwx <qwx@sciops.net>
date: Tue Feb 4 15:35:30 EST 2020

add -f: don't read whole file into memory at startup

also many changes after 9001 reimplementations:
- setpan: don't multiply pan offset
- add _,= for zooming by factors of two
- compute waveform by increments instead of all at once
- minor simplifications
- update: don't try to limit drawing until we find a better heuristic

--- a/dat.h
+++ b/dat.h
@@ -1,5 +1,14 @@
-extern ulong nbuf;
-extern uchar *buf, *bufp, *bufe, *viewp, *viewe, *viewmax, *loops, *loope;
-extern int T;
-extern int stereo;
+enum{
+	Ndelay = 44100 / 25,
+	Nchunk = Ndelay * 4,
+	Nreadsz = 4*1024*1024,
+};
+
+extern int ifd;
+extern uchar *pcmbuf;
+extern vlong filesz, nsamp;
+
+extern vlong seekp, T, loops, loope;
+
+extern int file, stereo;
 extern int zoom;
--- a/draw.c
+++ b/draw.c
@@ -5,7 +5,6 @@
 #include "fns.h"
 
 enum{
-	Cbg,
 	Csamp,
 	Cline,
 	Cloop,
@@ -15,7 +14,12 @@
 static Image *viewbg, *view;
 static Rectangle liner;
 static Point statp;
+static vlong views, viewe, viewmax, bgofs;
+static int bgscalyl, bgscalyr, bgscalf;
+static uchar bgbuf[Nchunk * 200];
 
+static void (*drawbg)(void);
+
 static Image *
 eallocimage(Rectangle r, int repl, ulong col)
 {
@@ -27,42 +31,42 @@
 }
 
 static void
-drawstat(void)
-{
-	char s[64];
-
-	snprint(s, sizeof s, "T %d p %zd", T, bufp-buf);
-	string(screen, statp, col[Cline], ZP, font, s);
-}
-
-static void
 drawsamps(void)
 {
-	int x, yl, yr, w, scal, lmin, lmax, rmin, rmax;
-	short s;
-	uchar *p, *e;
+	int x, n, lmin, lmax, rmin, rmax;
+	s16int s;
+	uchar *p, *e, *et;
 	Rectangle l, r;
 
-	w = T * 4;
-	p = viewp;
-	x = 0;
-	yl = viewbg->r.max.y / (stereo ? 4 : 2);
-	yr = viewbg->r.max.y - yl;
-	scal = 32767 / yl;
-	while(p < viewe){
-		e = p + w;
-		if(e > viewe)
-			e = viewe;
+	if(bgofs >= viewe)
+		return;
+	if(!file){
+		p = pcmbuf + bgofs;
+		n = viewe - bgofs < sizeof bgbuf ? viewe - bgofs : sizeof bgbuf;
+	}else{
+		seek(ifd, bgofs, 0);
+		n = read(ifd, bgbuf, sizeof bgbuf);
+		seek(ifd, seekp, 0);
+		p = bgbuf;
+	}
+	e = p + n;
+	x = (bgofs - views) / T;
+	while(p < e){
+		n = filesz - bgofs < T ? filesz - bgofs : T;
+		if(n > e - p)
+			n -= n - (e - p);
+		bgofs += n;
+		et = p + n;
 		lmin = lmax = 0;
 		rmin = rmax = 0;
-		while(p < e){
-			s = (short)(p[1] << 8 | p[0]);
+		while(p < et){
+			s = (s16int)(p[1] << 8 | p[0]);
 			if(s < lmin)
 				lmin = s;
 			else if(s > lmax)
 				lmax = s;
 			if(stereo){
-				s = (short)(p[3] << 8 | p[2]);
+				s = (s16int)(p[3] << 8 | p[2]);
 				if(s < rmin)
 					rmin = s;
 				else if(s > rmax)
@@ -70,34 +74,27 @@
 			}
 			p += 4;
 		}
-		l = Rect(x, yl - lmax / scal, x+1, yl - lmin / scal);
+		l = Rect(x, bgscalyl - lmax / bgscalf,
+			x+1, bgscalyl - lmin / bgscalf);
+		r = Rect(x, bgscalyr - rmax / bgscalf,
+			x+1, bgscalyr - rmin / bgscalf);
 		draw(viewbg, l, col[Csamp], nil, ZP);
-		if(stereo){
-			r = Rect(x, yr - rmax / scal, x+1, yr - rmin / scal);
+		if(stereo)
 			draw(viewbg, r, col[Csamp], nil, ZP);
-		}
 		x++;
 	}
 }
 
-void
-update(void)
+static void
+drawstat(void)
 {
-	int x;
+	char s[64];
 
-	x = screen->r.min.x + (bufp - viewp) / 4 / T;
-	if(liner.min.x == x || bufp < viewp && x > liner.min.x)
-		return;
-	draw(screen, screen->r, view, nil, ZP);
-	liner.min.x = x;
-	liner.max.x = x + 1;
-	if(bufp >= viewp)
-		draw(screen, liner, col[Cline], nil, ZP);
-	drawstat();
-	flushimage(display, 1);
+	snprint(s, sizeof s, "T %lld p %lld", T/4, seekp);
+	string(screen, statp, col[Cline], ZP, font, s);
 }
 
-void
+static void
 drawview(void)
 {
 	int x;
@@ -104,46 +101,106 @@
 	Rectangle r;
 
 	draw(view, view->r, viewbg, nil, ZP);
-	if(loops != buf && loops >= viewp){
-		x = (loops - viewp) / 4 / T;
+	if(loops != 0 && loops >= views){
+		x = (loops - views) / T;
 		r = view->r;
 		r.min.x += x;
 		r.max.x = r.min.x + 1;
 		draw(view, r, col[Cloop], nil, ZP);
 	}
-	if(loope != bufe && loope >= viewp){
-		x = (loope - viewp) / 4 / T;
+	if(loope != filesz && loope >= views){
+		x = (loope - views) / T;
 		r = view->r;
 		r.min.x += x;
 		r.max.x = r.min.x + 1;
 		draw(view, r, col[Cloop], nil, ZP);
 	}
+}
+
+void
+update(void)
+{
+	int x;
+
+	drawbg();
+	drawview();
+	x = screen->r.min.x + (seekp - views) / T;
+	//if(liner.min.x == x || seekp < views && x > liner.min.x)
+	//	return;
 	draw(screen, screen->r, view, nil, ZP);
-	draw(screen, liner, col[Cline], nil, ZP);
+	liner.min.x = x;
+	liner.max.x = x + 1;
+	if(seekp >= views)
+		draw(screen, liner, col[Cline], nil, ZP);
 	drawstat();
 	flushimage(display, 1);
 }
 
 void
-redrawbg(void)
+setzoom(int Δz, int pow)
 {
-	int w, x;
+	int z;
+
+	if(!pow)
+		z = zoom + Δz;
+	else if(Δz < 0)
+		z = zoom >> -Δz;
+	else
+		z = zoom << Δz;
+	if(z < 1 || z > nsamp / Dx(screen->r))
+		return;
+	zoom = z;
+	redraw();
+}
+
+void
+setpan(int Δx)
+{
+	Δx *= T;
+	if(zoom == 1 || views == 0 && Δx < 0 || views >= viewmax && Δx > 0)
+		return;
+	views += Δx;
+	redraw();
+}
+
+void
+setloop(vlong ofs)
+{
+	ofs *= T;
+	ofs += views;
+	if(ofs < 0 || ofs > filesz)
+		return;
+	if(ofs < seekp)
+		loops = ofs;
+	else
+		loope = ofs;
+	update();
+}
+
+void
+setpos(vlong ofs)
+{
+	if(ofs < loops || ofs > loope - Nchunk)
+		return;
+	seekp = ofs;
+	if(file)
+		seek(ifd, ofs, 0);
+	update();
+}
+
+void
+setofs(vlong ofs)
+{
+	setpos(views + ofs * T);
+}
+
+static void
+resetdraw(void)
+{
+	int x;
 	Rectangle viewr, midr;
 
-	T = nbuf / zoom / Dx(screen->r);
-	if(T == 0)
-		T = 1;
-	w = Dx(screen->r) * T * 4;
-	viewmax = bufe - w;
-	if(viewp < buf)
-		viewp = buf;
-	else if(viewp > viewmax)
-		viewp = viewmax;
-	viewe = viewp + w;
-	x = screen->r.min.x + (bufp - viewp) / 4 / T;
-	liner = screen->r;
-	liner.min.x = x;
-	liner.max.x = x + 1;
+	x = screen->r.min.x + (seekp - views) / T;
 	viewr = rectsubpt(screen->r, screen->r.min);
 	freeimage(viewbg);
 	freeimage(view);
@@ -158,18 +215,43 @@
 			screen->r.min.y + (Dy(screen->r) - font->height) / 2 + 1);
 	}else
 		statp = Pt(screen->r.min.x, screen->r.max.y - font->height);
-	drawsamps();
-	drawview();
+	liner = screen->r;
+	liner.min.x = x;
+	liner.max.x = x + 1;
+	bgscalyl = viewbg->r.max.y / (stereo ? 4 : 2);
+	bgscalyr = viewbg->r.max.y - bgscalyl;
+	bgscalf = 32767 / bgscalyl;
 }
 
 void
-initview(void)
+redraw(void)
 {
+	vlong span;
+
+	T = filesz / zoom / Dx(screen->r) & ~3;
+	if(T == 0)
+		T = 4;
+	span = Dx(screen->r) * T;
+	viewmax = filesz - span;
+	if(views < 0)
+		views = 0;
+	else if(views > viewmax)
+		views = viewmax;
+	viewe = views + span;
+	bgofs = views;
+	resetdraw();
+	update();
+}
+
+void
+initdrw(void)
+{
 	if(initdraw(nil, nil, "pplay") < 0)
 		sysfatal("initdraw: %r");
-	col[Cbg] = display->black;
 	col[Csamp] = eallocimage(Rect(0,0,1,1), 1, 0x440000FF);
 	col[Cline] = eallocimage(Rect(0,0,1,1), 1, 0x884400FF);
 	col[Cloop] = eallocimage(Rect(0,0,1,1), 1, 0x777777FF);
-	redrawbg();
+	drawbg = drawsamps;
+	loope = filesz;
+	redraw();
 }
--- a/edit.c
+++ b/edit.c
@@ -7,6 +7,7 @@
 writepcm(char *path)
 {
 	int n, fd, sz;
+	vlong ofs;
 	uchar *p;
 
 	if((fd = create(path, OWRITE, 0664)) < 0){
@@ -15,8 +16,15 @@
 	}
 	if((sz = iounit(fd)) == 0)
 		sz = 8192;
-	for(p=loops; p<loope; p+=sz){
-		n = loope - p < sz ? loope - p : sz;
+	if(file)
+		seek(ifd, loops, 0);
+	p = pcmbuf;
+	for(ofs=loops; ofs<loope; ofs+=sz){
+		n = loope - ofs < sz ? loope - ofs : sz;
+		if(!file)
+			p = pcmbuf + ofs;
+		else if(read(ifd, p, n) != n)
+			fprint(2, "read: %r\n");
 		if(write(fd, p, n) != n){
 			fprint(2, "write: %r\n");
 			break;
@@ -23,4 +31,6 @@
 		}
 	}
 	close(fd);
+	if(file)
+		seek(ifd, seekp, 0);
 }
--- a/fns.h
+++ b/fns.h
@@ -1,5 +1,10 @@
 void	writepcm(char*);
 void	update(void);
-void	drawview(void);
-void	redrawbg(void);
-void	initview(void);
+void	setzoom(int, int);
+void	setpan(int);
+void	setloop(vlong);
+void	setpos(vlong);
+void	setofs(vlong);
+void	redraw(void);
+void	initdrw(void);
+void*	emalloc(ulong);
--- a/man/1/pplay
+++ b/man/1/pplay
@@ -4,9 +4,9 @@
 .SH SYNOPSIS
 .B pplay
 [
-.B -cs
+.B -cfs
 ] [
-.B pcm file
+.B pcmfile
 ]
 .SH DESCRIPTION
 .I Pplay
@@ -15,7 +15,10 @@
 .IR audio (3)).
 .PP
 At startup, the program loads the audio data in its entirety into memory,
-either from the file provided as argument, or from standard in.
+unless the
+.B -f
+option is given, in which case the input file must be seekable.
+Data is read either from the file provided as argument, or from standard in.
 By default,
 .I pplay
 writes audio data back to
@@ -51,6 +54,11 @@
 keys, and reset with the
 .L z
 key.
+The
+.L _
+and
+.L =
+keys zoom in by greater increments.
 The view can then be moved by holding the right mouse button,
 and moving the cursor horizontally.
 .PP
@@ -69,11 +77,20 @@
 .EX
 % audio/mp3dec <file.mp3 | pplay
 .EE
+.PP
+Use
+.IR play (1)
+to decode any known audio format:
+.IP
+.EX
+% play -o /fd/1 file | pplay
+.EE
 .SH "SEE ALSO"
 .IR audio (1),
+.IR play (1),
 .IR audio (3)
 .SH HISTORY
 .I Pplay
 first appeared in 9front (October, 2017).
 .SH BUGS
-Most mouse actions redraw the entire plot and may interrupt audio playback for too long.
+Most mouse actions redraw the entire plot.
--- a/mkfile
+++ b/mkfile
@@ -6,5 +6,5 @@
 	edit.$O\
 	pplay.$O\
 
-HFILES=
+HFILES=dat.h fns.h
 </sys/src/cmd/mkone
--- a/pplay.c
+++ b/pplay.c
@@ -7,18 +7,12 @@
 #include "dat.h"
 #include "fns.h"
 
-enum{
-	Ndelay = 44100 / 25,
-	Nchunk = Ndelay * 4,
-	Nreadsz = 4*1024*1024,
-};
+int ifd;
+uchar *pcmbuf;
+vlong filesz, nsamp;
+int file, stereo, zoom = 1;
+vlong seekp, T, loops, loope;
 
-ulong nbuf;
-uchar *buf, *bufp, *bufe, *viewp, *viewe, *viewmax, *loops, *loope;
-int T;
-int stereo;
-int zoom = 1;
-
 static Keyboardctl *kc;
 static Mousectl *mc;
 static QLock lck;
@@ -25,21 +19,40 @@
 static int pause;
 static int cat;
 
+void *
+emalloc(ulong n)
+{
+	void *p;
+
+	if((p = mallocz(n, 1)) == nil)
+		sysfatal("emalloc: %r");
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
 static void
 athread(void *)
 {
 	int n, fd;
+	uchar *p;
 
 	if((fd = cat ? 1 : open("/dev/audio", OWRITE)) < 0)
 		sysfatal("open: %r");
+	p = pcmbuf;
 	for(;;){
 		qlock(&lck);
-		n = bufp + Nchunk >= loope ? loope - bufp : Nchunk;
-		if(write(fd, bufp, n) != n)
+		n = seekp + Nchunk >= loope ? loope - seekp : Nchunk;
+		if(!file)
+			p = pcmbuf + seekp;
+		else if(read(ifd, pcmbuf, n) != n)
+			fprint(2, "read: %r\n");
+		if(write(fd, p, n) != n){
+			fprint(2, "athread: %r\n");
 			break;
-		bufp += n;
-		if(bufp >= loope)
-			bufp = loops;
+		}
+		seekp += n;
+		if(seekp >= loope)
+			setpos(loops);
 		update();
 		qunlock(&lck);
 		yield();
@@ -65,69 +78,10 @@
 }
 
 static void
-setzoom(int n)
+reallocbuf(ulong n)
 {
-	int m;
-
-	m = zoom + n;
-	if(m < 1 || m > nbuf / Dx(screen->r))
-		return;
-	zoom = m;
-	if(nbuf / zoom / Dx(screen->r) != T)
-		redrawbg();
-}
-
-static void
-setpan(int n)
-{
-	n *= T * 4 * 16;
-	if(zoom == 1 || viewp == buf && n < 0 || viewp == viewmax && n > 0)
-		return;
-	viewp += n;
-	redrawbg();
-}
-
-static void
-setloop(void)
-{
-	int n;
-	uchar *p;
-
-	n = (mc->xy.x - screen->r.min.x) * T * 4;
-	p = viewp + n;
-	if(p < buf || p > bufe)
-		return;
-	if(p < bufp)
-		loops = p;
-	else
-		loope = p;
-	drawview();
-}
-
-static void
-setpos(void)
-{
-	int n;
-	uchar *p;
-
-	n = (mc->xy.x - screen->r.min.x) * T * 4;
-	p = viewp + n;
-	if(p < loops || p > loope - Nchunk)
-		return;
-	bufp = p;
-	update();
-}
-
-static void
-bufrealloc(ulong n)
-{
-	int off;
-
-	off = bufp - buf;
-	if((buf = realloc(buf, n)) == nil)
+	if((pcmbuf = realloc(pcmbuf, n)) == nil)
 		sysfatal("realloc: %r");
-	bufe = buf + n;
-	bufp = buf + off;
 }
 
 static void
@@ -134,24 +88,39 @@
 initbuf(int fd)
 {
 	int n, sz;
+	vlong ofs;
+	Dir *d;
 
-	bufrealloc(nbuf += Nreadsz);
+	reallocbuf(filesz += Nreadsz);
+	ifd = fd;
+	if(file){
+		if((d = dirfstat(fd)) == nil)
+			sysfatal("dirfstat: %r");
+		filesz = d->length;
+		nsamp = filesz / 4;
+		free(d);
+		return;
+	}
 	if((sz = iounit(fd)) == 0)
 		sz = 8192;
-	while((n = read(fd, bufp, sz)) > 0){
-		bufp += n;
-		if(bufp + sz >= bufe)
-			bufrealloc(nbuf += Nreadsz);
+	ofs = 0;
+	while((n = read(fd, pcmbuf+ofs, sz)) > 0){
+		ofs += n;
+		if(ofs + sz >= filesz)
+			reallocbuf(filesz += Nreadsz);
 	}
 	if(n < 0)
 		sysfatal("read: %r");
-	bufrealloc(nbuf = bufp - buf);
+	filesz = ofs;
+	reallocbuf(filesz);
+	nsamp = filesz / 4;
+	close(fd);
 }
 
 static void
 usage(void)
 {
-	fprint(2, "usage: %s [-cs] [pcm]\n", argv0);
+	fprint(2, "usage: %s [-cfs] [pcm]\n", argv0);
 	threadexits("usage");
 }
 
@@ -165,6 +134,7 @@
 
 	ARGBEGIN{
 	case 'c': cat = 1; break;
+	case 'f': file = 1; break;
 	case 's': stereo = 1; break;
 	default: usage();
 	}ARGEND
@@ -171,13 +141,7 @@
 	if((fd = *argv != nil ? open(*argv, OREAD) : 0) < 0)
 		sysfatal("open: %r");
 	initbuf(fd);
-	close(fd);
-	nbuf /= 4;
-	bufp = buf;
-	viewp = buf;
-	loops = buf;
-	loope = bufe;
-	initview();
+	initdrw();
 	if((kc = initkeyboard(nil)) == nil)
 		sysfatal("initkeyboard: %r");
 	if((mc = initmouse(nil, screen)) == nil)
@@ -195,35 +159,31 @@
 		case 0:
 			if(getwindow(display, Refnone) < 0)
 				sysfatal("resize failed: %r");
-			redrawbg();
 			mo = mc->Mouse;
+			redraw();
 			break;
 		case 1:
 			switch(mc->buttons){
-			case 1: setpos(); break;
-			case 2: setloop(); break;
+			case 1: setofs(mc->xy.x - screen->r.min.x); break;
+			case 2: setloop(mc->xy.x - screen->r.min.x); break;
 			case 4: setpan(mo.xy.x - mc->xy.x); break;
-			case 8: setzoom(1); break;
-			case 16: setzoom(-1); break;
+			case 8: setzoom(1, 1); break;
+			case 16: setzoom(-1, 1); break;
 			}
 			mo = mc->Mouse;
 			break;
 		case 2:
 			switch(r){
-			case ' ':
-				if(pause ^= 1)
-					qlock(&lck);
-				else
-					qunlock(&lck);
-				break;
-			case 'b': bufp = loops; update(); break;
-			case 'r': loops = buf; loope = bufe; drawview(); break;
+			case ' ': ((pause ^= 1) ? qlock : qunlock)(&lck); break;
+			case 'b': setpos(loops); break;
+			case 'r': loops = 0; loope = filesz; update(); break;
 			case Kdel:
 			case 'q': threadexitsall(nil);
-			case 'z': if(zoom == 1) break; zoom = 1; redrawbg(); break;
-			case '-': setzoom(-1); break;
-			case '=':
-			case '+': setzoom(1); break;
+			case 'z': setzoom(-zoom + 1, 0); break;
+			case '-': setzoom(-1, 0); break;
+			case '=': setzoom(1, 0); break;
+			case '_': setzoom(-1, 1); break;
+			case '+': setzoom(1, 1); break;
 			case 'w':
 				if((p = prompt()) != nil)
 					writepcm(p);