shithub: pitch

ref: 8803428fda6667b186eae4f20060744ed357e5a8
dir: pitch/pitch.c

View raw version
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include "kiss_fftr.h"

typedef struct Note Note;

struct Note {
	char *s;
	Image **ct;
	int f;
	Image **cl;
};

static s16int window[2048];
static kiss_fft_cpx freq[nelem(window)/2+1];
static Image *ct, *ct2, *cl, *cl2, *cf;
static Note notes[] = {
	{"A5", &ct, 880, &cl},
	{"G5", &ct2, 784, &cl2},
	{"F5", &ct2, 698, &cl2},
	{"E5", &ct2, 659, &cl2},
	{"D5", &ct2, 587, &cl2},
	{"C5", &ct2, 523, &cl2},
	{"B4", &ct2, 494, &cl2},
	{"A4", &ct, 440, &cl},
	{"G4", &ct2, 392, &cl2},
	{"F4", &ct2, 349, &cl2},
	{"E4", &ct2, 330, &cl2},
	{"D4", &ct2, 293, &cl2},
	{"C4", &ct2, 261, &cl2},
	{"B3", &ct2, 246, &cl2},
	{"A3", &ct, 220, &cl},
	{"G3", &ct2, 196, &cl2},
	{"F3", &ct2, 175, &cl2},
	{"E3", &ct2, 165, &cl2},
	{"D3", &ct2, 147, &cl2},
	{"C3", &ct2, 130, &cl2},
	{"B2", &ct2, 123, &cl2},
	{"A2", &ct, 110, &cl},
	{"A1", &ct, 55, &cl},
};

#define last (nelem(notes)-1)

static void
redraw(int freq, int full)
{
	static int w;
	static double m;
	static Image *data;
	static Point p0;
	static int nosignal;

	int i, x, rx;
	Rectangle r, dr;
	Point p;
	Font *f;

	f = display->defaultfont;
	r = screen->r;
	r.min.x += stringwidth(f, " ");
	r.min.y += f->height;
	r.max.x -= stringwidth(f, " ");
	r.max.y -= f->height*2;
	rx = r.max.x;

	p = r.min;

	if(full){
		draw(screen, screen->r, display->black, nil, ZP);
		freeimage(data);
		data = 	allocimage(display, screen->r, RGB24, 0, DNofill);

		w = 0;
		for(i = 0; i < nelem(notes); i++){
			if(w < (x = 2*stringwidth(f, notes[0].s)))
				w = x;
		}
		m = Dy(r) / fabs(notes[0].f - notes[last].f);
	}

	r.max.x -= w;

	for(i = 0; i < nelem(notes); i++){
		p.y = r.min.y + m * fabs(notes[0].f - notes[i].f);
		if(full)
			string(screen, p, *notes[i].ct, ZP, f, notes[i].s);
		line(
			screen,
			addpt(p, Pt(full ? w : r.max.x-p.x-1, f->height/2)),
			addpt(p, Pt(r.max.x-p.x, f->height/2)),
			Endsquare, Endsquare, 0,
			*notes[i].cl,
			ZP
		);
		if(full)
			string(screen, Pt(rx-w/2, p.y), *notes[i].ct, ZP, f, notes[i].s);
	}

	if(freq <= notes[0].f && freq >= notes[last].f){
		nosignal = 0;
		p = Pt(r.max.x - 1, r.min.y + m * fabs(notes[0].f - freq));
		line(screen, p, p0.x ? p0 : addpt(p, Pt(1,0)), Endsquare, Endsquare, 0, cf, ZP);
		p0 = p;
	}else if(nosignal++ > 4){
		p0 = ZP;
	}

	dr = screen->r;
	dr.min.x = -1;
	dr.min.y = 0;
	r.max.x++;
	r.min.x += w;
	replclipr(screen, 0, r);
	draw(screen, dr, display->screenimage, nil, ZP);
	flushimage(display, 1);
	replclipr(screen, 0, screen->r);
}

static void
usage(void)
{
	fprint(2, "usage: %s [/dev/audio]\n", argv0);
	threadexitsall("usage");
}

void
threadmain(int argc, char **argv)
{
	int f, n, i, x, ox[8], max, avg;
	kiss_fftr_cfg *c;
	Keyboardctl *kctl;
	Mousectl *mctl;
	Mouse m;
	Rune r;

	enum {
		Ckey,
		Cmouse,
		Cresize,
		Numchan,
	};
	Alt a[Numchan+1] = {
		[Ckey] = { nil, &r, CHANRCV },
		[Cmouse] = { nil, &m, CHANRCV },
		[Cresize] = { nil, nil, CHANRCV },
		{ nil, nil, CHANNOBLK },
	};

	ARGBEGIN{
	default:
		usage();
	}ARGEND

	if(argc > 1)
		usage();

	if((f = open(argc ? argv[0] : "/dev/audio", OREAD)) < 0)
		sysfatal("%r");
	if((c = kiss_fftr_alloc(nelem(window), 0, nil, nil)) == nil)
		sysfatal("failed to get fft config");

	if(initdraw(nil, nil, "pitch") < 0)
		sysfatal("initdraw: %r");
	if ((mctl = initmouse(nil, screen)) == nil)
		sysfatal("initmouse: %r");
	if((kctl = initkeyboard(nil)) == nil)
		sysfatal("initkeyboard: %r");
	a[Ckey].c = kctl->c;
	a[Cmouse].c = mctl->c;
	a[Cresize].c = mctl->resizec;
	threadsetname("pitch");
	ct = display->white;
	ct2 = allocimage(display, Rect(0,0,1,1), RGB24, 1, DWhite/3);
	cl = allocimage(display, Rect(0,0,1,1), RGB24, 1, DGreen);
	cl2 = allocimage(display, Rect(0,0,1,1), RGB24, 1, DGreen/3);
	cf = allocimage(display, Rect(0,0,1,1), RGB24, 1, DYellow);

	redraw(0, 1);

	memset(ox, 0, sizeof(ox));
	for(;;){
		if((n = readn(f, window, sizeof(window))) < 0)
			sysfatal("%r");
		if(n == 0)
			break;
		if(kiss_fftr(c, window, freq) != 0)
			sysfatal("%r");
		for(i = 0, x = 0, max = 5, avg = 0; i < nelem(freq); i++){
			if(freq[i].r > max){
				x = (44100/nelem(freq))*i/2;
				max = freq[i].r;
			}
			avg += freq[i].r;
		}
		avg /= i;
		if(max > avg+5){
			ox[0] = x;
			for(i = x = 0; i < nelem(ox); i++)
				x += ox[i];
			x /= nelem(ox);
			memmove(ox+1, ox, sizeof(ox)-sizeof(ox[0]));
		}else{
			x = 0;
		}

		switch(alt(a)){
		case Ckey:
			if(r == Kdel || r == 'q')
				goto done;
			break;
		case Cmouse:
			break;
		case Cresize:
			getwindow(display, Refnone);
			redraw(x, 1);
			continue;
		}

		redraw(x, 0);
	}

done:
	kiss_fftr_free(c);
	threadexitsall(nil);
}