ref: 71b0c44a7b2d2b878ba521bffb2f550c9b4e0550
dir: /fplay.c/
#include <u.h> #include <libc.h> #include <thread.h> #include <draw.h> #include <mouse.h> #include <keyboard.h> #include "dat.h" #include "fns.h" /* FIXME: labeled axes */ /* FIXME: midi y scale */ /* FIXME: optimization: stereo s16int fft (real integral 16-bit fft) */ enum{ Rate = 44100, Ndelay = Rate / 25, Nchunk = Ndelay * 4, Csamp = 0, Cft, Cline, Cloop, Ncol }; int cat; int T, stereo, scale = 1; Rectangle liner; Point pan, statp; double freq[128]; int nfft = 1<<10; int *fftb; ulong nbuf; uchar *buf, *bufp, *bufe, *viewp, *viewe, *viewmax, *loops, *loope; Image *viewbg, *view, *col[Ncol]; Keyboardctl *kc; Mousectl *mc; QLock lck; u32int cmapa[] = { 0x000000ff, 0x000003ff, 0x000005ff, 0x000008ff, 0x00000bff, 0x00000dff, 0x000010ff, 0x000013ff, 0x000015ff, 0x000018ff, 0x00001bff, 0x00001dff, 0x000020ff, 0x000022ff, 0x000025ff, 0x000027ff, 0x00002aff, 0x00002cff, 0x00002fff, 0x000031ff, 0x000034ff, 0x000036ff, 0x000039ff, 0x00003bff, 0x00003dff, 0x000040ff, 0x000042ff, 0x000044ff, 0x000047ff, 0x000049ff, 0x00004bff, 0x00004dff, 0x00004fff, 0x010051ff, 0x040053ff, 0x070055ff, 0x090057ff, 0x0c0059ff, 0x0f005bff, 0x11005dff, 0x14005fff, 0x170061ff, 0x190062ff, 0x1c0064ff, 0x1f0066ff, 0x210067ff, 0x240069ff, 0x27006aff, 0x29006cff, 0x2c006dff, 0x2e006eff, 0x310070ff, 0x340071ff, 0x360072ff, 0x390073ff, 0x3c0074ff, 0x3e0076ff, 0x410077ff, 0x430078ff, 0x460078ff, 0x480079ff, 0x4b007aff, 0x4e007bff, 0x50007bff, 0x53007cff, 0x55007dff, 0x58007dff, 0x5a007eff, 0x5d007eff, 0x5f007eff, 0x62007fff, 0x64007fff, 0x66007fff, 0x69007fff, 0x6b007fff, 0x6e0080ff, 0x70007fff, 0x73007fff, 0x75007fff, 0x77007fff, 0x7a007fff, 0x7c007eff, 0x7e007eff, 0x81007eff, 0x83007dff, 0x85007dff, 0x88007cff, 0x8a007bff, 0x8c007bff, 0x8e007aff, 0x900079ff, 0x930078ff, 0x950078ff, 0x970077ff, 0x990076ff, 0x9b0074ff, 0x9d0073ff, 0x9f0072ff, 0xa20071ff, 0xa40070ff, 0xa6006eff, 0xa8006dff, 0xaa006cff, 0xac006aff, 0xae0069ff, 0xb00067ff, 0xb10066ff, 0xb30064ff, 0xb50062ff, 0xb70061ff, 0xb9005fff, 0xbb005dff, 0xbd005bff, 0xbe0059ff, 0xc00057ff, 0xc20055ff, 0xc40053ff, 0xc50051ff, 0xc7004fff, 0xc9004dff, 0xca004bff, 0xcc0049ff, 0xce0047ff, 0xcf0044ff, 0xd10042ff, 0xd20040ff, 0xd4003dff, 0xd5003bff, 0xd70039ff, 0xd80036ff, 0xd90034ff, 0xdb0031ff, 0xdc002fff, 0xde002cff, 0xdf002aff, 0xe00027ff, 0xe10025ff, 0xe30022ff, 0xe40020ff, 0xe5001dff, 0xe6001bff, 0xe70018ff, 0xe80015ff, 0xe90013ff, 0xeb0010ff, 0xec000dff, 0xed000bff, 0xee0008ff, 0xef0003ff, 0xef0005ff, 0xf00000ff, 0xf10500ff, 0xf20a00ff, 0xf30f00ff, 0xf41500ff, 0xf41a00ff, 0xf51f00ff, 0xf62400ff, 0xf72900ff, 0xf72e00ff, 0xf83300ff, 0xf93800ff, 0xf93d00ff, 0xfa4200ff, 0xfa4700ff, 0xfb4c00ff, 0xfb5100ff, 0xfc5600ff, 0xfc5b00ff, 0xfc6000ff, 0xfd6500ff, 0xfd6900ff, 0xfd6e00ff, 0xfe7300ff, 0xfe7700ff, 0xfe7c00ff, 0xfe8000ff, 0xff8500ff, 0xff8900ff, 0xff8d00ff, 0xff9200ff, 0xff9600ff, 0xff9a00ff, 0xff9e00ff, 0xffa200ff, 0xffa600ff, 0xffaa00ff, 0xffae00ff, 0xffb200ff, 0xffb500ff, 0xffb900ff, 0xffbc00ff, 0xffc000ff, 0xffc300ff, 0xffc600ff, 0xffca00ff, 0xffcd05ff, 0xffd009ff, 0xffd30eff, 0xffd613ff, 0xffd817ff, 0xffdb1cff, 0xffde20ff, 0xffe025ff, 0xffe32aff, 0xffe52eff, 0xffe733ff, 0xffe938ff, 0xffeb3cff, 0xffed41ff, 0xffef46ff, 0xfff14aff, 0xfff34fff, 0xfff453ff, 0xfff658ff, 0xfff75dff, 0xfff861ff, 0xfff966ff, 0xfffa6bff, 0xfffb6fff, 0xfffc74ff, 0xfffd79ff, 0xfffd7dff, 0xfffe82ff, 0xfffe86ff, 0xffff8bff, 0xffff90ff, 0xffff94ff, 0xffff99ff, 0xffff9eff, 0xffffa2ff, 0xffffa7ff, 0xffffacff, 0xffffb0ff, 0xffffb5ff, 0xffffb9ff, 0xffffbeff, 0xffffc3ff, 0xffffc7ff, 0xffffccff, 0xffffd1ff, 0xffffd5ff, 0xffffdaff, 0xffffdfff, 0xffffe3ff, 0xffffe8ff, 0xffffecff, 0xfffff1ff, 0xfffff6ff, 0xfffffaff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; Image *cmap[nelem(cmapa)]; Image * eallocimage(Rectangle r, int repl, ulong col) { Image *i; if((i = allocimage(display, r, screen->chan, repl, col)) == nil) sysfatal("allocimage: %r"); return i; } 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); } void update(void) { int x; x = screen->r.min.x + (bufp - viewp) / 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); } void athread(void *) { int n, fd; if((fd = cat ? 1 : open("/dev/audio", OWRITE)) < 0) sysfatal("open: %r"); for(;;){ qlock(&lck); n = bufp + Nchunk >= loope ? loope - bufp : Nchunk; if(write(fd, bufp, n) != n) break; bufp += n; if(bufp >= loope) bufp = loops; update(); qunlock(&lck); yield(); } close(fd); } void dofft(void) { int n, *cp; double *fp, *u, f; uchar *p; free(fftb); if((fftb = mallocz(T * nfft/2 * sizeof *fftb, 1)) == nil) sysfatal("mallocz: %r"); if((u = mallocz(nfft * sizeof *u, 1)) == nil) sysfatal("mallocz: %r"); cp = fftb; p = buf; n = nfft; while(p < bufe){ if(n > (bufe - p) / 4) n = (bufe - p) / 4; memset(u, 0, nfft * sizeof *u); for(fp=u; fp<u+n; fp++, p+=4) *fp = (s64int)(s16int)(p[1] << 8 | p[0]); realft(u, nfft, 1); for(fp=u; fp<u+nfft; fp+=2){ if(fp == u) f = u[0]; else if(fp == u+nfft-2) f = u[1]; else f = sqrt(fp[0] * fp[0] + fp[1] * fp[1]); if(f < 0.0) f = -f; f /= nfft; f *= 2; if(f != 0.0) f = 20 * log10(f / 32768.); else f = -100; if(f < -100.) f = -100.; else if(f > -20) f = -20; f += 100; f *= 255. / 80; *cp++ = f; } } free(u); } void drawfft(void) { int i, *cp; Rectangle r, r2; r = rectaddpt(viewbg->r, pan); if(rectclip(&r, rectsubpt(screen->r, screen->r.min)) == 0) return; r2 = r; if(r2.max.x > T) r2.max.x = T; if(r2.max.y > nfft>>1) r2.max.y = nfft>>1; while(r.min.x < r2.max.x){ r.max.x = r.min.x + scale; r.min.y = r2.min.y; cp = fftb + nfft/2*r.min.x + r.min.y; while(r.min.y < r2.max.y){ r.max.y = r.min.y + scale; assert(cp >= fftb); assert(cp < fftb + T * nfft/2); i = *cp++; assert(i >= 0); assert(i < 256); draw(viewbg, r, cmap[i], nil, ZP); r.min.y = r.max.y; } r.min.x = r.max.x; } } void drawview(void) { int x; Rectangle r; draw(view, view->r, display->black, nil, ZP); draw(view, view->r, viewbg, nil, pan); //draw(view, view->r, viewbg, nil, addpt(ZP, Δview)); if(loops != buf && loops >= viewp){ x = (loops - viewp) / 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) / T; r = view->r; r.min.x += x; r.max.x = r.min.x + 1; draw(view, r, col[Cloop], nil, ZP); } draw(screen, screen->r, view, nil, ZP); draw(screen, liner, col[Cline], nil, ZP); drawstat(); flushimage(display, 1); } enum{ Rall, Rzoom, Rpan, }; void redrawbg(int type) { int w, x; Rectangle r, midr; T = 1 + nbuf / scale / nfft; 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) / T; liner = screen->r; liner.min.x = x; liner.max.x = x + 1; if(type == Rpan) goto end; if(type == Rall){ dofft(); freeimage(view); r = rectsubpt(screen->r, screen->r.min); view = eallocimage(r, 0, DBlack); } freeimage(viewbg); viewbg = eallocimage(rectsubpt(screen->r, screen->r.min), 0, DBlack); if(type > Rall) goto end; if(stereo){ midr = r; midr.min.y = midr.max.y / 2; midr.max.y = midr.min.y + 1; draw(viewbg, midr, col[Csamp], nil, ZP); statp = Pt(screen->r.min.x, screen->r.min.y + (Dy(screen->r) - font->height) / 2 + 1); }else statp = Pt(screen->r.min.x, screen->r.max.y - font->height); end: if(type <= Rzoom) drawfft(); drawview(); } void writepcm(int pause) { int fd, n, sz; char path[256]; uchar *p; memset(path, 0, sizeof path); if(!pause) qlock(&lck); n = enter("path:", path, sizeof(path)-UTFmax, mc, kc, nil); if(!pause) qunlock(&lck); if(n < 0) return; if((fd = create(path, OWRITE, 0664)) < 0){ fprint(2, "create: %r\n"); return; } if((sz = iounit(fd)) == 0) sz = 8192; for(p=loops; p<loope; p+=sz){ n = loope - p < sz ? loope - p : sz; if(write(fd, p, n) != n){ fprint(2, "write: %r\n"); goto end; } } end: close(fd); } void setnfft(int n) { if(n < 1<<2 || n > 1<<26) return; nfft = n; redrawbg(Rall); } void setscale(int n) { if(n < 1 || n > 1<<30) return; scale = n; redrawbg(Rzoom); } void setpan(int dx, int dy) { Rectangle r; r = rectaddpt(viewbg->r, pan); if(dx > 0){ r.max.x = r.min.x + dx; draw(screen, r, display->black, nil, ZP); }else if(dx < 0){ r.min.x = r.max.x + dx; draw(screen, r, display->black, nil, ZP); } r = rectaddpt(viewbg->r, pan); if(dy > 0){ r.max.y = r.min.y + dy; draw(screen, r, display->black, nil, ZP); }else if(dy < 0){ r.min.y = r.max.y + dy; draw(screen, r, display->black, nil, ZP); } pan.x += dx; pan.y += dy; redrawbg(Rpan); } void setloop(void) { int n; uchar *p; n = (mc->xy.x - screen->r.min.x) * nfft * 4; p = viewp + n; if(p < buf || p > bufe) return; if(p < bufp) loops = p; else loope = p; drawview(); } void setpos(void) { int n; uchar *p; n = (mc->xy.x - screen->r.min.x) * nfft * 4; p = viewp + n; if(p < loops || p > loope - Nchunk) return; bufp = p; update(); } void bufrealloc(ulong n) { int off; off = bufp - buf; if((buf = realloc(buf, n)) == nil) sysfatal("realloc: %r"); bufe = buf + n; bufp = buf + off; } void usage(void) { fprint(2, "usage: %s [-cs] [pcm]\n", argv0); threadexits("usage"); } void threadmain(int argc, char **argv) { int n, dx, dy, sz, fd, pause; double f; Mouse mo; Rune r; ARGBEGIN{ case 'c': cat = 1; break; case 's': stereo = 1; break; default: usage(); }ARGEND if((fd = *argv != nil ? open(*argv, OREAD) : 0) < 0) sysfatal("open: %r"); if(sz = iounit(fd), sz == 0) sz = 8192; bufrealloc(nbuf += 4*1024*1024); while((n = read(fd, bufp, sz)) > 0){ bufp += n; if(bufp + sz >= bufe) bufrealloc(nbuf += 4*1024*1024); } if(n < 0) sysfatal("read: %r"); close(fd); bufrealloc(nbuf = bufp - buf); nbuf /= 4; bufp = buf; if(initdraw(nil, nil, "fplay") < 0) sysfatal("initdraw: %r"); if(kc = initkeyboard(nil), kc == nil) sysfatal("initkeyboard: %r"); if(mc = initmouse(nil, screen), mc == nil) sysfatal("initmouse: %r"); col[Csamp] = eallocimage(Rect(0,0,1,1), 1, 0x440000FF); col[Cft] = eallocimage(Rect(0,0,1,1), 1, 0x660000FF); col[Cline] = eallocimage(Rect(0,0,1,1), 1, 0x884400FF); col[Cloop] = eallocimage(Rect(0,0,1,1), 1, 0x777777FF); for(n=0; n<nelem(cmapa); n++) cmap[n] = eallocimage(Rect(0,0,1,1), 1, cmapa[n]); viewp = buf; loops = buf; loope = bufe; redrawbg(Rall); f = pow(2, 1./12); for(n=0; n<nelem(freq); n++) freq[n] = 440 * pow(f, n - 69); pause = 0; dx = dy = 0; Alt a[] = { {mc->resizec, nil, CHANRCV}, {mc->c, &mc->Mouse, CHANRCV}, {kc->c, &r, CHANRCV}, {nil, nil, CHANEND} }; if(threadcreate(athread, nil, mainstacksize) < 0) sysfatal("threadcreate: %r"); for(;;){ switch(alt(a)){ case 0: if(getwindow(display, Refnone) < 0) sysfatal("resize failed: %r"); redrawbg(Rall); mo = mc->Mouse; break; case 1: switch(mc->buttons){ case 0: if(dx != 0 || dy != 0) redrawbg(Rzoom); dx = dy = 0; break; case 1: setpos(); break; case 2: setloop(); break; case 4: dx = mo.xy.x - mc->xy.x; dy = mo.xy.y - mc->xy.y; setpan(dx, dy); break; case 8: setscale(scale << 1); break; case 16: setscale(scale >> 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 Kdel: case 'q': threadexitsall(nil); case 'z': if(scale == 1) break; scale = 1; pan = (Point){0, 0}; redrawbg(Rzoom); break; case '9': setnfft(nfft >> 1); break; case '0': setnfft(nfft << 1); break; case '-': setscale(scale >> 1); break; case '=': case '+': setscale(scale << 1); break; case 'w': writepcm(pause); break; } break; } } }