#include #include #include #include #include #include #include typedef struct Block Block; typedef struct Window Window; typedef struct Global Global; struct Block { Block *next, *prev; Rectangle r; int vol; }; struct Window { Rectangle r; void (*redraw)(Window *w); void (*click)(Window *w); }; struct Global { int x, y; int x1, x2; int z; int snap; int y2; int h; int vol; Rectangle r; }; enum { Wtext, Wbox, Wscrollx, Wscrolly, Wmain, Wlast }; enum { Stackmax = 32*1024, Volumemax = 127, Maxargs = 8, Snapbits = 4, Spacingbits = 7, Barbits = 16, Beatbits = 12, Fragbits = 8, Maxnames = 256, }; enum { Mplay, Mrepeat, Mstop, Mload, Msave, Mzoomin, Mzoomout, Mbeg, Mend, Mquit, }; enum { Mmute, }; enum { Cback, Cline, Ctext, Cgrid0, Cgrid1, Cgrid2, Cgrid3, }; char *mainitems[] = { [Mplay] "play", [Mrepeat] "repeat", [Mstop] "stop", [Mload] "load", [Msave] "save", [Mzoomin] "zoom+", [Mzoomout] "zoom-", [Mbeg] "beg", [Mend] "end", [Mquit] "quit", 0, }; char *leftitems[] = { [Mmute] "mute", 0, }; Menu mainmenu = { mainitems }; Menu leftmenu = { leftitems }; Mousectl *mctl; Keyboardctl *kctl; Mouse m; Channel *keyreqchan, *readlinechan; Block blocks, snarf, selected; Image *col[8], *backdrop; Image *selcol[10], *unselcol[10]; Font *font; char textline[1024]; char *names[Maxnames]; int nnames; Global g; extern Window win[Wlast]; #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) void redrawall(void); void finish(void); void redrawwin(int n); void redraw(void); void info(char *msg, ...); /* * Block operations */ void bunlink(Block *b) { b->prev->next = b->next; b->next->prev = b->prev; } void binsert(Block *h, Block *b) { b->next = h->next; b->prev = h; h->next->prev = b; h->next = b; } void binit(Block *h) { h->next = h; h->prev = h; } Block * bnew(Block *h, Block *old) { Block *b; b = mallocz(sizeof(*b), 1); if(old) *b = *old; if(h) binsert(h, b); return b; } void bdelete(Block *b) { bunlink(b); free(b); } Block * blookup(Block *h, Point a) { Block *b; for(b=h->next; b != h; b=b->next) if(ptinrect(a, b->r)) return b; return nil; } void bselect(Block *h, Block *hs, Rectangle r) { Block *b, *next; for(b=h->next; b != h; b=next){ next = b->next; if(rectXrect(r, b->r)){ bunlink(b); binsert(hs, b); } } } int blen(Block *h) { int len; Block *b; for(len=0, b=h->next; b != h; b=b->next) len++; return len; } void bjoin(Block *h1, Block *h2) { Block *a, *b; if(h2->prev == h2) return; a = h2->next; b = h2->prev; a->prev = h1->prev; b->next = h1; h1->prev->next = a; h1->prev = b; binit(h2); } void bshift(Block *h, Point pos) { Block *b; for(b=h->next; b != h; b=b->next) b->r = rectaddpt(b->r, pos); } void bsetvol(Block *h, int vol) { Block *b; for(b=h->next; b != h; b=b->next) b->vol = vol; } Point bmin(Block *h) { Point p; Block *b; if(blen(h) == 0) return Pt(0, 0); p = h->next->r.min; for(b=h->next->next; b != h; b=b->next){ p.x = MIN(p.x, b->r.min.x); p.y = MIN(p.y, b->r.min.y); } return p; } void bdup(Block *hd, Block *hs) { Block *b; for(b=hs->next; b != hs; b=b->next) bnew(hd, b); } void bkill(Block *h) { Block *b, *next; for(b=h->next; b != h; b=next){ next = b->next; free(b); } binit(h); } int bcompar(void *a1, void *a2) { Block **b1, **b2; b1 = a1; b2 = a2; if((*b1)->r.min.x < (*b2)->r.min.x) return -1; if((*b1)->r.min.x > (*b2)->r.min.x) return 1; return 0; } Block ** bsortblock(Block *h, int *nn) { int n, i; Block **bb, *b; n = blen(h); if(n == 0) return nil; bb = malloc(sizeof(*bb) * n); for(b=h->next, i=0; b != h; b=b->next, i++) bb[i] = b; qsort(bb, n, sizeof(*bb), bcompar); *nn = n; return bb; } void bramp(Block *h) { Block **b; int n, i, a, k; b = bsortblock(h, &n); if(b == nil) return; a = b[0]->vol; k = b[n-1]->vol - b[0]->vol; for(i=0; ivol = a + k * i / (n - 1); free(b); } /* * Input & Output */ int writelist(int fd, Block *h, int x1, int x2, int all) { Block *b; char *s, buf[32]; Point p; int n, t, vol; for(b=h->next, n=0; b != h; b=b->next, n++){ p = b->r.min; if(all == 0){ if(p.x < x1 || p.x >= x2) continue; t = p.x - x1; }else{ t = p.x; } vol = (b->vol + 1) * Volumemax / 10; if(p.y >= nnames){ sprint(buf, "%d", p.y); s = buf; }else s = names[p.y]; fprint(fd, "%d %s %d %d\n", t, s, vol, Dx(b->r)); } return n; } int parseline(Block *b, char *line, int no) { char *argv[Maxargs]; int argc, i; int s, t, l; argc = tokenize(line, argv, Maxargs); if(argc == 0) return 0; if(argv[0][0] == '#') return 0; if(argc < 4) goto err; s = nnames; for(i=0; ivol = strtol(argv[2], 0, 0) * 10 / (Volumemax + 1); t = strtol(argv[0], 0, 0); l = strtol(argv[3], 0, 0); b->r = Rect(t, s, t+l, s+1); return 1; err: info("parser error on line %d", no); return -1; } int readlist(int fd, Block *h) { char *line; int no, n, ret; Block b; Biobuf in; Binit(&in, fd, OREAD); for(no=1, n=0; (line = Brdline(&in, '\n')) != nil; no++){ line[Blinelen(&in)-1] = 0; ret = parseline(&b, line, no); if(ret < 0){ n = -1; break; } if(ret > 0 && Dx(b.r) > 0){ bnew(h, &b); n++; } } Bterm(&in); return n; } /* * More */ Image * color(int col) { return allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, (col << 8) | 0xff); } void update(void) { flushimage(display, 1); } int clamp(int x, int a, int b) { return x < a ? a : x > b ? b : x; } void posfmt(char *s, int x, int z) { int bar, beat, frag, xx; xx = x; bar = x >> Barbits; x -= bar << Barbits; beat = x >> Beatbits; x -= beat << Beatbits; frag = x >> Fragbits; // x -= frag << Fragbits; if(z >= Barbits) sprint(s, "%X", bar); else if(z >= Beatbits) sprint(s, "%X.%X", bar, beat); else if(z >= Fragbits) sprint(s, "%X.%X.%X", bar, beat, frag); else sprint(s, "#%X", xx); } int highbits(int x, int b) { return x >> b << b; } Point pixtoshr(Point p, int snap) { p = subpt(p, g.r.min); p.x = g.x + (p.x << g.z); p.y = (g.y + p.y) / g.h; if(snap) p.x -= p.x % g.snap; return p; } Point shrtopix(Point p) { p.x = (p.x - g.x) >> g.z; p.y = p.y * g.h - g.y; return addpt(p, g.r.min); } Rectangle pixtorect(Point p, Point q) { Rectangle r; r.min = pixtoshr(p, 1); r.max = pixtoshr(q, 1); r = canonrect(r); r.max.x += g.snap; r.max.y += 1; return r; } void on(Block *b, Image *col) { Rectangle r; r.min = shrtopix(b->r.min); r.max = shrtopix(b->r.max); r.min.x += 1; r.min.y += 1; if(rectclip(&r, g.r)) draw(screen, r, col, nil, r.min); } void onvol(Block *b, Image **cols) { on(b, cols[b->vol]); } void onmany(Block *h, Image *col) { Block *b; for(b=h->next; b != h; b=b->next) on(b, col); } void onvolmany(Block *h, Image **cols) { Block *b; for(b=h->next; b != h; b=b->next) onvol(b, cols); } void drain(int what) { while((m.buttons & what) != 0) recv(mctl->c, &m); } void rebasesnarf(void) { Point p; p = bmin(&snarf); bshift(&snarf, subpt(ZP, p)); } void chord(Point p) { if(m.buttons & 2){ // cut chord bkill(&snarf); bshift(&selected, subpt(ZP, p)); bjoin(&snarf, &selected); redrawall(); drain(6); }else if(m.buttons & 4){ // paste chord bkill(&selected); bdup(&selected, &snarf); bshift(&selected, p); redrawall(); drain(6); } } void dragmove(void) { Point p, q; p = pixtoshr(m.xy, 1); redraw(); while(m.buttons != 0){ q = pixtoshr(m.xy, 1); if(!eqpt(p, q)){ onmany(&selected, backdrop); bshift(&selected, subpt(q, p)); p = q; redraw(); } recv(mctl->c, &m); chord(p); } rebasesnarf(); } Rectangle dragselect(void) { Point p, q; Rectangle r; p = m.xy; while(m.buttons != 0){ q = m.xy; r = pixtorect(p, q); bjoin(&blocks, &selected); bselect(&blocks, &selected, r); redraw(); recv(mctl->c, &m); chord(pixtoshr(p, 1)); } rebasesnarf(); return r; } Block dragcreate(void) { Point p, q; Block b; b.vol = g.vol; p = pixtoshr(m.xy, 1); while(m.buttons != 0){ q = pixtoshr(m.xy, 1); b.r = Rect(p.x, p.y, q.x + g.snap, p.y + 1); onvol(&b, selcol); redraw(); recv(mctl->c, &m); on(&b, backdrop); } return b; } char * readline(char *prompt) { sendp(keyreqchan, prompt); return recvp(readlinechan); } void writestate(int fd, int x1, int x2, int all) { int n; n = writelist(fd, &selected, x1, x2, all); n += writelist(fd, &blocks, x1, x2, all); fprint(fd, "%d stop 0 0\n", x2 - x1); info("written %d events", n); } void readstate(int fd) { Block b; int n; binit(&b); n = readlist(fd, &b); if(n >= 0){ bkill(&blocks); bkill(&selected); bjoin(&blocks, &b); info("readed %d events", n); }else{ bkill(&b); } } void playstate(char *type) { writestate(1, g.x1, g.x2, 0); fprint(1, "%s\n", type); } void mainclick(Window *) { Point p; Block *b, *s, bb; char *path; int d, fd; if(m.buttons & 1){ p = pixtoshr(m.xy, 0); s = blookup(&selected, p); b = blookup(&blocks, p); if(b != nil){ bjoin(&blocks, &selected); bunlink(b); binsert(&selected, b); redraw(); } if(b != nil || s != nil) dragmove(); else dragselect(); }else if(m.buttons & 2){ p = pixtoshr(m.xy, 0); bjoin(&blocks, &selected); b = blookup(&blocks, p); if(b){ on(b, backdrop); bdelete(b); redraw(); drain(7); }else{ bb = dragcreate(); if(Dx(bb.r) > 0) bnew(&blocks, &bb); redraw(); } }else if(m.buttons & 4){ p = pixtoshr(m.xy, 0); d = highbits(p.x, g.z + Spacingbits); switch(menuhit(3, mctl, &mainmenu, nil)){ case Mquit: finish(); break; case Mplay: playstate("play"); break; case Mrepeat: playstate("repeat"); break; case Mstop: fprint(1, "stop\n"); break; case Msave: path = readline("save"); if(path == nil) break; fd = create(path, OWRITE, 0666); if(fd < 0){ info("%r"); break; } writestate(fd, 0, 0, 1); close(fd); break; case Mload: path = readline("load"); if(path == nil) break; fd = open(path, OREAD); if(fd < 0){ info("%r"); break; } readstate(fd); close(fd); break; case Mzoomin: g.z = clamp(g.z - 1, 0, 18); break; case Mzoomout: g.z = clamp(g.z + 1, 0, 18); break; case Mbeg: g.x1 = d; break; case Mend: g.x2 = d + (1 << (g.z + Spacingbits)); break; default: return; } redrawall(); } } Image * makegrid(int w, int h, Image **col, int n) { Image *grid; Rectangle r; int i; r = Rect(0, 0, w, h*n); grid = allocimage(display, r, screen->chan, 1, DBlack); for(i=0; i<2; i++){ r = Rect(1, i*h+1, w, (i+1)*h); draw(grid, r, col[i], nil, ZP); } return grid; } void mainredraw(Window *w) { int gw, gh; Point p; Image *grid; if(backdrop) free(backdrop); backdrop = allocimage(display, w->r, screen->chan, 1, DBlack); gw = 1 << ((32 - g.z) % 4 + 4); gh = g.h; p = Pt((g.x >> g.z) % gw, g.y); grid = makegrid(gw, gh, &col[Cgrid0], 2); draw(backdrop, w->r, grid, nil, p); draw(screen, w->r, backdrop, nil, w->r.min); freeimage(grid); g.r = w->r; onvolmany(&blocks, selcol); onvolmany(&selected, unselcol); } void redraw(void) { onvolmany(&blocks, selcol); onvolmany(&selected, unselcol); update(); } void scrollxredraw(Window *w) { int x, x1, x2, k, a, b; char buf[32]; Rectangle r; Point p; /* draw background strip */ x1 = clamp((g.x1 - g.x) >> g.z, 0, Dx(w->r)); x2 = clamp((g.x2 - g.x) >> g.z, 0, Dx(w->r)); draw(screen, w->r, col[Cgrid2], nil, ZP); if(x2 > x1){ r = rectaddpt(Rect(x1, 0, x2, Dy(w->r)), w->r.min); draw(screen, r, col[Cgrid3], nil, ZP); } /* draw numbers */ a = g.x; b = g.x + (Dx(w->r) << g.z); k = 1 << (g.z + Spacingbits); for(x = a - a % k; x < b; x += k){ posfmt(buf, x, g.z + Spacingbits); p = addpt(w->r.min, Pt((x - g.x) >> g.z, 1)); string(screen, p, col[Ctext], ZP, font, buf); } } void scrollxclick(Window *w) { int d; d = (m.xy.x - w->r.min.x) << g.z; if(m.buttons & 4) g.x += d; else if(m.buttons & 1) g.x -= d; g.x = MAX(g.x, 0); redrawall(); drain(7); } void scrollyredraw(Window *w) { int i; Image *grid; grid = makegrid(Dx(w->r), g.h, &col[Cgrid2], 2); draw(screen, w->r, grid, nil, Pt(0, g.y)); for(i = g.y / g.h; i < nnames; i++) string(screen, addpt(w->r.min, Pt(1, 1 + i * g.h - g.y)), col[Ctext], ZP, font, names[i]); freeimage(grid); } void scrollyclick(Window *w) { int d; if(m.buttons & 2){ switch(menuhit(2, mctl, &leftmenu, nil)){ case Mmute: break; } }else{ d = m.xy.y - w->r.min.y; if(m.buttons & 4) g.y += d; else if(m.buttons & 1) g.y -= d; g.y = clamp(g.y, 0, g.y2); redrawall(); drain(7); } } void textredraw(Window *w) { draw(screen, w->r, col[Cline], nil, ZP); string(screen, w->r.min, col[Ctext], ZP, font, textline); } void nilredraw(Window *w) { draw(screen, w->r, col[Cgrid2], nil, ZP); } void info(char *msg, ...) { va_list arg; va_start(arg, msg); vsnprint(textline, sizeof(textline), msg, arg); va_end(arg); redrawwin(Wtext); } void redrawwin(int n) { Rectangle clip; Window *w; w = &win[n]; clip = screen->clipr; replclipr(screen, 0, w->r); if(w->redraw) w->redraw(w); replclipr(screen, 0, clip); update(); } void redrawall(void) { int a, b, c; int i; Window *w; Rectangle r, clip; r = screen->r; g.h = font->height; g.y2 = nnames * g.h; g.snap = 1 << (g.z + Snapbits); a = g.h; b = g.h; c = font->height * 8; win[Wtext].r = Rect(0, 0, Dx(r), a); win[Wbox].r = Rect(0, a, c, a + b); win[Wscrollx].r = Rect(c, a, Dx(r), a + b); win[Wscrolly].r = Rect(0, a + b, c, Dy(r)); win[Wmain].r = Rect(c, a + b, Dx(r), Dy(r)); clip = screen->clipr; for(i=0; ir = rectaddpt(w->r, r.min); if(w->redraw){ replclipr(screen, 0, w->r); w->redraw(w); } } replclipr(screen, 0, clip); update(); } Window win[Wlast] = { { .redraw = textredraw }, { .redraw = nilredraw }, { .redraw = scrollxredraw, .click = scrollxclick }, { .redraw = scrollyredraw, .click = scrollyclick }, { .redraw = mainredraw, .click = mainclick }, }; void mousethread(void *) { enum { Cresize, Cmouse, NCM }; Alt alts[NCM+1]; Window *w; int i; for(;;){ alts[Cmouse].c = mctl->c; alts[Cmouse].v = &m; alts[Cmouse].op = CHANRCV; alts[Cresize].c = mctl->resizec; alts[Cresize].v = nil; alts[Cresize].op = CHANRCV; alts[NCM].op = CHANEND; switch(alt(alts)){ case Cresize: if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); redrawall(); continue; case Cmouse: break; } if(m.buttons == 0) continue; for(i=0; ir)){ if(w->click != nil) w->click(w); break; } } } } char * kreadline(char *prompt) { Rune r; char buf[128]; char *s, *end; s = buf; *s = 0; end = buf + sizeof(buf) - 1; for(;;){ info("%s: %s", prompt, buf); recv(kctl->c, &r); switch(r){ case Kbs: if(s == buf) continue; s--; break; case '\n': *s = 0; if(s != buf) return strdup(buf); case Kdel: case Kesc: info("aborted"); return nil; default: if(s < end) *s++ = r; break; } *s = 0; } } void keyboardthread(void *) { enum { Ckey, Creq, NC }; Rune r; char *s; Alt alts[NC+1]; for(;;){ alts[Ckey].c = kctl->c; alts[Ckey].v = &r; alts[Ckey].op = CHANRCV; alts[Creq].c = keyreqchan; alts[Creq].v = &s; alts[Creq].op = CHANRCV; alts[NC].op = CHANEND; switch(alt(alts)){ case Ckey: break; case Creq: s = kreadline(s); sendp(readlinechan, s); continue; } if(r >= '0' && r <= '9'){ g.vol = r - '0'; bsetvol(&selected, g.vol); onvolmany(&selected, selcol); redraw(); continue; } switch(r){ case ' ': playstate("play"); break; case Kdel: case Kesc: case 'q': finish(); break; case 'n': info("blocks=%d, selected=%d\n", blen(&blocks), blen(&selected)); break; case 'r': bramp(&selected); redraw(); break; } } } void finish(void) { closedisplay(display); closekeyboard(kctl); closemouse(mctl); threadexitsall(nil); } void threadmain(int argc, char *argv[]) { int i; binit(&snarf); binit(&selected); binit(&blocks); newwindow(0); if(initdraw(nil, nil, argv[0]) < 0) sysfatal("initdraw: %r"); mctl = initmouse("/dev/mouse", display->image); if(mctl == nil) sysfatal("initmouse: %r"); kctl = initkeyboard("/dev/cons"); if(kctl == nil) sysfatal("initkeyboard: %r"); for(i=1; idefaultfont; g.z = Fragbits; g.vol = 9; g.x1 = 0; g.x2 = 1 << Barbits; redrawall(); keyreqchan = chancreate(sizeof(char *), 0); readlinechan = chancreate(sizeof(char *), 0); threadcreate(mousethread, mctl, Stackmax); threadcreate(keyboardthread, kctl, Stackmax); threadexits(nil); }