shithub: fplay

Download patch

ref: 71b0c44a7b2d2b878ba521bffb2f550c9b4e0550
author: qwx <[email protected]>
date: Mon May 27 19:30:04 EDT 2019

initial import

--- /dev/null
+++ b/fft.c
@@ -1,0 +1,100 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+/* from numerical recipes 2nd and 3rd ed */
+
+#define SWAP(a,b)	{tempr=(a); (a)=(b); (b)=tempr;}
+
+void
+four1(double *d, int n, int isign)
+{
+	int nn, mmax, m, j, istep, i;
+	double wtemp, wr, wpr, wpi, wi, theta, tempr, tempi;
+
+	if(n < 2 || n & n-1)
+		sysfatal("non power of two");
+	nn = n << 1;
+	j = 1;
+	for(i=1; i<nn; i+=2){
+		if(j > i){
+			SWAP(d[j-1], d[i-1]);
+			SWAP(d[j], d[i]);
+		}
+		m = n;
+		while(m >= 2 && j > m){
+			j -= m;
+			m >>= 1;
+		}
+		j += m;
+	}
+	mmax = 2;
+	while(nn > mmax){
+		istep = mmax << 1;
+		theta = isign * (6.28318530717959 / mmax);
+		wtemp = sin(0.5 * theta);
+		wpr = -2.0 * wtemp * wtemp;
+		wpi = sin(theta);
+		wr = 1.0;
+		wi = 0.0;
+		for(m=1; m<mmax; m+=2){
+			for(i=m; i<=nn; i+=istep){
+				j = i + mmax;
+				tempr = wr * d[j-1] - wi * d[j];
+				tempi = wr * d[j] + wi * d[j-1];
+				d[j-1] = d[i-1] - tempr;
+				d[j] = d[i] - tempi;
+				d[i-1] += tempr;
+				d[i] += tempi;
+			}
+			wr = (wtemp = wr) * wpr - wi * wpi + wr;
+			wi = wi * wpr + wtemp * wpi + wi;
+		}
+		mmax = istep;
+	}
+}
+
+void
+realft(double *d, int n, int isign)
+{
+	int i, i1, i2, i3, i4;
+	double c1 = 0.5, c2, h1r, h1i, h2r, h2i, wr, wi, wpr, wpi, wtemp;
+	double theta = 3.141592653589793238 / (double)(n >> 1);
+
+	if(isign == 1){
+		c2 = -0.5;
+		four1(d, n>>1, 1);
+	}else{
+		c2 = 0.5;
+		theta = -theta;
+	}
+	wtemp = sin(0.5 * theta);
+	wpr = -2.0 * wtemp * wtemp;
+	wpi = sin(theta);
+	wr = 1.0 + wpr;
+	wi = wpi;
+	h1r = 0;
+	for(i=1; i<n>>2; i++){
+		i2 = 1 + (i1 = i+i);
+		i4 = 1 + (i3 = n - i1);
+		h1r = c1 * (d[i1] + d[i3]);
+		h1i = c1 * (d[i2] - d[i4]);
+		h2r = -c2 * (d[i2] + d[i4]);
+		h2i = c2 * (d[i1] - d[i3]);
+		d[i1] = h1r + wr * h2r - wi * h2i;
+		d[i2] = h1i + wr * h2i + wi * h2r;
+		d[i3] = h1r - wr * h2r + wi * h2i;
+		d[i4] = -h1i + wr * h2i + wi * h2r;
+		wr = (wtemp = wr) * wpr - wi * wpi + wr;
+		wi = wi * wpr + wtemp * wpi + wi;
+	}
+	if(isign == 1){
+		d[0] = (h1r = d[0]) + d[1];
+		d[1] = h1r - d[1];
+	}else{
+		d[0] = c1 * ((h1r == d[0]) + d[1]);
+		d[1] = c1 * (h1r - d[1]);
+		four1(d, n>>1, -1);
+	}
+}
--- /dev/null
+++ b/fns.h
@@ -1,0 +1,2 @@
+void	four1(double*, int, int);
+void	realft(double*, int, int);
--- /dev/null
+++ b/fplay.c
@@ -1,0 +1,538 @@
+#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;
+		}
+	}
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,6 @@
+</$objtype/mkfile
+BIN=$home/bin/$objtype
+TARG=fplay
+OFILES=fplay.$O fft.$O
+HFILES=dat.h fns.h
+</sys/src/cmd/mkone