shithub: neindaw

ref: 2f144ccad768088ec811f0945eb8611fbc1e0218
dir: /waveform/waveform.c/

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

#define MIN(a,b) ((a)<=(b)?(a):(b))
#define MAX(a,b) ((a)>=(b)?(a):(b))

typedef struct Waveform Waveform;

struct Waveform {
	float *samples;
	Image *image;
	float min;
	float max;
	int nchan;
	int nframes;
	int rate;
};

static Waveform *
wvform(int f, int nchan, int rate)
{
	Waveform *w;
	int i, r, n, sz;

	w = calloc(1, sizeof(*w));
	w->nchan = nchan;
	w->rate = rate;
	sz = 128*nchan;
	w->samples = malloc(sz*sizeof(float));
	for (n = 0;;) {
		if (sz-n < 1) {
			sz *= 2;
			w->samples = realloc(w->samples, sz*sizeof(float));
		}
		if ((r = read(f, w->samples+n, (sz-n)*sizeof(float))) < 0) {
			free(w->samples);
			free(w);
			return nil;
		}
		r /= sizeof(float);
		if (r == 0)
			break;
		if (n == 0)
			w->min = w->max = w->samples[0];
		for (i = 0; i < r; i++, n++) {
			w->min = MIN(w->min, w->samples[n]);
			w->max = MAX(w->max, w->samples[n]);
		}
	}

	w->samples = realloc(w->samples, n*sizeof(float));
	w->nframes = n / nchan;

	return w;
}

static u8int sh[65] = {
0,
56, 57, 58, 59, 60, 61, 62, 63,
48, 49, 50, 51, 52, 53, 54, 55,
40, 41, 42, 43, 44, 45, 46, 47,
32, 33, 34, 35, 36, 37, 38, 39,
24, 25, 26, 27, 28, 29, 30, 31,
16, 17, 18, 19, 20, 21, 22, 23,
8, 9, 10, 11, 12, 13, 14, 15,
0, 1, 2, 3, 4, 5, 6, 7,
};

static u64int *
rotate90128(u64int *b, int h)
{
	u64int *p;
	int i, j;
	u64int v;

	p = calloc(1, 128*h/8);

	for (i = 0; i < h; i++) {
		if ((v = b[i*2+1]) != 0) {
			for (j = 0; j < 64; j++) {
				if (v & (1ULL<<sh[j+1]))
					p[j*h/64 + i/64] |= 1ULL<<(63-sh[(i&63)+1]);
			}
		}
		if ((v = b[i*2+0]) != 0) {
			for (j = 0; j < 64; j++) {
				if (v & (1ULL<<sh[j+1]))
					p[(j+64)*h/64 + i/64] |= 1ULL<<(63-sh[(i&63)+1]);
			}
		}
	}
	for (i = 0; i < 128*h/64; i++)
		p[i] = ~p[i];

	return p;
}

static int
wvimage128(Waveform *w, int offset, int nframes, Rectangle r, float zoom)
{
	float m, i;
	u64int *b, *p;
	int x, x2, y, incy, y2;

	r = Rect(0, 0, 128, MIN(nframes-offset, Dx(r)));
	if (badrect(r))
		return -1;
	r.max.y += 64;
	b = calloc(1, 128*Dy(r));
	m = MAX(abs(w->min), abs(w->max));
	m = m > 1.0f ? 63.0f/m : 63.0f;

	for (y = 0, i = offset; y < Dy(r) && i < nframes;) {
		x = m*w->samples[(int)(i*w->nchan)+0];
		i += zoom;
		x2 = i < nframes ? m*w->samples[(int)(i*w->nchan)+0] : x;
		incy = x == x2 ? -999 : (x2 + x)/2;
		y2 = y + 1;
		do {
			if (x >= 0)
				b[y*2+1] |= 1ULL<<sh[64-x];
			else
				b[y*2+0] |= 1ULL<<sh[-x];
			if (x < x2)
				x++;
			else if (x > x2)
				x--;
			if (x == incy)
				y++;
		} while (x2 != x);
		y = y2;
	}

	y -= (y & 63);
	y += 64;
	p = rotate90128(b, y);
	free(b);
	r.max.x = y;
	r.max.y = 128;
	if (w->image == nil || !eqrect(r, w->image->r)) {
		freeimage(w->image);
		if ((w->image = allocimage(display, r, GREY1, 0, DNofill)) == nil)
			return -1;
	}

	if (loadimage(w->image, r, (void*)p, 128*y) < 0)
		fprint(2, "failed: %r\n");
	free(p);

	return 0;
}

static int
wvimage(Waveform *w, int offset, int nframes, Rectangle r, float zoom)
{
	int i, yd, yi, bsz, xdone, ydone;
	float x, ox, oyi, dyi;
	u8int *b, col;

	r = Rect(0, 0, Dx(r), Dy(r));
	freeimage(w->image);
	if ((w->image = allocimage(display, r, GREY8, 0, DNofill)) == nil)
		return -1;
	bsz = Dx(r)*Dy(r);
	if ((b = malloc(bsz)) == nil) {
		freeimage(w->image);
		w->image = nil;
		return -1;
	}
	memset(b, 0xff, bsz);

	yd = Dy(r)/2;
	if (w->max > 1.0f)
		yd /= w->max;
	yd--;
	oyi = yd;
	for (ox = x = 0, i = offset; i < offset+nframes; i++, x += 1.0f/zoom) {
		yi = yd + w->samples[i*w->nchan+0] * yd;
		if (yi >= 0 && yi < Dy(r) && x < Dx(r))
			b[(int)x + yi*Dx(r)] = 0;
		dyi = (yi < oyi ? -1.0f : 1.0f)/MAX(1.0f, zoom);
		xdone = ydone = 0;
		col = MIN(0x80, zoom*(abs(yi - oyi) + abs(x - ox)));
		while (ox < Dx(r) && (!xdone || !ydone)) {
			b[(int)ox + (int)oyi*Dx(r)] = col;
			if (ox < x)
				ox = MIN(ox + 1.0f/MAX(1.0f, zoom), x);
			else
				xdone = 1;
			if ((dyi > 0 && oyi < yi) || (dyi < 0 && oyi > yi))
				oyi = MAX(0, MIN(oyi+dyi, Dy(r)-1));
			else
				ydone = 1;
		}
		if (x >= Dx(r))
			break;
		ox = x;
		oyi = yi;
	}

	loadimage(w->image, r, b, bsz);
	free(b);

	return 0;
}

static void
redraw(Waveform *w, int offset, float zoom)
{
	Rectangle r;

	r = screen->r;
	r.min.y += Dy(r)/4;
	r.max.y -= Dy(r)/4;
	wvimage128(w, offset, w->nframes-offset, r, zoom);
	draw(screen, screen->r, display->white, nil, ZP);
	draw(screen, r, w->image, nil, ZP);
	flushimage(display, 1);
}

void
threadmain(int argc, char **argv)
{
	Waveform *w;
	Mousectl *mctl;
	Keyboardctl *kctl;
	Rune key;
	float zoom;
	int offset, oldo;
	Mouse m;
	int oldb;
	Point oldp;
	Alt a[] = {
		{ nil, &m, CHANRCV },
		{ nil, nil, CHANRCV },
		{ nil, &key, CHANRCV },
		{ nil, nil, CHANEND },
	};

	USED(argc); USED(argv);

	if ((w = wvform(0, 1, 44100)) == nil)
		sysfatal("%r");

	if (initdraw(nil, nil, "daw/waveform") < 0)
		sysfatal("initdraw: %r");
	if ((mctl = initmouse(nil, screen)) == nil)
		sysfatal("initmouse: %r");
	if ((kctl = initkeyboard(nil)) == nil)
		sysfatal("initkeyboard: %r");

	a[0].c = mctl->c;
	a[1].c = mctl->resizec;
	a[2].c = kctl->c;

	srand(time(0));
	threadsetname("daw/cfg");

	zoom = 1.0f;
	offset = 0;
	redraw(w, offset, zoom);
	oldb = 0;
	oldo = 0;
	oldp = ZP;

	for (;;) {
		switch (alt(a)) {
		case 0: /* mouse */
			if (m.buttons == 1) {
				if (oldb == 0) {
					oldp = m.xy;
					oldo = offset;
				} else if (oldb == 1) {
					offset = MAX(0, oldo + (oldp.x - m.xy.x)*zoom);
					redraw(w, offset, zoom);
				}
			}
			oldb = m.buttons;
			break;

		case 1: /* resize */
			getwindow(display, Refnone);
			redraw(w, offset, zoom);
			break;

		case 2: /* keyboard */
			switch (key) {
			case Kdel:
				goto end;
			case Kleft:
				offset = MAX(0, offset-MAX(8, 8*MAX(1, 1/zoom)));
				redraw(w, offset, zoom);
				break;
			case Kright:
				offset = MIN(w->nframes-1, offset+MAX(8, 8*MAX(1, 1/zoom)));
				redraw(w, offset, zoom);
				break;
			case '-':
				zoom *= 1.1f;
				if (zoom > 32.0f)
					zoom = 32.0f;
				redraw(w, offset, zoom);
				break;
			case '+':
				zoom /= 1.1f;
				if (zoom < 0.01f)
					zoom = 0.01f;
				redraw(w, offset, zoom);
				break;
			}
			break;
		}
	}

end:
	threadexitsall(nil);
}