#include #include #include #include #include #include #include typedef struct Piece Piece; struct Piece{ Piece *next; uchar *s, *e, data[]; }*head; typedef struct Edit Edit; struct Edit{ Piece **p, *undo, *dostart, *doend; vlong from, undoto, doto; Edit *next; }*edhead, *ed; typedef struct Mark Mark; struct Mark{ char *name; vlong from, to; Mark *next; }; Cursor skull = { {-7,-7},{ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0xe7, 0xe7, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x3f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xef, 0xf7, 0xc7, 0xe3, 0x00, 0x00, 0x00, 0x00 },{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xE7, 0xE7, 0x3F, 0xFC, 0x0F, 0xF0, 0x0D, 0xB0, 0x07, 0xE0, 0x06, 0x60, 0x37, 0xEC, 0xE4, 0x27, 0xC3, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }; Cursor busy = { {-7, -7},{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x8e, 0x1d, 0xc7, 0xff, 0xe3, 0xff, 0xf3, 0xff, 0xff, 0x7f, 0xfe, 0x3f, 0xf8, 0x17, 0xf0, 0x03, 0xe0, 0x00, 0x00 },{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x82, 0x04, 0x41, 0xff, 0xe1, 0x5f, 0xf1, 0x3f, 0xfe, 0x17, 0xf0, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00 } }; char *b3[] = { "play", "all selection", "no selection", "start selection", "end selection", "mark selection", "jump to selection", "> cmd (ignore stdout)", "| cmd (replace selection)", "< cmd (insert at position)", "x cmd (mix from position)", "undo", "redo", "quit", nil }; Image *bg, *fg, *hl; Keyboardctl *kc; Mousectl *mc; vlong len, pos, from, to, vis, zoom; char t[128]; int pfd[2]; void drawprog(void){ Rectangle r; r = Rect( screen->r.min.x, screen->r.min.y + screen->r.max.y - font->height >> 1, screen->r.max.x, screen->r.min.y + screen->r.max.y + font->height >> 1 ); draw(screen, r, bg, nil, ZP); r.min.x = screen->r.min.x + (pos - vis) / zoom; r.max.x = r.min.x + 1; draw(screen, r, fg, nil, ZP); seprint(t, t + sizeof t, " %.2lld:%.2lld", pos / 44100 / 60 % 60, pos / 44100 % 60); string(screen, r.min, fg, ZP, font, t); seprint(t, t + sizeof t, "%lld ", pos); r.min.x -= stringwidth(font, t) - 1; string(screen, r.min, fg, ZP, font, t); flushimage(display, 1); } void redraw(void){ Rectangle r; Piece *p; uchar *b; s16int s; vlong i; int h, lpos, lneg, rpos, rneg; r = screen->r; i = vis + Dx(screen->r) * zoom; draw(screen, r, bg, nil, ZP); seprint(t, t + sizeof t, "%.2lld:%.2lld", vis / 44100 / 60 % 60, vis / 44100 % 60); string(screen, r.min, fg, ZP, font, t); seprint(t, t + sizeof t, "%lld samples / pixel", zoom); r.min.x = r.min.x + r.max.x - stringwidth(font, t) >> 1; string(screen, r.min, fg, ZP, font, t); seprint(t, t + sizeof t, "%.2lld:%.2lld", i / 44100 / 60 % 60, i / 44100 % 60); r.min.x = r.max.x - stringwidth(font, t); string(screen, r.min, fg, ZP, font, t); r.min = Pt(screen->r.min.x, r.max.y - font->height); seprint(t, t + sizeof t, "%lld samples selected", to - from); string(screen, r.min, fg, ZP, font, t); seprint(t, t + sizeof t, "len: %lld (%.2lld:%.2lld)", len, len / 44100 / 60 % 60, len / 44100 % 60); r.min.x = r.max.x - stringwidth(font, t); string(screen, r.min, fg, ZP, font, t); if(from < to && vis < to && from < i){ r.min.x = screen->r.min.x; if(from > vis) r.min.x += (from - vis) / zoom; if(to < i) r.max.x = screen->r.min.x + (to - vis) / zoom; r.min.y = screen->r.min.y + font->height; r.max.y -= font->height; draw(screen, r, hl, nil, ZP); } h = Dy(screen->r) - 3 * font->height >> 2; r.max.x = screen->r.min.x; for(p = head, i = vis * 4; p != nil && i >= p->e - p->s; p = p->next) i -= p->e - p->s; if(p == nil){ drawprog(); return; } b = p->s + i; while(p != nil && r.max.x < screen->r.max.x){ r.min.x = r.max.x++; for(lpos = lneg = rpos = rneg = i = 0; i < zoom; i++){ s = b[1] << 8 | b[0]; if(s > lpos) lpos = s; if(s < lneg) lneg = s; s = b[3] << 8 | b[2]; if(s > rpos) rpos = s; if(s < rneg) rneg = s; if(p->e - b < 4){ p = p->next; if(p == nil) break; b = p->s; }else b += 4; } r.min.y = screen->r.min.y + font->height + h - lpos * h / 0x8000; r.max.y = screen->r.min.y + font->height + h - lneg * h / 0x8000; draw(screen, r, fg, nil, ZP); r.min.y = screen->r.max.y - font->height - h - rpos * h / 0x8000; r.max.y = screen->r.max.y - font->height - h - rneg * h / 0x8000; draw(screen, r, fg, nil, ZP); } drawprog(); } void centre(void){ if(pos >= vis && pos < vis + Dx(screen->r) * zoom){ drawprog(); return; } vis = pos - Dx(screen->r) * zoom / 2; if(vis < 0) vis = 0; redraw(); } Piece* slurp(int fd){ long n; ulong u, a; Piece *p = nil; a = u = 0; while(9){ if(a - u < 1024UL){ a += 0x100000UL; p = realloc(p, a + sizeof(Piece)); if(p == nil){ *t = 0; errstr(t, sizeof t); return nil; } } n = read(fd, p->data + u, a - u); if(n < 1) break; u += n; } close(fd); if(n){ *t = 0; errstr(t, sizeof t); free(p); return nil; } if(u & 3){ strecpy(t, t + sizeof t, "read not a multiple of four bytes"); free(p); return nil; } p = realloc(p, u + sizeof(Piece)); if(p == nil){ *t = 0; errstr(t, sizeof t); return nil; } setrealloctag(p, getcallerpc(&fd)); p->s = p->data; p->e = p->s + u; return p; } int out(int fd){ Piece *p; vlong n; n = from << 2; for(p = head; n >= p->e - p->s; p = p->next) n -= p->e - p->s; if(to - from << 2 <= p->e - p->s - n){ if(write(fd, p->s + n, to - from << 2) != to - from << 2) return -1; }else{ if(write(fd, p->s + n, p->e - p->s - n) != p->e - p->s - n) return -1; n -= p->e - p->s - n; for(p = p->next; n >= p->e - p->s; p = p->next){ if(write(fd, p->s, p->e - p->s) != p->e - p->s) return -1; n -= p->e - p->s; } if(n && write(fd, p->s, n) != n) return -1; } return 0; } void rcproc(void *pidc){ close(pfd[1]); dup(pfd[0], 0); dup(pfd[0], 1); close(pfd[0]); procexecl(pidc, "/bin/rc", "rc", "-c", t, nil); threadexits("procexec: %r"); } void outproc(void*){ close(pfd[0]); out(pfd[1]); write(pfd[1], t, 0); close(pfd[1]); threadexits(nil); } void histfwd(void){ if(ed == nil) return; *ed->p = ed->dostart; pos = from = ed->from; to = ed->doto; len += ed->doto - ed->undoto; ed = ed->next; redraw(); } void histbwd(void){ Edit *e; if(ed == edhead) return; e = ed; for(ed = edhead; ed->next != e; ed = ed->next) ; *ed->p = ed->undo; pos = from = ed->from; to = ed->undoto; len += ed->undoto - ed->doto; redraw(); } void edit(vlong off, vlong del, Piece *add){ Edit *e; Piece **p, *tmp; if(ed == nil){ ed = malloc(sizeof(Edit)); if(ed == nil) goto hungry; ed->next = nil; if(edhead == nil) edhead = ed; else{ for(e = edhead; e->next != nil; e = e->next) ; e->next = ed; } }else while(9){ while(ed->dostart != ed->doend){ tmp = ed->dostart; ed->dostart = tmp->next; free(tmp); } if(ed->next == nil) break; e = ed; ed = ed->next; free(e); } ed->from = ed->doto = off; ed->undoto = off + del; off *= 4; for(p = &head; *p != nil && off >= (*p)->e - (*p)->s; p = &(*p)->next) off -= (*p)->e - (*p)->s; assert(*p != nil || off == 0); ed->p = p; ed->undo = *p; if(off){ tmp = malloc(sizeof(Piece)); if(tmp == nil) goto hungry; tmp->s = (*p)->s; tmp->e = tmp->s + off; }else tmp = nil; for(off += del * 4; *p != nil && off >= (*p)->e - (*p)->s; p = &(*p)->next) off -= (*p)->e - (*p)->s; assert(*p != nil || off == 0); if(off){ ed->dostart = malloc(sizeof(Piece)); if(ed->dostart == nil) goto hungry; ed->dostart->next = ed->doend = (*p)->next; ed->dostart->s = (*p)->s + off; ed->dostart->e = (*p)->e; }else ed->dostart = ed->doend = *p; if(add != nil){ ed->doto += add->e - add->s >> 2; add->next = ed->dostart; ed->dostart = add; } if(tmp != nil){ tmp->next = ed->dostart; ed->dostart = tmp; } histfwd(); return; hungry: *t = 0; errstr(t, sizeof t); enter(t, nil, 0, mc, kc, nil); } void mix(Piece *new){ Piece *p; vlong n; uchar *b; if(new == nil) return; for(p = head, n = pos * 4; n >= p->e - p->s; p = p->next) n -= p->e - p->s; b = p->s + n; while(p != nil && new->s < new->e){ n = (s16int)(b[0] | b[1] << 8) + (s16int)(new->s[0] | new->s[1] << 8); if(n < -0x8000) n = -0x8000; if(n > 0x7fff) n = 0x7fff; new->s[0] = n; new->s[1] = n >> 8; b += 2; if(b == p->e) p = p->next; new->s += 2; } n = new->s - new->data >> 2; new->s = new->data; edit(pos, n, new); } void threadmain(int argc, char **argv){ Channel *pidc, *waitc; Waitmsg *w; Mark *m, *mhead = nil; Piece *p; Rune k; int fd; vlong n; uchar *b; Menu mb3 = {b3, nil, 0}; enum{RESIZE, MOUSE, KEYBOARD, PLAYING}; Alt a[] = { [RESIZE]{nil, nil, CHANRCV}, [MOUSE]{nil, nil, CHANRCV}, [KEYBOARD]{nil, &k, CHANRCV}, [PLAYING]{nil, nil, CHANEND} }; while(--argc){ fd = open(argv[argc], OREAD); if(fd < 0) sysfatal("open: %r"); p = slurp(fd); if(p == nil) sysfatal(t); p->next = head; head = p; len += p->e - p->s >> 2; } if(initdraw(nil, nil, argv[0]) < 0) sysfatal("initdraw: %r"); bg = allocimagemix(display, DPalebluegreen, DWhite); fg = display->black; hl = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DPurpleblue); if(bg == nil || fg == nil || hl == nil) sysfatal("allocimage: %r"); if((mc = initmouse(nil, screen)) == nil) sysfatal("initmouse: %r"); a[RESIZE].c = mc->resizec; a[MOUSE].c = mc->c; a[MOUSE].v = &mc->Mouse; if((kc = initkeyboard(nil)) == nil) sysfatal("initkeyboard: %r"); a[KEYBOARD].c = kc->c; pidc = chancreate(sizeof(ulong), 1); waitc = threadwaitchan(); for(zoom = len / Dx(screen->r); zoom & zoom - 1; zoom &= zoom - 1) ; if(!(zoom <<= 1)) zoom = 1; sendul(mc->resizec, 1); while(9)switch(alt(a)){ default: sysfatal("stercorari ad murum progredere"); case RESIZE: if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); redraw(); break; case MOUSE: switch(mc->buttons){ case 1: pos = vis + (mc->xy.x - screen->r.min.x) * zoom; if(pos > len) pos = len; if(pos < 0) pos = 0; drawprog(); default: continue; case 2: n = vis; fd = mc->xy.x; readmouse(mc); while(mc->buttons){ vis = n + (fd - mc->xy.x) * zoom; if(vis < 0) vis = 0; if(vis > len) vis = len; redraw(); readmouse(mc); } continue; case 4: n = menuhit(3, mc, &mb3, nil); if(n < 0) continue; k = *b3[n]; if(k != 'q') break; setcursor(mc, &skull); while(alt(a) != MOUSE || !mc->buttons) ; if(mc->buttons == 4) threadexitsall(nil); setcursor(mc, nil); continue; case 8: k = Kdown; break; case 16: k = Kup; break; } case KEYBOARD: switch(k){ case '\n': pos = from; case ' ': case 'p': if(len == 0) break; fd = open("/dev/audio", OWRITE); if(fd < 0){ *t = 0; errstr(t, sizeof t); enter(t, nil, 0, mc, kc, nil); break; } a[PLAYING].op = CHANNOBLK; if(pos == len) pos = 0; k = from < to && pos >= from && pos < to; for(p = head, n = pos * 4; n >= p->e - p->s; p = p->next) n -= p->e - p->s; b = p->s + n; while(fd >= 0)switch(alt(a)){ default: sysfatal("si presus fueris poena patiare necese est caue"); case RESIZE: if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); redraw(); break; case MOUSE: if(!mc->buttons) break; case KEYBOARD: close(fd); fd = -1; break; case PLAYING: n = (k && pos + 4410 > to ? to - pos : 4410) << 2; if(p->e - b < n) n = p->e - b; if(write(fd, b, n) != n){ *t = 0; errstr(t, sizeof t); close(fd); fd = -1; enter(t, nil, 0, mc, kc, nil); break; } drawprog(); pos += n >> 2; if(k && pos == to){ pos = from; n = pos << 2; for(p = head; n >= p->e - p->s; p = p->next) n -= p->e - p->s; b = p->s + n; }else if(b + n == p->e){ p = p->next; if(p == nil){ close(fd); fd = -1; break; } b = p->s; }else b += n; break; } a[PLAYING].op = CHANEND; drawprog(); continue; case Kup: case '=': case '+': case ']': if(zoom > 1) zoom >>= 1; break; case Kdown: case '-': case '_': case '[': if(zoom < 0x400000) zoom <<= 1; break; case Khome: pos = from < to ? from : 0; centre(); continue; case Kend: pos = from < to ? to : len; centre(); continue; case Kright: case Kpgdown: n = zoom * Dx(screen->r); if(len < vis + n) continue; vis += n; break; case Kleft: case Kpgup: vis -= zoom * Dx(screen->r); if(vis < 0) vis = 0; break; case 'a': from = 0; to = len; break; case Kesc: case 'n': from = to = pos; break; case 's': from = pos; if(to < from) to = from; break; case 'e': to = pos; if(from > to) from = to; break; case 'm': *t = 0; if(enter(b3[5], t, sizeof t, mc, kc, nil) < 1) continue; for(m = mhead; m != nil && strcmp(t, m->name); m = m->next) ; if(m == nil){ m = malloc(sizeof(Mark)); if(m == nil) goto ☢; m->name = strdup(t); if(m->name == nil){ free(m); goto ☢; } m->next = mhead; mhead = m; } if(from < to){ m->from = from; m->to = to; }else m->from = m->to = pos; continue; case 'j': *t = 0; if(enter(b3[6], t, sizeof t, mc, kc, nil) < 1) continue; for(m = mhead; m != nil && strcmp(t, m->name); m = m->next) ; if(m == nil){ enter("no selection marked with that name", nil, 0, mc, kc, nil); continue; } pos = from = m->from < len ? m->from : len; to = m->to < len ? m->to : len; break; case '>': /* ignore stdout */ n = 7; if(0) case '|': /* replace selection */ n = 8; if(0) case '<': /* insert at position */ n = 9; if(0) case 'x': /* mix from position */ n = 10; *t = 0; if(enter(b3[n], t, sizeof t, mc, kc, nil) < 1) continue; if(pipe(pfd) < 0) goto ☢; setcursor(mc, &busy); if(procrfork(rcproc, pidc, mainstacksize, RFFDG|RFNOTEG|RFNAMEG) < 0 || recvul(pidc) == ~0){ close(pfd[0]); ☠: close(pfd[1]); setcursor(mc, nil); ☢: *t = 0; errstr(t, sizeof t); enter(t, nil, 0, mc, kc, nil); continue; } close(pfd[0]); if(from < to && procrfork(outproc, nil, mainstacksize, RFFDG) < 0) goto ☠; if(k != '>') p = slurp(pfd[1]); else{ p = nil; close(pfd[1]); } w = recvp(waitc); if(w != nil && *w->msg){ enter(w->msg, nil, 0, mc, kc, nil); free(p); }else if(k == '|') edit(from, to - from, p); else if(k == '<') edit(pos, to - from, p); else if(k == 'x') mix(p); free(w); setcursor(mc, nil); continue; case 'u': histbwd(); continue; case 'r': histfwd(); continue; case 'q': setcursor(mc, &skull); while(alt(a) != KEYBOARD) ; if(k == 'q') case Keof: threadexitsall(nil); setcursor(mc, nil); default: continue; } redraw(); break; } }