shithub: pplay

Download patch

ref: 8f75ccd8bca408e6f080f774658ec23ac4839838
parent: 9714f4b05d5759db2086edc34044b48305c7087e
author: qwx <[email protected]>
date: Mon Nov 14 15:52:48 EST 2022

experimental chunked buffers to allow editing (itself disabled for testing)

- keep track of a `dot', either a point position or a range; ranges are
set with the loop bounds mouse commands; any operation acts on the dot
- interaction is through an interface similar to paint: start typing, then
the first letter is the command, the rest are arguments; things like | are
passed to rc -c, so things like selecting a range and issuing:
	| norm -f 5 >buf.pcm
works just fine
- with copy/cut and others, a range across multiple chunks is merged into
one prior to any handling
- a `hold' chunk is used as a temporary scratch buffer for cut/copy/paste
- audio thread and other functions ask for a pointer to data and provide
a buffer, which may be unused if the data range is small not across chunks
- split input file straightaway into ≈ 1mb chunks to avoid huge ops later
- implemented but temporarily disabled: copy/cut/paste
- naive implementation, but i prefer this to a more complicated one with
a bunch of state to maintain: this just has positions in bytes; to look up
chunks etc, we scan the list of chunks left to right. this should be
improved for large files, but the rest is nice
- remove -f: it makes everything complicated and there is no point; if the
file is too big, just split it up manually
- normalize status string
- fix locking issue in drawsamp

--- a/cmd.c
+++ b/cmd.c
@@ -4,45 +4,357 @@
 #include "dat.h"
 #include "fns.h"
 
+/* stupidest implementation with the least amount of state to keep track of */
+
+Dot dot;
+usize totalsz;
+static Chunk norris = {.left = &norris, .right = &norris};
+static Chunk *held;
+static uchar plentyofroom[Iochunksz];
+static int cutheld;
+static int epfd[2];
+
+static Chunk *
+newchunk(usize n)
+{
+	Chunk *c;
+
+	assert((n & 3) == 0);
+	c = emalloc(sizeof *c);
+	c->bufsz = n;
+	c->buf = emalloc(c->bufsz);
+	return c;
+}
+
+static Chunk *
+clonechunk(void)
+{
+	Chunk *c;
+
+	assert(held != nil);
+	c = newchunk(held->bufsz);
+	memcpy(c->buf, held->buf, c->bufsz);
+	return c;
+}
+
+static void
+freechunk(Chunk *c)
+{
+	if(c == nil)
+		return;
+	free(c->buf);
+	free(c);
+}
+
+static void
+linkchunk(Chunk *left, Chunk *c)
+{
+	c->right = left->right;
+	c->left = left;
+	c->right->left = c;
+	left->right = c;
+}
+
+static void
+unlinkchunk(Chunk *c)
+{
+	c->left->right = c->right;
+	c->right->left = c->left;
+	c->left = c->right = nil;
+}
+
+/* stupidest possible approach for now: minimal bookkeeping */
+static Chunk *
+p2c(usize p, usize *off)
+{
+	Chunk *c;
+
+	c = norris.right;
+	while(p > c->bufsz){
+		p -= c->bufsz;
+		c = c->right;
+	}
+	if(off != nil)
+		*off = p;
+	assert(c != &norris);
+	return c;
+}
+static usize
+c2p(Chunk *tc)
+{
+	Chunk *c;
+	usize p;
+
+	p = 0;
+	c = norris.right;
+	while(c != tc){
+		p += c->bufsz;
+		c = c->right;
+	}
+	return p;
+}
+
+void
+setrange(usize from, usize to)
+{
+	dot.from.pos = from;
+	dot.to.pos = to;
+}
+
+int
+setpos(usize off)
+{
+	if(off < dot.from.pos || off > dot.to.pos){
+		werrstr("cannot jump outside of loop bounds\n");
+		return -1;
+	}
+	setrange(0, totalsz);
+	dot.pos = off;
+	return 0;
+}
+
+void
+jump(usize off)
+{
+	dot.pos = off;
+}
+
 static int
-writediskbuf(int fd)
+holdchunk(int cut)
 {
-	int n, m, iosz;
+	if(held != nil){
+		if(held == p2c(dot.pos, nil))
+			return 0;
+		else if(cutheld){
+			unlinkchunk(held);
+			freechunk(held);
+		}
+	}
+	held = p2c(dot.from.pos, nil);
+	cutheld = cut;
+	if(cut){
+		setpos(dot.from.pos);
+		unlinkchunk(held);
+	}
+	return 0;
+}
 
-	seek(ifd, loops, 0);
-	if((iosz = iounit(fd)) == 0)
-		iosz = 8192;
-	for(m=loope-loops; m>0;){
-		n = m < iosz ? m : iosz;
-		if(read(ifd, pcmbuf, n) != n)
-			return -1;
-		if(write(fd, pcmbuf, n) != n)
-			return -1;
-		m -= n;
+static Chunk *
+merge(Chunk *left, Chunk *right)
+{
+	if(left->buf == nil || right->buf == nil){
+		werrstr("can\'t merge self into void");
+		return nil;
 	}
-	seek(ifd, seekp, 0);
+	if(left->buf != right->buf){
+		left->buf = erealloc(left->buf, left->bufsz + right->bufsz, left->bufsz);
+		memmove(left->buf + left->bufsz, right->buf, right->bufsz);
+	}else
+		right->buf = nil;
+	left->bufsz += right->bufsz;
+	unlinkchunk(right);
+	freechunk(right);
 	return 0;
 }
 
+/* does not modify dot range; merges range then splits
+ * at left and right offset */
+static void
+split(void)
+{
+	usize n, p, Δ;
+	Chunk *c, *from, *to, *tc;
+
+	from = p2c(dot.pos, &p);
+	to = p2c(dot.to.pos, nil);
+	if(from != to){
+		for(p=dot.pos; p<dot.to.pos; p+=n){
+			tc = from->right;
+			assert(tc != &norris);
+			n = tc->bufsz;
+			from = merge(from, tc);
+		}
+	}
+	if(p < dot.from.pos){
+		Δ = dot.from.pos - p;
+		c = newchunk(Δ);
+		memcpy(c->buf, from->buf, Δ);
+		linkchunk(from->left, c);
+	}
+	tc = p2c(dot.to.pos, &p);
+	p += tc->bufsz;
+	if(dot.to.pos < p){
+		Δ = p - dot.to.pos;
+		c = newchunk(Δ);
+		memcpy(c->buf, tc->buf + dot.to.pos, Δ);
+		linkchunk(from, c);
+	}
+}
+
+uchar *
+getbuf(Dot d, usize n, uchar *scratch, usize *boff)
+{
+	uchar *bp, *p;
+	usize Δbuf, Δloop, m, off, Δ;
+	Chunk *c;
+
+	c = p2c(d.pos, &off);
+	p = c->buf + off;
+	m = n;
+	bp = scratch;
+	while(m > 0){
+		Δloop = d.to.pos - d.pos;
+		Δbuf = c->bufsz - off;
+		if(m < Δloop && m < Δbuf){
+			Δ = m;
+			memcpy(bp, p, Δ);
+			d.pos += Δ;
+		}else if(Δloop <= Δbuf){
+			Δ = Δloop;
+			memcpy(bp, p, Δ);
+			d.pos = d.from.pos;
+			c = p2c(d.from.pos, nil);
+			off = 0;
+			p = c->buf;
+		}else{
+			if(c == &norris)
+				c = c->right;
+			Δ = Δbuf;
+			memcpy(bp, p, Δ);
+			d.pos += Δ;
+			c = c->right;
+			off = 0; 
+			p = c->buf;
+		}
+		bp += Δ;
+		m -= Δ;
+	}
+	*boff = n;
+	return scratch;
+}
+
+void
+advance(Dot *d, usize n)
+{
+	usize Δ, Δbuf, Δloop, m, off;
+	Chunk *c;
+
+	c = p2c(d->pos, &off);
+	m = n;
+	while(m > 0){
+		Δloop = d->to.pos - d->pos;
+		Δbuf = c->bufsz - off;
+		if(m < Δloop && m < Δbuf){
+			d->pos += m;
+			break;
+		}else if(Δloop < Δbuf){
+			Δ = Δloop;
+			d->pos = d->from.pos;
+			c = p2c(d->from.pos, nil);
+			off = 0;
+		}else{
+			Δ = Δbuf;
+			d->pos += Δ;
+			c = c->right;
+			off = 0;
+		}
+		m -= Δ;
+	}
+}
+
 static int
+paste(char *)
+{
+	Chunk *c, *l, *dotc;
+
+	c = clonechunk();
+	if(dot.from.pos == dot.to.pos){		/* insert */
+		linkchunk(p2c(dot.to.pos, nil), c);
+		setrange(dot.to.pos, dot.to.pos + c->bufsz);
+		totalsz += c->bufsz;
+	}else{						/* replace */
+		dotc = p2c(dot.pos, nil);
+		l = dotc->left;
+		totalsz -= dotc->bufsz;
+		unlinkchunk(dotc);
+		freechunk(dotc);
+		linkchunk(l, c);
+		setrange(dot.from.pos, dot.from.pos + c->bufsz);
+		totalsz += c->bufsz;
+	}
+	return 0;
+}
+
+static int
+copy(char *)
+{
+	split();
+	holdchunk(0);
+	return 0;
+}
+
+static int
+cut(char *)
+{
+	split();
+	totalsz -= p2c(dot.pos, nil)->bufsz;
+	holdchunk(1);
+	return 0;
+}
+
+static Chunk *
+readintochunks(int fd)
+{
+	int n;
+	usize off;
+	Chunk *c, *nc;
+
+	c = newchunk(Iochunksz);
+	linkchunk(&norris, c);
+	for(off=0;; off+=n){
+		if(off == Iochunksz){
+			totalsz += Iochunksz;
+			nc = newchunk(Iochunksz);
+			linkchunk(c, nc);
+			c = nc;
+			off = 0;
+		}
+		if((n = read(fd, c->buf+off, Ioreadsz)) <= 0)
+			break;
+	}
+	close(fd);
+	if(n < 0)
+		fprint(2, "readintochunks: %r\n");
+	c->buf = erealloc(c->buf, off, c->bufsz);
+	c->bufsz = off;
+	totalsz += c->bufsz;
+	return norris.right;
+}
+
+static int
 writebuf(int fd)
 {
-	int n, iosz;
-	uchar *p, *e;
+	usize n, m;
+	uchar *p;
+	Dot d;
 
-	if((iosz = iounit(fd)) == 0)
-		iosz = 8192;
-	for(p=pcmbuf+loops, e=pcmbuf+loope; p<e;){
-		n = e - p < iosz ? e - p : iosz;
-		if(write(fd, p, n) != n)
+	d.pos = d.from.pos = dot.from.pos;
+	d.to.pos = dot.to.pos;
+	for(m=d.to.pos-d.from.pos; m>0;){
+		n = sizeof plentyofroom < m ? sizeof plentyofroom : m;
+		if((p = getbuf(d, n, plentyofroom, &n)) == nil){
+			fprint(2, "writebuf: getbuf won't feed\n");
 			return -1;
-		p += n;
+		}
+		if((n = write(fd, p, m)) != n){
+			fprint(2, "writebuf: short write not %zd\n", n);
+			return -1;
+		}
+		m -= n;
 	}
 	return 0;
 }
 
-static int epfd[2];
-
 static void
 rc(void *s)
 {
@@ -74,7 +386,7 @@
 {
 	int r, fd;
 
-	if(loope - loops == 0){
+	if(dot.to.pos - dot.from.pos == 0){
 		werrstr("writeto: dot isn't a range");
 		return -1;
 	}
@@ -82,7 +394,7 @@
 		werrstr("writeto: %r");
 		return -1;
 	}
-	r = file ? writediskbuf(fd) : writebuf(fd);
+	r = writebuf(fd);
 	close(fd);
 	return r;
 }
@@ -107,18 +419,27 @@
 		s += n;
 	}
 	switch(r){
-//	case 'd': return delete(s);
-//	case 'c': return cut(s);
-//	case 'p': return paste(s);
-//	case 'x': return crop(s);
+//	case '<': return pipefrom(s);
 //	case '^': return exchange(s);
 	case '|': return pipeto(s);
-//	case '<': return pipefrom(s);
-	case 'w': return writeto(s);
+//	case 'c': return copy(s);
+//	case 'd': return cut(s);
+//	case 'p': return paste(s);
 //	case 'r': return readfrom(s);
+	case 'w': return writeto(s);
+//	case 'x': return crop(s);
 	default: werrstr("unknown command %C", r); break;
 	}
 	return -1;
+}
+
+int
+loadin(int fd)
+{
+	if(readintochunks(fd) == nil)
+		sysfatal("loadin: %r");
+	setrange(0, totalsz);
+	return 0;
 }
 
 static void
--- a/dat.h
+++ b/dat.h
@@ -1,14 +1,35 @@
+typedef struct Chunk Chunk;
+typedef struct Pos Pos;
+typedef struct Dot Dot;
+
 enum{
-	Ndelay = 44100 / 25,
-	Nchunk = Ndelay * 4,
-	Nreadsz = 4*1024*1024,
+	Rate = 44100,
+	WriteRate = 25,
+	WriteDelay = Rate / WriteRate,	/* 1764 default delay */
+	Sampsz = 2 * 2,
+	Outsz = WriteDelay * Sampsz,
+	Iochunksz = 1*1024*1024,	/* ≈ 6 sec. at 44.1 kHz */
+	Ioreadsz = Iochunksz / 32,
 };
+struct Chunk{
+	uchar *buf;
+	usize bufsz;
+	Chunk *left;
+	Chunk *right;
+};
+struct Pos{
+	usize pos;	/* bytes */
+};
+extern struct Dot{
+	Pos;
+	Pos from;
+	Pos to;
+};
+extern Dot dot;
+extern usize totalsz;
 
-extern int ifd;
-extern uchar *pcmbuf;
-extern vlong filesz, nsamp;
-
-extern vlong seekp, T, loops, loope;
-
-extern int file, stereo;
+extern int stereo;
 extern int zoom;
+
+#define MIN(x,y)	((x) < (y) ? (x) : (y))
+#define MAX(x,y)	((x) > (y) ? (x) : (y))
--- a/draw.c
+++ b/draw.c
@@ -15,9 +15,11 @@
 static Image *viewbg, *view;
 static Rectangle liner;
 static Point statp;
-static vlong views, viewe, viewmax;
+static usize views, viewe, viewmax;
 static int bgscalyl, bgscalyr, bgscalf;
 static Channel *drawc;
+static usize T;
+static uchar sbuf[Iochunksz];
 
 static Image *
 eallocimage(Rectangle r, int repl, ulong col)
@@ -32,38 +34,36 @@
 static void
 drawsamps(void*)
 {
-	int x, n, lmin, lmax, rmin, rmax;
+	int x, lmin, lmax, rmin, rmax;
+	usize n, m;
 	s16int s;
-	uchar *p, *e, *et;
+	uchar *p, *e;
 	Rectangle l, r;
+	Dot d;
 
 	for(;;){
+end:
 		recvul(drawc);
 again:
 		lockdisplay(display);
 		draw(viewbg, viewbg->r, display->black, nil, ZP);
 		unlockdisplay(display);
-		n = viewe - views;
-		/*if(!file)*/
-			p = pcmbuf + views;
-		/*else{
-			seek(ifd, bgofs, 0);
-			n = read(ifd, bgbuf, n);
-			seek(ifd, seekp, 0);
-			p = bgbuf;
-		}*/
-		e = p + n;
+		d.pos = views;
+		m = viewe - views;
 		x = 0;
-		while(p < e){
+		while(m > 0){
 			if(nbrecvul(drawc) == 1)
 				goto again;
-			n = T;
-			if(n > e - p)
-				n -= n - (e - p);
-			et = p + n;
+			n = m < T ? m : T;
+			if((p = getbuf(d, n, sbuf, &n)) == nil){
+				fprint(2, "getbuf: %r\n");
+				goto end;
+			}
+			d.pos += n;
+			e = p + n;
 			lmin = lmax = 0;
 			rmin = rmax = 0;
-			while(p < et){
+			while(p < e){
 				s = (s16int)(p[1] << 8 | p[0]);
 				if(s < lmin)
 					lmin = s;
@@ -78,15 +78,16 @@
 				}
 				p += 4;
 			}
+			m -= n;
 			l = Rect(x, bgscalyl - lmax / bgscalf,
 				x+1, bgscalyl - lmin / bgscalf);
 			r = Rect(x, bgscalyr - rmax / bgscalf,
 				x+1, bgscalyr - rmin / bgscalf);
-		lockdisplay(display);
+			lockdisplay(display);
 			draw(viewbg, l, col[Csamp], nil, ZP);
-		unlockdisplay(display);
 			if(stereo)
 				draw(viewbg, r, col[Csamp], nil, ZP);
+			unlockdisplay(display);
 			x++;
 		}
 	}
@@ -93,9 +94,9 @@
 }
 
 static void
-b2t(uvlong ofs, int *th, int *tm, int *ts, int *tμ)
+b2t(usize ofs, int *th, int *tm, int *ts, int *tμ)
 {
-	uvlong nsamp;
+	usize nsamp;
 
 	nsamp = ofs / 4;
 	*ts = nsamp / 44100;
@@ -109,27 +110,21 @@
 drawstat(void)
 {
 	int th, tm, ts, tμ;
-	uvlong ssamp;
 	char s[256], *p;
 
-	ssamp = seekp / 4;
-	ts = ssamp / 44100;
-	tm = ts / 60;
-	th = tm / 60;
-	tμ = 100 * (ssamp - ts * 44100) / 44100;
-	ts %= 60;
+	b2t(dot.pos, &th, &tm, &ts, &tμ);
 	p = seprint(s, s+sizeof s, "T %lld @ %02d:%02d:%02d.%03d (%llud) ⋅ ",
-		T/4, th, tm, ts, tμ, ssamp);
-	if(loops != 0 && loops >= views){
-		b2t(loops, &th, &tm, &ts, &tμ);
+		T/4, th, tm, ts, tμ, dot.pos/4);
+	if(dot.from.pos > 0){
+		b2t(dot.from.pos, &th, &tm, &ts, &tμ);
 		p = seprint(p, s+sizeof s, "%02d:%02d:%02d.%03d (%llud) ↺ ",
-			th, tm, ts, tμ, loops);
+			th, tm, ts, tμ, dot.from.pos/4);
 	}else
 		p = seprint(p, s+sizeof s, "0 ↺ ");
-	if(loope != filesz){
-		b2t(loope, &th, &tm, &ts, &tμ);
+	if(dot.to.pos != totalsz){
+		b2t(dot.to.pos, &th, &tm, &ts, &tμ);
 		seprint(p, s+sizeof s, "%02d:%02d:%02d.%03d (%llud)",
-			th, tm, ts, tμ, loope);
+			th, tm, ts, tμ, dot.to.pos/4);
 	}else
 		seprint(p, s+sizeof s, "∞");
 	string(screen, statp, col[Cline], ZP, font, s);
@@ -139,18 +134,21 @@
 drawview(void)
 {
 	int x;
+	usize left, right;
 	Rectangle r;
 
+	left = dot.from.pos;
 	draw(view, view->r, viewbg, nil, ZP);
-	if(loops != 0 && loops >= views){
-		x = (loops - views) / T;
+	if(left != 0 && left >= views){
+		x = (left - 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 != filesz ){
-		x = (loope - views) / T;
+	right = dot.to.pos;
+	if(right != totalsz){
+		x = (right - views) / T;
 		r = view->r;
 		r.min.x += x;
 		r.max.x = r.min.x + 1;
@@ -162,16 +160,18 @@
 update(void)
 {
 	int x;
+	usize p;
 
+	p = dot.pos;
 	lockdisplay(display);
 	drawview();
-	x = screen->r.min.x + (seekp - views) / T;
-	//if(liner.min.x == x || seekp < views && x > liner.min.x)
+	x = screen->r.min.x + (p - views) / T;
+	//if(liner.min.x == x || p < views && x > liner.min.x)
 	//	return;
 	draw(screen, screen->r, view, nil, ZP);
 	liner.min.x = x;
 	liner.max.x = x + 1;
-	if(seekp >= views)
+	if(p >= views)
 		draw(screen, liner, col[Cline], nil, ZP);
 	drawstat();
 	flushimage(display, 1);
@@ -189,7 +189,7 @@
 		z = zoom >> -Δz;
 	else
 		z = zoom << Δz;
-	if(z < 1 || z > nsamp / Dx(screen->r))
+	if(z < 1 || z > (totalsz / 4) / Dx(screen->r))
 		return;
 	zoom = z;
 	redraw(0);
@@ -206,36 +206,43 @@
 }
 
 void
-setloop(vlong ofs)
+setloop(vlong off)
 {
-	ofs *= T;
-	ofs += views;
-	if(ofs < 0 || ofs > filesz)
+	off *= T;
+	off += views;
+	if(off < 0 || off > totalsz)
 		return;
-	if(ofs < seekp)
-		loops = ofs;
+	if(off < dot.pos)
+		setrange(off, dot.to.pos);
 	else
-		loope = ofs;
+		setrange(dot.from.pos, off);
 	update();
 }
 
 void
-setpos(vlong ofs)
+setcur(usize off, int jumponly)
 {
-	if(ofs < loops || ofs > loope - Nchunk)
+	if(off < dot.from.pos || off > dot.to.pos - Outsz)
 		return;
-	seekp = ofs;
-	if(file)
-		seek(ifd, ofs, 0);
+	if(jumponly)
+		jump(off);
+	else
+		setpos(off);
 	update();
 }
 
 void
-setofs(vlong ofs)
+setjump(usize off)
 {
-	setpos(views + ofs * T);
+	setcur(off, 1);
 }
 
+void
+setofs(usize ofs)
+{
+	setcur(views + ofs * T, 0);
+}
+
 static void
 resetdraw(void)
 {
@@ -242,7 +249,7 @@
 	int x;
 	Rectangle viewr, midr;
 
-	x = screen->r.min.x + (seekp - views) / T;
+	x = screen->r.min.x + (dot.pos - views) / T;
 	viewr = rectsubpt(screen->r, screen->r.min);
 	freeimage(viewbg);
 	freeimage(view);
@@ -268,17 +275,15 @@
 void
 redraw(int all)
 {
-	vlong span;
+	usize span;
 
 	lockdisplay(display);
-	T = filesz / zoom / Dx(screen->r) & ~3;
+	T = totalsz / 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)
+	viewmax = totalsz - span;
+	if(views > viewmax)
 		views = viewmax;
 	viewe = views + span;
 	if(all)
@@ -298,7 +303,6 @@
 	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);
-	loope = filesz;
 	if((drawc = chancreate(sizeof(ulong), 4)) == nil)
 		sysfatal("chancreate: %r");
 	if(proccreate(drawsamps, nil, mainstacksize) < 0)
--- a/fns.h
+++ b/fns.h
@@ -4,8 +4,18 @@
 void	setzoom(int, int);
 void	setpan(int);
 void	setloop(vlong);
-void	setpos(vlong);
-void	setofs(vlong);
+void	setcur(usize, int);
+void	setofs(usize);
+void	setjump(usize);
 void	redraw(int);
 void	initdrw(void);
-void*	emalloc(ulong);
+void	advance(Dot*, usize);
+void	jump(usize);
+void	setrange(usize, usize);
+int	setpos(usize);
+uchar*	getbuf(Dot, usize, uchar*, usize*);
+int	loadin(int);
+void*	emalloc(usize);
+void*	erealloc(void*, usize, usize);
+char*	estrdup(char*);
+int	setpri(int);
--- a/mkfile
+++ b/mkfile
@@ -6,6 +6,7 @@
 	cmd.$O\
 	draw.$O\
 	pplay.$O\
+	util.$O\
 
 HFILES=dat.h fns.h
 </sys/src/cmd/mkone
--- a/pplay.c
+++ b/pplay.c
@@ -7,68 +7,36 @@
 #include "dat.h"
 #include "fns.h"
 
-int ifd;
-uchar *pcmbuf;
-vlong filesz, nsamp;
-int file, stereo, zoom = 1;
-vlong seekp, T, loops, loope;
+int stereo, zoom = 1;
 
 static Keyboardctl *kc;
 static Mousectl *mc;
 static int cat;
 static int afd = -1;
+static uchar sbuf[Iochunksz];
 
-void *
-emalloc(ulong n)
-{
-	void *p;
-
-	if((p = mallocz(n, 1)) == nil)
-		sysfatal("emalloc: %r");
-	setmalloctag(p, getcallerpc(&n));
-	return p;
-}
-
-static int
-setpri(int pri)
-{
-	int n, fd, pid;
-	char path[32];
-
-	if((pid = getpid()) == 0)
-		return -1;
-	snprint(path, sizeof path, "/proc/%d/ctl", pid);
-	if((fd = open(path, OWRITE)) < 0)
-		return -1;
-	n = fprint(fd, "pri %d\n", pri);
-	close(fd);
-	if(n < 0)
-		return -1;
-	return 0;
-}
-
 static void
 athread(void *)
 {
-	int n;
+	int nerr;
 	uchar *p;
+	usize n;
 
-	p = pcmbuf;
+	nerr = 0;
 	for(;;){
-		if(afd < 0)
+		if(afd < 0 || nerr > 10)
 			return;
-		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(afd, p, n) != n){
+		if((p = getbuf(dot, Outsz, sbuf, &n)) == nil){
 			fprint(2, "athread: %r\n");
+			nerr++;
+			continue;
+		}
+		if(write(afd, p, n) != n){
+			fprint(2, "athread write: %r (nerr %d)\n", nerr);
 			break;
 		}
-		seekp += n;
-		if(seekp >= loope)
-			setpos(loops);
+		nerr = 0;
+		advance(&dot, n);
 		update();
 		yield();
 	}
@@ -106,48 +74,9 @@
 }
 
 static void
-reallocbuf(ulong n)
-{
-	if((pcmbuf = realloc(pcmbuf, n)) == nil)
-		sysfatal("realloc: %r");
-}
-
-static void
-initbuf(int fd)
-{
-	int n, sz;
-	vlong ofs;
-	Dir *d;
-
-	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;
-	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");
-	filesz = ofs;
-	reallocbuf(filesz);
-	nsamp = filesz / 4;
-}
-
-static void
 usage(void)
 {
-	fprint(2, "usage: %s [-cfs] [pcm]\n", argv0);
+	fprint(2, "usage: %s [-cs] [pcm]\n", argv0);
 	threadexits("usage");
 }
 
@@ -161,13 +90,13 @@
 
 	ARGBEGIN{
 	case 'c': cat = 1; break;
-	case 'f': file = 1; break;
 	case 's': stereo = 1; break;
 	default: usage();
 	}ARGEND
 	if((fd = *argv != nil ? open(*argv, OREAD) : 0) < 0)
 		sysfatal("open: %r");
-	initbuf(fd);
+	if(loadin(fd) < 0)
+		sysfatal("inittrack: %r");
 	close(fd);
 	initdrw();
 	if((kc = initkeyboard(nil)) == nil)
@@ -208,8 +137,8 @@
 		case 2:
 			switch(r){
 			case ' ': toggleplay(); break;
-			case 'b': setpos(loops); break;
-			case Kesc: loops = 0; loope = filesz; update(); break;
+			case 'b': setjump(dot.from.pos); break;
+			case Kesc: setrange(0, totalsz); update(); break;
 			case Kdel:
 			case 'q': threadexitsall(nil);
 			case 'z': setzoom(-zoom + 1, 0); break;
--- /dev/null
+++ b/util.c
@@ -1,0 +1,54 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "dat.h"
+#include "fns.h"
+
+void *
+emalloc(usize n)
+{
+	void *p;
+
+	if((p = mallocz(n, 1)) == nil)
+		sysfatal("emalloc: %r");
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+void *
+erealloc(void *p, usize n, usize oldn)
+{
+	if((p = realloc(p, n)) == nil)
+		sysfatal("realloc: %r");
+	setrealloctag(p, getcallerpc(&p));
+	if(n > oldn)
+		memset((uchar *)p + oldn, 0, n - oldn);
+	return p;
+}
+
+char *
+estrdup(char *s)
+{
+	if((s = strdup(s)) == nil)
+		sysfatal("estrdup: %r");
+	setmalloctag(s, getcallerpc(&s));
+	return s;
+}
+
+int
+setpri(int pri)
+{
+	int n, fd, pid;
+	char path[32];
+
+	if((pid = getpid()) == 0)
+		return -1;
+	snprint(path, sizeof path, "/proc/%d/ctl", pid);
+	if((fd = open(path, OWRITE)) < 0)
+		return -1;
+	n = fprint(fd, "pri %d\n", pri);
+	close(fd);
+	if(n < 0)
+		return -1;
+	return 0;
+}