ref: a8402254addaa203ed5e6f175677b971274a2f86
parent: 3f2ec921691046ec1738aeeeb494be9bfef21120
author: qwx <qwx@sciops.net>
date: Mon Apr 24 16:05:45 EDT 2023
reimplement chunking; near-infinite undo/redo; fixes; regressive commit no longer ever modify chunks, only replace them. - as before, chunks define "chains", ie. circular lists of chunks - whatever edit op is performed, all overlapping chunks are *replaced* by new ones representing the new content of the interval + sliced borders of the two terminal chunks on each side: -[ a ]-[ b ] .. [ c ]-[ d ]- replace ^^^^^^^^^^ -[ a ]-[L][x] .. [y][R]-[ d ]- new undo frame: [ b ]-...-[ c ], [L]-[x]-..-[y]-[R] -[ a ]-[ b ] .. [ c ]-[ d ]- cut ^^^^^^^^^^ -[ a ]-[L]-[R]-[ d ]- new undo frame: [ b ]-...-[ c ], [L]-[R] -[ a ]-[ b ] .. [ c ]-[ d ]- insert ^ -[ a ]-[L]-[ i ]-[R]-[ c ]-[ d ]- new undo frame: [ b ], [L]-[ i ]-[R] same with insert/replace/crop/copy/etc. - bordering overlapping chunks are sliced into two; the outermost slices are named L and R resp. for the left and right border and just their truncated unmodified prefix/suffix; the entire overlapping chain is unlinked and replaced by a new chain terminated by L and R; the op is pushed to an undo stack by just saving the pointers to the "parent" unsliced left and right border, and pointers to L and R. - always replacing entire overlapping chunks means that all pointers in between are always valid and never need repair, that undo/redo merely substitute one chain for another, and that all operations are in constant time; the cost in terms of chunks is dwarfed by the audio buffer size either way regardless of the multiplication of the number of chunks, and this dispenses with locks and other complicated logic for snarfing buffers; it also dispenses with the need to ever memcpy anything, only pointers are passed around. this resembles or replicates umbraticus' own method in pcmed(1). also: - "forget" ops if undoing then rewriting history from an earlier point - just allocate new undo slots as needed instead of even limiting them, who cares, negligeable in practice - synchronous edit after read proc is done reading in new data - lower i/o buffer size; don't memcpy anything... for now. - ui: there is always a valid range selected; upon any left click, we set an insertion point; upon any right click we set a replacement range and unset any insertion point; simpler than prior approach - display total time duration of range not thoroughly tested and there are regressions -- all much easier to debug.
--- a/chunk.c
+++ b/chunk.c
@@ -12,6 +12,25 @@
};
static Chunk *norris;
+// FIXME: crazy idea, multisnarf with addressable elements; $n registers; fork pplay to display them → ?
+
+typedef struct Op Op;
+struct Op{
+ Chunk *p1;
+ Chunk *p2;
+ Chunk *l;
+ Chunk *r;
+};
+static Op *opbuf, *ophead, *opend;
+static usize opbufsz;
+
+static struct{
+ Chunk *from;
+ usize foff;
+ Chunk *to;
+ usize toff;
+} hold;
+
int
Δfmt(Fmt *fmt)
{
@@ -20,8 +39,8 @@
d = va_arg(fmt->args, Dot*);
if(d == nil)
return fmtstrcpy(fmt, "[??:??:??:??]");
- return fmtprint(fmt, "[from=%08zux cur=%08zux at=%08zux to=%08zux]",
- d->from, d->pos, d->at, d->to);
+ return fmtprint(fmt, "[from=%08zux cur=%08zux to=%08zux]",
+ d->from, d->pos, d->to);
}
int
@@ -76,7 +95,7 @@
c->left = c;
c->right = c;
c->b = b;
- c->off = 0;
+ c->boff = 0;
c->len = b->bufsz;
incref(&b->Ref);
return c;
@@ -117,12 +136,12 @@
assert(c != nil && c->b != nil);
nc = newchunk(c->b);
- nc->off = c->off;
+ nc->boff = c->boff;
nc->len = c->len;
incref(c->b);
return nc;
}
-Chunk *
+static Chunk *
clone(Chunk *left, Chunk *right)
{
Chunk *cl, *c, *nc;
@@ -137,7 +156,20 @@
}
return cl;
}
+static Chunk *
+splitchunk(Chunk *p, usize from, usize to)
+{
+ Chunk *c;
+ assert(from < p->len);
+ assert(to > 0 && to - from <= p->len);
+ c = clonechunk(p);
+ c->boff += from;
+ c->len = to - from;
+ c->left = c->right = c;
+ return c;
+}
+
static void
freebuf(Buf *b)
{
@@ -170,21 +202,8 @@
freechunk(c);
}
-static void
-shrinkbuf(Chunk *c, usize newsz)
-{
- Buf *b;
-
- b = c->b;
- assert(b != nil);
- assert(newsz < b->bufsz && newsz > 0);
- if(c->off + c->len > newsz)
- c->len = newsz - c->off;
- b->buf = erealloc(b->buf, newsz, b->bufsz);
-}
-
usize
-chunklen(Chunk *c)
+chainlen(Chunk *c)
{
usize n;
Chunk *cp;
@@ -210,257 +229,271 @@
*off = p;
return c;
}
-usize
-c2p(Chunk *tc)
+
+static void
+forgetop(Op *op)
{
- Chunk *c;
- usize p;
+ freechain(op->l);
+}
- for(p=0, c=norris; c!=tc; c=c->right)
- p += c->len;
- return p;
+int
+unpop(char *)
+{
+ Op *op;
+
+ if(opend == opbuf || ophead == opend)
+ return 0;
+ op = ophead++;
+ dprint(op->p1, "cmd/unpop dot=%Δ P [%χ][%χ] LR [%χ][%χ]\n",
+ &dot, op->p1, op->p2, op->l, op->r);
+ totalsz += chainlen(op->l);
+ linkchunk(op->p1->left, op->l);
+ unlink(op->p1, op->p2);
+ totalsz -= chainlen(op->p1);
+ if(norris == op->p1)
+ norris = op->l;
+ dot.from = dot.pos = 0;
+ dot.to = totalsz;
+ return 1;
}
-void
-recalcsize(void)
+int
+popop(char *)
{
- int n;
+ Op *op;
- n = c2p(norris->left) + norris->left->len;
- if(dot.to == totalsz || dot.to > n)
- dot.to = n;
- if(dot.pos < dot.from || dot.pos > dot.to)
- dot.pos = dot.from;
- dot.at = dot.from;
- dprint(nil, "final %Δ\n", &dot);
- totalsz = n;
+ if(ophead == opbuf)
+ return 0;
+ op = --ophead;
+ dprint(op->l, "cmd/pop dot=%Δ P [%χ][%χ] LR [%χ][%χ]\n",
+ &dot, op->p1, op->p2, op->l, op->r);
+ totalsz += chainlen(op->p1);
+ linkchunk(op->l->left, op->p1);
+ unlink(op->l, op->r);
+ totalsz -= chainlen(op->l);
+ if(norris == op->l)
+ norris = op->p1;
+ dot.from = dot.pos = 0;
+ dot.to = totalsz;
+ return 1;
}
-#define ASSERT(x) {if(!(x)) printchunks(norris); assert((x)); }
-void
-paranoia(int exact)
+static void
+pushop(Chunk *p1, Chunk *p2, Chunk *l, Chunk *r)
{
- usize n;
- Chunk *c, *pc;
- Buf *b;
+ Op *op;
- ASSERT(dot.pos >= dot.from && dot.pos < dot.to);
- for(pc=norris, n=pc->len, c=pc->right; c!=norris; pc=c, c=c->right){
- b = c->b;
- ASSERT(b != nil);
- ASSERT((b->bufsz & 3) == 0 && b->bufsz >= Sampsz);
- ASSERT(c->off < b->bufsz);
- ASSERT(c->len > Sampsz);
- ASSERT(c->off + c->len <= b->bufsz);
- ASSERT(c->left == pc);
- n += c->len;
+ if(ophead == opbuf + opbufsz){
+ opbuf = erealloc(opbuf,
+ (opbufsz + 1024) * sizeof *opbuf,
+ opbufsz * sizeof *opbuf);
+ ophead = opbuf + opbufsz;
+ opend = ophead;
+ opbufsz += 1024;
}
- if(exact){
- ASSERT(n <= totalsz);
- ASSERT(dot.to <= totalsz);
+ if(opend > ophead){
+ for(op=ophead; op<opend; op++)
+ forgetop(op);
+ memset(ophead, 0, (opend - ophead) * sizeof *ophead);
}
+ *ophead++ = (Op){p1, p2, l, r};
+ opend = ophead;
}
-#undef ASSERT
-/* FIXME: should set .pos as well? or just bounds? s/setdot/setbounds/? */
void
-setdot(Dot *dot, Chunk *right)
+ccrop(usize from, usize to)
{
- dot->from = 0;
- if(right == nil)
- dot->to = c2p(norris->left) + norris->left->len;
- else
- dot->to = c2p(right);
- dot->at = dot->from;
-}
+ usize n, off;
+ Chunk *p1, *p2, *l, *r;
-void
-fixroot(Chunk *rc, usize off)
-{
- Chunk *c;
-
- dprint(rc, "fixroot [%χ] %08zux\n", rc, off);
- for(c=rc->left; off>0; off-=c->len, c=c->left){
- if(off - c->len == 0)
- break;
- assert(off - c->len < off);
- }
- norris = c;
+ assert(from < to && to <= totalsz);
+ p1 = p2c(from, &off);
+ l = splitchunk(p1, off, p1->len);
+ p2 = p2c(to, &off);
+ r = splitchunk(p2, 0, off);
+ linkchunk(p1, l);
+ linkchunk(p2->left, r);
+ unlink(p2, p1);
+ n = chainlen(l);
+ totalsz = n;
+ pushop(p2, p1, r, l);
+ norris = l;
+ dot.pos -= dot.from;
+ dot.from = 0;
+ dot.to = n;
}
-Chunk *
-splitchunk(Chunk *c, usize off)
+static int
+creplace(usize from, usize to, Chunk *c)
{
- Chunk *nc;
+ usize n, off;
+ Chunk *p1, *p2, *l, *r;
- dprint(nil, "splitchunk %Δ [%χ] off=%08zux\n", &dot, c, off);
- if(off == 0 || c == norris->left && off == c->len)
- return c;
- assert(off <= c->len);
- nc = clonechunk(c);
- nc->off = c->off + off;
- nc->len = c->len - off;
- c->len = off;
- linkchunk(c, nc);
- return nc;
+ assert(from > 0 && from < to && to <= totalsz);
+ p1 = p2c(from, &off);
+ l = splitchunk(p1, 0, off);
+ p2 = p2c(to, &off);
+ r = splitchunk(p2, off, p2->len);
+ linkchunk(c, r);
+ linkchunk(l, c);
+ n = chainlen(l);
+ totalsz += n;
+ linkchunk(p1->left, l);
+ unlink(p1, p2);
+ totalsz -= chainlen(p1);
+ pushop(p1, p2, l, r);
+ if(p1 == norris)
+ norris = l;
+ dot.to = dot.from + n;
+ dot.pos = from;
+ return 0;
}
-/* c1 [nc … c2] nc */
-int
-splitrange(usize from, usize to, Chunk **left, Chunk **right)
+// FIXME: use a specific Dot (prep for multibuf); generalize
+static int
+cinsert(usize pos, Chunk *c)
{
- usize off;
- Chunk *c;
+ usize n, off;
+ Chunk *p, *l, *r;
- dprint(nil, "splitrange from=%08zux to=%08zux\n", from, to);
- c = p2c(from, &off);
- if(off > 0){
- splitchunk(c, off);
- *left = c->right;
- }else
- *left = c; /* dangerous in combination with *right */
- c = p2c(to, &off);
- if(off < c->len - 1){
- splitchunk(c, off);
- *right = c;
- }else
- *right = c;
+ assert(pos <= totalsz);
+ p = p2c(pos, &off);
+ l = splitchunk(p, 0, off);
+ r = splitchunk(p, off, p->len);
+ linkchunk(c, r);
+ linkchunk(l, c);
+ n = chainlen(l);
+ totalsz += n;
+ linkchunk(p->left, l);
+ unlink(p, p);
+ totalsz -= chainlen(p);
+ pushop(p, p, l, r);
+ if(p == norris)
+ norris = l;
+ dot.to = dot.from + n;
+ dot.pos = pos;
return 0;
}
-Chunk *
-cutrange(usize from, usize to, Chunk **latch)
+int
+cpaste(usize from, usize to)
{
- Chunk *c, *left, *right;
+ Chunk *p1, *p2, *l, *r;
- dprint(nil, "cutrange from=%08zux to=%08zux\n", from, to);
- if(splitrange(from, to, &left, &right) < 0)
- return nil;
- c = left->left;
- if(left == norris)
- norris = right->right;
- unlink(left, right);
- if(latch != nil)
- *latch = left;
- return c;
+ if(hold.from == nil || hold.to == nil){
+ werrstr("cpaste: nothing to paste");
+ return -1;
+ }
+ p1 = hold.from;
+ p2 = hold.to;
+ if(p1 == p2)
+ l = splitchunk(p1, hold.foff, hold.toff);
+ else{
+ l = splitchunk(p1, hold.foff, p1->len);
+ r = splitchunk(p2, 0, hold.toff);
+ if(p1->right != p2)
+ linkchunk(l, clone(p1->right, p2->left));
+ linkchunk(l->left, r);
+ }
+ return from == to ? cinsert(from, l) : creplace(from, to, l);
}
-Chunk *
-croprange(usize from, usize to, Chunk **latch)
+void
+ccopy(usize from, usize to)
{
- Chunk *cut, *left, *right;
-
- dprint(nil, "croprange from=%08zux to=%08zux\n", from, to);
- if(splitrange(from, to, &left, &right) < 0)
- return nil;
- norris = left;
- cut = right->right;
- if(latch != nil)
- *latch = cut;
- unlink(right->right, left->left);
- return left;
+ hold.from = p2c(from, &hold.foff);
+ hold.to = p2c(to, &hold.toff);
}
-// FIXME: generalized insert(from, to), where from and to not necessarily distinct
-Chunk *
-inserton(usize from, usize to, Chunk *c, Chunk **latch)
+void
+chold(Chunk *c)
{
- Chunk *left;
-
- dprint(c, "inserton from=%08zux to=%08zux\n", from, to);
- left = cutrange(from, to, latch);
- linkchunk(left, c);
- if(from == 0)
- norris = c;
- dprint(nil, "done\n");
- return left;
+ hold.from = hold.to = c;
+ hold.foff = hold.toff = 0;
}
-Chunk *
-insertat(usize pos, Chunk *c)
+void
+ccut(usize from, usize to)
{
- usize off;
- Chunk *left;
+ usize n;
+ Chunk *p1, *p2, *l, *r;
- dprint(c, "insertat cur=%08zux\n", pos);
- if(pos == 0){
- left = norris->left;
- norris = c;
- }else{
- left = p2c(pos, &off);
- splitchunk(left, off);
- }
- if(off == 0)
- left = left->left;
- linkchunk(left, c);
- return left;
+ assert(from > 0 && from < to && to <= totalsz);
+ ccopy(from, to);
+ p1 = hold.from;
+ r = splitchunk(p1, 0, hold.foff);
+ p2 = hold.to;
+ l = splitchunk(p2, hold.toff, p2->len);
+ linkchunk(l, r);
+ n = chainlen(l);
+ totalsz += n;
+ linkchunk(p1->left, l);
+ unlink(p1, p2);
+ totalsz -= chainlen(p1);
+ pushop(p1, p2, l, r);
+ if(p1 == norris)
+ norris = l;
+ dot.from = 0;
+ dot.to = n;
+ dot.pos = from;
}
uchar *
-getslice(Dot *d, usize n, usize *sz)
+getslice(Dot *d, usize want, usize *have)
{
- usize Δbuf, Δloop, off;
+ usize n, off;
Chunk *c;
if(d->pos >= totalsz){
werrstr("out of bounds");
- *sz = 0;
+ *have = 0;
return nil;
}
c = p2c(d->pos, &off);
- Δloop = d->to - d->pos;
- Δbuf = c->len - off;
- if(n < Δloop && n < Δbuf){
- *sz = n;
- d->pos += n;
- }else if(Δloop <= Δbuf){
- *sz = Δloop;
- d->pos = d->from;
- }else{
- *sz = Δbuf;
- d->pos += Δbuf;
- }
- return c->b->buf + c->off + off;
+ n = c->len - off;
+ *have = want > n ? n : want;
+ return c->b->buf + c->boff + off;
}
Chunk *
-readintochunks(int fd)
+chunkfile(int fd)
{
int n;
- usize m;
- Chunk *rc, *c, *nc;
+ Chunk *c;
+ Buf *b;
- for(m=0, rc=c=nil;; m+=n){
- nc = newbuf(Iochunksz);
- if(rc == nil)
- rc = nc;
- else
- linkchunk(c, nc);
- c = nc;
- if((n = readn(fd, c->b->buf, Iochunksz)) < Iochunksz)
+ c = newbuf(Chunksz);
+ b = c->b;
+ for(;;){
+ if(b->bufsz - c->len < Chunksz){
+ b->buf = erealloc(c->b->buf, 2 * c->b->bufsz, c->b->bufsz);
+ b->bufsz *= 2;
+ }
+ if((n = readn(fd, b->buf + c->len, Chunksz)) < Chunksz)
break;
+ c->len += n;
yield();
}
- close(fd);
- if(n < 0)
- fprint(2, "readintochunks: %r\n");
- else if(n == 0){
- if(c != rc)
- unlink(c, c);
+ if(n < 0){
+ fprint(2, "chunkfile: %r\n");
freechunk(c);
- if(c == rc){
- werrstr("readintochunks: nothing read");
- return nil;
- }
- }else if(n > 0 && n < Iochunksz)
- shrinkbuf(c, n);
- return rc;
+ return nil;
+ }else if(c->len == 0){
+ fprint(2, "chunkfile: nothing read\n");
+ freechunk(c);
+ return nil;
+ }else if(c->len < b->bufsz){
+ b->buf = erealloc(b->buf, c->len, b->bufsz);
+ b->bufsz = c->len;
+ }
+ return c;
}
void
-graphfrom(Chunk *c)
+initbuf(Chunk *c)
{
norris = c;
- recalcsize();
- setdot(&dot, nil);
+ totalsz = chainlen(c);
+ dot.pos = dot.from = 0;
+ dot.to = totalsz;
}
--- a/cmd.c
+++ b/cmd.c
@@ -8,182 +8,24 @@
usize totalsz;
int treadsoftly;
-// FIXME: undo/redo as an unbatched series of inserts and deletes
-// FIXME: crazy idea, multisnarf with addressable elements; $n registers; fork pplay to display them → ?
-
-enum{
- OPins,
- OPdel,
- OPcrop,
-
- Nops = 128,
-};
static int epfd[2];
-typedef struct Op Op;
-struct Op{
- int type;
- usize from;
- usize to;
- Chunk *c;
-};
-static int ohead, otail;
-static Chunk *hold;
-static Op ops[Nops];
-
-void
-setrange(usize from, usize to)
-{
- assert((from & 3) == 0);
- assert((to & 3) == 0);
- dot.from = from;
- dot.to = to;
- if(dot.pos < from || dot.pos >= to)
- dot.pos = from;
- dot.at = dot.from;
-}
-
-int
-jump(usize off)
-{
- if(off < dot.from || off > dot.to){
- werrstr("cannot jump outside of loop bounds\n");
- return -1;
- }
- dot.pos = off;
- if(dot.from == 0 && dot.to == totalsz)
- dot.at = off;
- return 0;
-}
-
-// FIXME: needs a different way of managing ops
-int
-unpop(char *)
-{
- return 0;
-}
-
-int
-popop(char *) // FIXME: u[n]
-{
- Op *op;
-
- if(otail == ohead)
- return 0;
- ohead = ohead - 1 & nelem(ops) - 1;
- op = ops + ohead;
- dprint(op->c, "cmd/pop dot=%Δ type=%d from=%08zux to=%08zux c=%#p\n",
- &dot, op->type, op->from, op->to, op->c);
- switch(op->type){
- case OPdel:
- if(insertat(op->from, op->c) == nil)
- return -1;
- break;
- case OPins:
- if(cutrange(op->from, op->to, nil) == nil)
- return -1;
- break;
- case OPcrop:
- if(insertat(op->to - op->from, op->c) == nil)
- return -1;
- dprint(nil, "uncropped with loose root\n");
- fixroot(op->c, op->from + (op->to - op->from));
- break;
- default: werrstr("phase error: unknown op %d\n", op->type); return -1;
- }
- memset(ops+ohead, 0, sizeof *ops);
- return 1;
-}
-
-void
-pushop(int type, usize from, usize to, Chunk *c)
-{
- freechain(ops[ohead].c);
- ops[ohead] = (Op){type, from, to, c};
- ohead = ohead + 1 & nelem(ops) - 1;
-}
-
static int
-replace(char *, Chunk *c)
+paste(char *)
{
- Chunk *left, *latch;
- usize n;
+ usize from, to;
- if(c == nil){
- fprint(2, "replace: nothing to paste\n");
- return -1;
- }
- n = chunklen(c);
- if((left = inserton(dot.from, dot.to, c, &latch)) == nil){
- fprint(2, "insert: %r\n");
- return -1;
- }
- pushop(OPdel, dot.from, dot.to, latch);
- pushop(OPins, dot.from, dot.from+n, nil);
- setdot(&dot, nil);
- dot.pos = c2p(left->right);
- return 1;
+ from = dot.from;
+ to = dot.to;
+ if(latchedpos >= 0 && dot.from == 0 && dot.to == totalsz)
+ to = from = latchedpos;
+ return cpaste(from, to) == 0 ? 1 : -1;
}
static int
-insert(char *, Chunk *c)
-{
- Chunk *left;
-
- if(c == nil){
- fprint(2, "insert: nothing to paste\n");
- return -1;
- }
- if(dot.at == dot.from){
- fprint(2, "insert: nowhere to paste\n");
- return -1;
- }
- assert(dot.at >= dot.from && dot.at <= dot.to);
- dprint(nil, "cmd/insert %Δ\n", &dot);
- dprint(c, "buffered\n");
- pushop(OPins, dot.at, dot.at+chunklen(c)-1, nil);
- if((left = insertat(dot.at, c)) == nil){
- fprint(2, "insert: %r\n");
- return -1;
- }
- setdot(&dot, nil);
- dot.pos = c2p(left->right);
- dot.at = dot.from;
- dprint(nil, "end\n");
- return 1;
-}
-
-static int
-paste(char *s, Chunk *c)
-{
- if(c == nil && (c = hold) == nil){
- werrstr("paste: no buffer");
- return -1;
- }
- c = clone(c, c->left);
- if(dot.from > 0 || dot.to < totalsz)
- return replace(s, c);
- else
- return insert(s, c);
-}
-
-static void
-snarf(Chunk *c)
-{
- dprint(hold, "snarf was:\n");
- freechain(hold);
- hold = c;
- dprint(hold, "snarf now:\n");
-}
-
-static int
copy(char *)
{
- Chunk *left, *right;
-
- dprint(hold, "cmd/copy %Δ\n", &dot);
- splitrange(dot.from, dot.to, &left, &right);
- snarf(clone(left, right));
+ ccopy(dot.from, dot.to);
return 0;
}
@@ -190,19 +32,12 @@
static vlong
cut(char *)
{
- Chunk *latch;
-
+ dprint(nil, "cmd/cut %Δ\n", &dot);
if(dot.from == 0 && dot.to == totalsz){
- werrstr("cut: no range selected");
+ werrstr("cut: can't cut entire buffer");
return -1;
}
- dprint(nil, "cmd/cut %Δ\n", &dot);
- cutrange(dot.from, dot.to, &latch);
- dprint(latch, "latched\n");
- snarf(clone(latch, latch->left));
- pushop(OPdel, dot.from, dot.from+chunklen(latch)-1, latch);
- dot.pos = dot.from;
- setdot(&dot, nil);
+ ccut(dot.from, dot.to);
return 1;
}
@@ -209,87 +44,38 @@
static int
crop(char *)
{
- Chunk *latch;
-
dprint(nil, "cmd/crop %Δ\n", &dot);
- if(croprange(dot.from, dot.to, &latch) == nil)
- return -1;
- dprint(latch, "latched\n");
- pushop(OPcrop, dot.from, dot.to, latch);
- setdot(&dot, nil);
- dot.pos = 0;
+ ccrop(dot.from, dot.to);
return 1;
}
-vlong
-getbuf(Dot d, usize n, uchar *buf, usize bufsz)
-{
- uchar *p, *b;
- usize sz;
-
- assert(d.pos < totalsz);
- assert(n <= bufsz);
- b = buf;
- while(n > 0){
- if((p = getslice(&d, n, &sz)) == nil || sz < Sampsz)
- return -1;
- memcpy(b, p, sz);
- b += sz;
- n -= sz;
- }
- return b - buf;
-}
-
static int
writebuf(int fd)
{
- static uchar *buf;
- static usize bufsz;
int nio;
- usize n, m, c, k;
+ uchar *b;
+ usize n, m, k;
Dot d;
- d.pos = d.from = dot.from;
- d.to = dot.to;
+ d = dot;
+ d.pos = d.from;
if((nio = iounit(fd)) == 0)
nio = 8192;
- if(bufsz < nio){
- buf = erealloc(buf, nio, bufsz);
- bufsz = nio;
- }
- for(m=d.to-d.from, c=0; m>0;){
+ for(m=d.to-d.from, b=(uchar*)&d; m>0; m-=n, d.pos+=n){
k = nio < m ? nio : m;
- if(getbuf(d, k, buf, bufsz) < 0){
+ if((b = getslice(&d, k, &k)) == nil || k <= 0){
fprint(2, "writebuf: couldn\'t snarf: %r\n");
return -1;
}
- if((n = write(fd, buf, k)) != k){
+ if((n = write(fd, b, k)) != k){
fprint(2, "writebuf: short write not %zd: %r\n", k);
return -1;
}
- m -= n;
- d.pos += n;
- c += n;
}
- write(fd, buf, 0); /* close pipe */
+ write(fd, b, 0); /* close pipe */
return 0;
}
-int
-advance(Dot *d, usize n)
-{
- usize m, sz;
-
- m = 0;
- while(n > 0){
- if(getslice(d, n, &sz) == nil)
- return -1;
- m += sz;
- n -= sz;
- }
- return m;
-}
-
static void
rc(void *s)
{
@@ -311,8 +97,7 @@
close(fd);
threadexits(nil);
}
-/* using a thread does slow down reads a bit */
-// FIXME: ugly
+
static void
rproc(void *efd)
{
@@ -321,23 +106,25 @@
Chunk *c;
d = dot;
- treadsoftly = 1;
+ treadsoftly++;
fd = (intptr)efd;
- if((c = readintochunks(fd)) == nil){
+ if((c = chunkfile(fd)) == nil){
treadsoftly = 0;
threadexits("failed reading from pipe: %r");
}
close(fd);
- dot = d;
- paste(nil, c);
- dot.pos = dot.from;
- setdot(&dot, nil);
- recalcsize();
+ qlock(&lsync);
+ dot.from = d.from;
+ dot.to = d.to;
+ chold(c);
+ paste(nil);
+ qunlock(&lsync);
+ treadsoftly--;
redraw(0);
- treadsoftly = 0;
threadexits(nil);
}
+// FIXME: make sure writes complete even after exit
static int
pipeline(char *arg, int rr, int wr)
{
@@ -346,7 +133,7 @@
if(procrfork(rc, arg, mainstacksize, RFFDG|RFNOTEG|RFNAMEG) < 0)
sysfatal("procrfork: %r");
close(epfd[0]);
- if(wr && procrfork(wproc, (int*)dup(epfd[1], -1), mainstacksize, RFFDG) < 0){
+ if(wr && procrfork(wproc, (int*)dup(epfd[1], -1), mainstacksize, RFFDG|RFNOTEG|RFNAMEG) < 0){
fprint(2, "procrfork: %r\n");
return -1;
}
@@ -399,9 +186,8 @@
return 0;
}
-/* the entire string is treated as the filename, ie.
- * spaces and any other weird characters will be part
- * of it */
+/* the entire string is treated as the filename, ie. spaces
+ * and any other weird characters will be part of it */
static int
writeto(char *arg)
{
@@ -411,7 +197,7 @@
werrstr("writeto: %r");
return -1;
}
- if(procrfork(wproc, (int*)fd, mainstacksize, RFFDG) < 0){
+ if(procrfork(wproc, (int*)fd, mainstacksize, RFFDG|RFNOTEG|RFNAMEG) < 0){
fprint(2, "procrfork: %r\n");
return -1;
}
@@ -438,8 +224,6 @@
break;
s += n;
}
- if(debug)
- paranoia(1);
switch(r){
case '<': x = pipefrom(s); break;
case '^': x = pipethrough(s); break;
@@ -446,30 +230,43 @@
case '|': x = pipeto(s); break;
case 'c': x = copy(s); break;
case 'd': x = cut(s); break;
- case 'p': x = paste(s, nil); break;
+ case 'p': x = paste(s); break;
case 'q': threadexitsall(nil);
case 'r': x = readfrom(s); break;
case 's': x = replicate(s); break;
-// case 'U': x = unpop(s); break;
+ case 'U': x = unpop(s); break;
case 'u': x = popop(s); break;
case 'w': x = writeto(s); break;
case 'x': x = crop(s); break;
default: werrstr("unknown command %C", r); x = -1; break;
}
- if(debug)
- paranoia(0);
- recalcsize();
return x;
}
int
+advance(Dot *d, usize n)
+{
+ usize m;
+
+ assert(d->pos >= d->from && d->pos <= d->to);
+ while(n > 0){
+ m = d->to - d->pos > n ? n : d->to - d->pos;
+ n -= m;
+ d->pos += m;
+ if(d->pos == d->to)
+ d->pos = d->from;
+ }
+ return 0;
+}
+
+int
loadin(int fd)
{
Chunk *c;
- if((c = readintochunks(fd)) == nil)
+ if((c = chunkfile(fd)) == nil)
sysfatal("loadin: %r");
- graphfrom(c);
+ initbuf(c);
return 0;
}
--- a/dat.h
+++ b/dat.h
@@ -8,12 +8,12 @@
WriteDelay = Rate / WriteRate, /* 1764 default delay */
Sampsz = 2 * 2,
Outsz = WriteDelay * Sampsz,
- Iochunksz = 4*1024*1024, /* ≈ 24 sec. at 44.1 kHz */
+ Chunksz = Sampsz * Rate,
};
#pragma incomplete Buf
struct Chunk{
Buf *b;
- usize off;
+ usize boff;
usize len;
Chunk *left;
Chunk *right;
@@ -21,13 +21,15 @@
extern struct Dot{
usize pos;
usize from;
- usize at;
usize to;
};
extern Dot dot;
+extern vlong latchedpos;
extern usize totalsz;
extern int treadsoftly;
extern int viewdone;
+
+extern QLock lsync;
extern int stereo;
extern int debug;
--- a/draw.c
+++ b/draw.c
@@ -8,6 +8,7 @@
QLock lsync;
int debugdraw;
int viewdone;
+vlong latchedpos;
enum{
Cbg,
@@ -129,12 +130,12 @@
lmin = lmax = 0;
rmin = rmax = 0;
while(n > 0){
- p = getslice(&d, n, &k);
- if(p == nil){
+ if((p = getslice(&d, n, &k)) == nil){
if(k > 0)
fprint(2, "getslice: %r\n");
goto end;
}
+ d.pos += k;
e = p + k;
while(p < e){
s = (s16int)(p[1] << 8 | p[0]);
@@ -182,11 +183,11 @@
seprint(s, s+sizeof s, "T %zd @ %τ", T / Sampsz, dot.pos);
p = string(screen, statp, col[Ctext], ZP, font, s);
if(dot.from > 0 || dot.to < totalsz){
- seprint(s, s+sizeof s, " ↺ %τ - %τ", dot.from, dot.to);
+ seprint(s, s+sizeof s, " ↺ %τ - %τ (%τ)", dot.from, dot.to, dot.to - dot.from);
p = string(screen, p, col[Cloop], ZP, font, s);
}
- if(dot.at != dot.from && dot.at != dot.to){
- seprint(s, s+sizeof s, " ‡ %τ", dot.at);
+ if(latchedpos >= 0){
+ seprint(s, s+sizeof s, " ‡ %τ", (usize)latchedpos);
p = string(screen, p, col[Cins], ZP, font, s);
}
}
@@ -199,8 +200,8 @@
drawchunks();
drawpos(dot.from, col[Cloop]);
drawpos(dot.to, col[Cloop]);
- if(dot.at != dot.from && dot.at != dot.to)
- drawpos(dot.at, col[Cins]);
+ if(latchedpos >= 0)
+ drawpos(latchedpos, col[Cins]);
}
void
@@ -289,6 +290,31 @@
}
void
+setrange(usize from, usize to)
+{
+ assert((from & 3) == 0);
+ assert((to & 3) == 0);
+ dot.from = from;
+ dot.to = to;
+ if(dot.pos < from || dot.pos >= to)
+ dot.pos = from;
+ latchedpos = -1;
+}
+
+static int
+setcur(usize off)
+{
+ if(off < dot.from || off > dot.to - Outsz){
+ werrstr("cannot jump outside of loop bounds\n");
+ return -1;
+ }
+ dot.pos = off;
+ latchedpos = off;
+ update();
+ return 0;
+}
+
+void
setloop(vlong off)
{
off *= T;
@@ -299,15 +325,6 @@
setrange(off, dot.to);
else
setrange(dot.from, off);
- update();
-}
-
-static void
-setcur(usize off)
-{
- if(off < dot.from || off > dot.to - Outsz)
- return;
- jump(off);
update();
}
--- a/fns.h
+++ b/fns.h
@@ -1,35 +1,29 @@
-void fixroot(Chunk*, usize);
void dprint(Chunk*, char*, ...);
void freechain(Chunk*);
-usize chunklen(Chunk*);
-void recalcsize(void);
-void paranoia(int);
-void setdot(Dot*, Chunk*);
-Chunk* clone(Chunk*, Chunk*);
-int splitrange(usize, usize, Chunk**, Chunk**);
-void graphfrom(Chunk*);
-Chunk* inserton(usize, usize, Chunk*, Chunk**);
-Chunk* insertat(usize, Chunk*);
-Chunk* croprange(usize, usize, Chunk**);
-Chunk* cutrange(usize, usize, Chunk**);
-Chunk* readintochunks(int);
+int unpop(char*);
+int popop(char*);
+int cpaste(usize, usize);
+void ccopy(usize, usize);
+void chold(Chunk*);
+void ccut(usize, usize);
+void ccrop(usize, usize);
+Chunk* chunkfile(int);
+void initbuf(Chunk*);
int cmd(char*);
void initcmd(void);
void update(void);
void setzoom(int, int);
int zoominto(vlong, vlong);
-void setpan(int);
-void setpage(int);
+void setrange(usize, usize);
void setloop(vlong);
void setofs(usize);
+void setpan(int);
+void setpage(int);
void setjump(usize);
void redraw(int);
void initdrw(int);
int advance(Dot*, usize);
-int jump(usize);
Chunk* p2c(usize, usize*);
-usize c2p(Chunk*);
-void setrange(usize, usize);
int setpos(usize);
uchar* getslice(Dot*, usize, usize*);
vlong getbuf(Dot, usize, uchar*, usize);
--- a/pplay.c
+++ b/pplay.c
@@ -16,25 +16,24 @@
static Mousectl *mc;
static int cat;
static int afd = -1;
-static uchar sbuf[Iochunksz];
static void
athread(void *)
{
int nerr;
- vlong n;
+ uchar *b;
+ usize n;
nerr = 0;
for(;;){
if(afd < 0 || nerr > 10)
return;
- n = getbuf(dot, Outsz, sbuf, sizeof sbuf);
- if(n < 0){
+ if((b = getslice(&dot, Outsz, &n)) == nil || n <= 0){
fprint(2, "athread: %r\n");
nerr++;
continue;
}
- if(write(afd, sbuf, n) != n){
+ if(write(afd, b, n) != n){
fprint(2, "athread write: %r (nerr %d)\n", nerr);
break;
}
@@ -164,12 +163,12 @@
case Kleft: setpage(-1); break;
case Kright: setpage(1); break;
default:
+ if((p = prompt(r)) == nil || strlen(p) == 0)
+ break;
if(treadsoftly){
- fprint(2, "dropping edit event during ongoing read\n");
+ fprint(2, "dropping edit command during ongoing read\n");
break;
}
- if((p = prompt(r)) == nil || strlen(p) == 0)
- break;
qlock(&lsync);
switch(cmd(p)){
case -1: fprint(2, "cmd \"%s\" failed: %r\n", p); update(); break;