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);