shithub: orca

ref: 9bcd2b4ba984c92709794baa422695f873f8e828
dir: /plan9.c/

View raw version
#include "plan9.h"
#include "field.h"
#include "gbuffer.h"
#include "sim.h"
#include <bio.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <thread.h>

#define MIN(x,y) ((x)<(y)?(x):(y))
#define MAX(x,y) ((x)>(y)?(x):(y))

enum {
	Txtoff = 16,
	Coloff = 2,
	Msgredraw = 0,

	Chanchar = 0,
	Chankey,
	Chanmouse,
	Chanresize,
	Chanredraw,
	Numchan,
};

typedef struct Key Key;

static struct {
	u8int u[4];
	Usz at;
}noteoff[16*128]; /* 16 channels, 128 notes each */

struct Key {
	int down;
	Rune rune;
};

static Rune *linebuf;
static Usz tick;
static int gridw = 8, gridh = 8;
static int bpm = 120, insert = 1, pause;
static int curx, cury;
static int selw = 1, selh = 1;
static Image *curbg;
static int charw, charh;
static Field field;
static Mbuf_reusable mbuf;
static Oevent_list events;
static char filename[256];
static Channel *cchan;
static Field copyfield;
static int altdown, ctldown, slide;
static long framedev; /* frame deviation >= 1µs */

static char *menu3i[] = {
	"load",
	"save",
	nil,
};

static Menu menu3 = {
	.item = menu3i,
};

Usz
orca_round_up_power2(Usz x)
{
	x -= 1;
	x |= x >> 1;
	x |= x >> 2;
	x |= x >> 4;
	x |= x >> 8;
	x |= x >> 16;
	return x + 1;
}

bool
orca_is_valid_glyph(Glyph c)
{
	if (c >= '0' && c <= '9')
		return true;
	if (c >= 'A' && c <= 'Z')
		return true;
	if (c >= 'a' && c <= 'z')
		return true;
	switch (c) {
	case '!':
	case '#':
	case '%':
	case '*':
	case '.':
	case ':':
	case ';':
	case '=':
	case '?':
		return true;
	}
	return false;
}

static void
process(Oevent_list *events)
{
	int i, off;
	Oevent *e;
	u8int u[4];

	for (e = events->buffer, i = 0; i < events->count; i++, e++) {
		if (e->any.oevent_type == Oevent_type_midi_note) {
			Oevent_midi_note const *n = &e->midi_note;
			u[0] = 1;
			u[1] = 0x90 | n->channel;
			u[2] = (n->octave + 1)*12 + n->note;
			u[3] = n->velocity;
			write(1, u, 4);

			off = n->channel*128 + u[2];
			noteoff[off].u[1] = 0x80 | n->channel;
			noteoff[off].u[2] = u[2];
			noteoff[off].u[3] = 0;
			noteoff[off].at = tick + n->duration;
		}
	}

	for (i = 0; i < nelem(noteoff); i++) {
		if (noteoff[i].at > 0 && noteoff[i].at < tick) {
			write(1, noteoff[i].u, 4);
			noteoff[i].at = 0;
		}
	}
}

/*
 * nsec() is wallclock and can be adjusted by timesync
 * so need to use cycles() instead
 *
 * "fasthz" is how many ticks there are in a second
 * can be read from /dev/time
 */
static uvlong
nanosec(void)
{
	static double fasthz = 0.0;
	uvlong x;
	int f, n, i;
	char tmp[128], *e;

	if (fasthz < 1.0) {
		if ((f = open("/dev/time", OREAD)) < 0)
			sysfatal("failed to open /dev/time");
		if ((n = read(f, tmp, sizeof(tmp)-1)) < 2)
			sysfatal("failed to read /dev/time");
		tmp[n] = 0;
		e = tmp;
		for (i = 0; i < 3; i++)
			strtoll(e, &e, 10);
		fasthz = strtod(e, nil);
		if (fasthz < 1.0)
			sysfatal("failed to read fasthz");
		close(f);
	}
	cycles(&x);
	return (double)x / (fasthz / 1000000000.0);
}

static void
orcathread(void *drawchan)
{
	vlong start, end, n, oldn;
	vlong processold, processnew;
	int w, h, oldbpm, fd;
	char tmp[64];

	threadsetname("orca/sim");

	snprint(tmp, sizeof(tmp), "/proc/%d/ctl", getpid());
	if((fd = open(tmp, OWRITE)) >= 0){
		fprint(fd, "pri 13\n");
		close(fd);
	}

	processnew = nanosec();
	for (;;) {
		start = nanosec();
		w = field.width;
		h = field.height;
		mbuffer_clear(mbuf.buffer, h, w);
		oevent_list_clear(&events);
		orca_run(field.buffer, mbuf.buffer, h, w, tick, &events, 0);

		processold = processnew;
		processnew = nanosec();
		process(&events);
		tick++;
		nbsendul(drawchan, Msgredraw);

		oldn = start;
		end = start + 1;
		oldbpm = 0;
		for (n = start; pause || n < end; n = nanosec()) {
			if (bpm != oldbpm) {
				end = start + 15000000000LL/bpm; /* 10^9 * 60 / 4 */
				oldbpm = bpm;
			}

			/* doesn't suppose to jump back, but just in case do _something_ */
			if (n < oldn)
				end -= oldn - n;

			oldn = n;
			if (pause || end - n > 750000000LL)
				sleep(70);
			else if (end - n > 25000000LL)
				sleep(20);
			else if (end - n > 10000000LL)
				sleep(1);
		}

		framedev = ((processnew - processold) - (15000000000LL / bpm)) / 1000LL;
	}
}

static void
redraw(void)
{
	Rectangle r;
	Point p, top;
	int x, y, len;

	draw(screen, screen->r, display->black, nil, ZP);
	p = screen->r.min;
	p.x += Txtoff;
	p.y += Txtoff;
	top = p;

	for (y = 0; y < field.height; y++) {
		for (x = 0; x < field.width; x++) {
			Rune c = field.buffer[field.width*y + x];
			if (c == L'.')
				c = L'·';
			linebuf[x] = c;
		}
		linebuf[x] = 0;
		runestring(screen, p, display->white, ZP, font, linebuf);
		p.y += font->height;
	}

	p.y += 2 * font->height;

	/* field size */
	p.x = screen->r.min.x + Txtoff;
	len = MAX(3+1+3, runesprint(linebuf, "%udx%ud", field.width, field.height));
	runestring(screen, p, display->white, ZP, font, linebuf);

	/* cursor position */
	p.y += font->height;
	runesprint(linebuf, "%ud,%ud", curx, cury);
	runestring(screen, p, display->white, ZP, font, linebuf);

	/* selection size */
	p.x += charw * (len + Coloff);
	len = MAX(2+1+2, runesprint(linebuf, "%d:%d", selw, selh));
	runestring(screen, p, display->white, ZP, font, linebuf);

	/* grid size */
	p.y -= font->height;
	runesprint(linebuf, "%d/%d", gridw, gridh);
	runestring(screen, p, display->white, ZP, font, linebuf);

	/* ticks */
	p.x += charw * (len + Coloff);
	runesprint(linebuf, "%ludf", tick);
	runestring(screen, p, display->white, ZP, font, linebuf);

	/* insert/append mode */
	p.y += font->height;
	len = MAX(6, runesprint(linebuf, "%s", (altdown || slide) ? "slide" : (insert ? "insert" : "append")));
	runestring(screen, p, display->white, ZP, font, linebuf);

	/* bpm */
	p.y -= font->height;
	p.x += charw * (len + Coloff);
	len = MAX(6, runesprint(linebuf, "%d", bpm));
	runestring(screen, p, display->white, ZP, font, linebuf);

	/* filename */
	p.y += font->height;
	string(screen, p, display->white, ZP, font, filename[0] ? filename : "unnamed");

	/* frame deviation */
	p.y -= font->height;
	p.x += charw * (len + Coloff);
	len = MAX(6, runesprint(linebuf, "%ldµs", labs(framedev)));
	runestring(screen, p, display->white, ZP, font, linebuf);

	USED(len);

	/* cursor bg */
	p = top;
	p.x += curx * charw;
	p.y += cury * charh;
	r.min = p;
	r.max = p;
	r.max.x += selw * charw;
	r.max.y += selh * charh;
	draw(screen, r, curbg, nil, ZP);

	flushimage(display, 1);
}

static int
fieldload(char *path)
{
	Field_load_error e;

	if ((e = field_load_file(path, &field)) != Field_load_error_ok) {
		werrstr(field_load_error_string(e));
		return -1;
	}
	curx = MIN(curx, field.width-1);
	cury = MIN(cury, field.height-1);

	return 0;
}

static int
fieldsave(char *path)
{
	FILE *f;

	if ((f = fopen(path, "w")) == nil)
		return -1;
	field_fput(&field, f);
	fclose(f);
	return 0;
}

static void
selset(Rune key)
{
	int y;

	for (y = cury; y < cury+selh && y < field.height; y++)
		memset(&field.buffer[curx + field.width*y], key, selw);
}

static void
selcopy(void)
{
	Biobuf *b;
	int y;

	if ((b = Bopen("/dev/snarf", OWRITE)) != nil) {
		for (y = cury; y < cury+selh && y < field.height; y++) {
			Bwrite(b, &field.buffer[curx + field.width*y], MIN(selw, field.width-curx));
			Bputc(b, '\n');
		}
		Bterm(b);
	}
}

static void
selpaste(void)
{
	Biobuf *b;
	char *s;
	int cols, rows, n;

	if ((b = Bopen("/dev/snarf", OREAD)) != nil) {
		for (cols = rows = 0; (s = Brdstr(b, '\n', 1)) != nil; rows++) {
			if ((n = Blinelen(b)) > cols)
				cols = n;
			if (cury+rows < field.height)
				memmove(&field.buffer[curx + field.width*(cury+rows)], s, MIN(n, field.width-curx));
			free(s);
		}
		selw = MAX(1, cols);
		selh = MAX(1, rows);
		Bterm(b);
	}
}

static void
fieldset(Rune key)
{
	if (insert) {
		selset(key);
	} else {
		field.buffer[curx + field.width*cury] = key;
		if (curx < field.width-1)
			curx++;
	}
}

static void
selmove(int x, int y)
{
	int i;

	if (curx+x < 0 || cury+y < 0)
		return;

	field_resize_raw(&copyfield, selh, selw);
	gbuffer_copy_subrect(
		field.buffer,
		copyfield.buffer,
		field.height, field.width,
		copyfield.height, copyfield.width,
		cury, curx, 0, 0,
		selh, selw
	);

	for (i = cury; i < cury+selh && i < field.height; i++)
		memset(&field.buffer[curx + field.width*i], '.', MIN(selw, field.width-curx));

	gbuffer_copy_subrect(
		copyfield.buffer,
		field.buffer,
		copyfield.height, copyfield.width,
		field.height, field.width,
		0, 0, cury+y, curx+x,
		selh, selw
	);
}

static void
selmap(int (*f)(int))
{
	int x, y;

	for (y = cury; y < cury+selh && y < field.height; y++) {
		for (x = curx; x < curx+selw && x < field.width; x++) {
			field.buffer[x + field.width*y] = f(field.buffer[x + field.width*y]);
		}
	}
}

static void
screensize(int *w, int *h)
{
	*w = (Dx(screen->r) - 2*Txtoff) / charw;
	*h = ((Dy(screen->r) - 2*Txtoff) - 3*charh) / charh;
}

static void
select(void)
{
}

static void
kbdproc(void *k)
{
	char buf[128], buf2[128], *s;
	Channel *kchan;
	int kfd, n;
	Key key;
	Rune r;

	threadsetname("kbdproc");
	if ((kfd = open("/dev/kbd", OREAD)) < 0)
		sysfatal("can't open kbd: %r");

	kchan = k;
	buf2[0] = 0;
	buf2[1] = 0;
	buf[0] = 0;
	for(;;){
		if (buf[0] != 0) {
			n = strlen(buf)+1;
			memmove(buf, buf+n, sizeof(buf)-n);
		}
		if (buf[0] == 0) {
			n = read(kfd, buf, sizeof(buf)-1);
			if (n <= 0)
				break;
			buf[n-1] = 0;
			buf[n] = 0;
		}

		switch (buf[0]) {
		case 'c':
			if (chartorune(&r, buf+1) > 0 && r != Runeerror)
				nbsend(cchan, &r);
			/* no break */
		default:
			continue;

		case 'k':
			s = buf+1;
			while (*s){
				s += chartorune(&r, s);
				if (utfrune(buf2+1, r) == nil) {
					key.down = 1;
					key.rune = r;
					nbsend(kchan, &key);
				}
			}
			break;

		case 'K':
			s = buf2+1;
			while (*s) {
				s += chartorune(&r, s);
				if(utfrune(buf+1, r) == nil) {
					key.down = 0;
					key.rune = r;
					nbsend(kchan, &key);
				}
			}
			break;
		}
		strcpy(buf2, buf);
	}
}

void
threadmain(int argc, char **argv)
{
	Mousectl *mctl;
	Key key;
	Keyboardctl kctl;
	Mouse m;
	char tmp[256];
	Channel *kchan;
	int oldw, oldh, w, h, n, shiftdown;
	Alt a[Numchan+1] = {
		[Chanchar] = { nil, &key.rune, CHANRCV },
		[Chankey] = { nil, &key, CHANRCV },
		[Chanmouse] = { nil, &m, CHANRCV },
		[Chanresize] = { nil, nil, CHANRCV },
		[Chanredraw] = { nil, nil, CHANRCV },
		{ nil, nil, CHANEND },
	};

	USED(argc, argv);

	kchan = chancreate(sizeof(Key), 20);
	cchan = chancreate(sizeof(Rune), 20);
	proccreate(kbdproc, kchan, mainstacksize);

	srand(time(0));
	threadsetname("orca/draw");

	if(initdraw(nil, nil, "orca") < 0)
		sysfatal("initdraw: %r");
	if ((mctl = initmouse(nil, screen)) == nil)
		sysfatal("initmouse: %r");
	kctl.c = cchan;
	kctl.file = "/dev/null";
	kctl.consfd = kctl.pid = kctl.ctlfd = -1;

	a[Chanchar].c = cchan;
	a[Chankey].c = kchan;
	a[Chanmouse].c = mctl->c;
	a[Chanresize].c = mctl->resizec;
	a[Chanredraw].c = chancreate(sizeof(ulong), 1);

	curbg = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, setalpha(DYellow, 128));
	charw = stringwidth(font, "X");
	charh = font->height;

	screensize(&w, &h);
	field_init_fill(&field, h, w, '.');
	field_init(&copyfield);

	linebuf = malloc(sizeof(Rune)*MAX(w+1, 64));
	memset(noteoff, 0, sizeof(noteoff));
	mbuf_reusable_init(&mbuf);
	mbuf_reusable_ensure_size(&mbuf, h, w);
	oevent_list_init(&events);

	proccreate(orcathread, a[Chanredraw].c, mainstacksize);
	shiftdown = 0;
	altdown = 0;

	for (;;) {
		redraw();
		oldw = w = field.width;
		oldh = h = field.height;

		switch (alt(a)) {
		case Chanmouse: /* mouse */
			switch (m.buttons & 7) {
			case 1:
				select();
				break;
			case 2:
				break;
			case 4:
				n = menuhit(3, mctl, &menu3, nil);
				if (n == 0 || n == 1) {
					strncpy(tmp, filename, sizeof(tmp));
					if (enter("file path:", tmp, sizeof(tmp), mctl, &kctl, nil) > 0) {
						if (n == 0 && fieldload(tmp) == 0) {
							w = field.width;
							h = field.height;
							strncpy(filename, tmp, sizeof(filename));
						} else if (n == 1 && fieldsave(tmp) == 0) {
							strncpy(filename, tmp, sizeof(filename));
						}
					}
				}
				break;
			}
			break;

		case Chanresize: /* resize */
			getwindow(display, Refnone);
			break;

		case Chankey: /* key up/down */
			switch (key.rune) {
			case Kshift:
				shiftdown = key.down;
				break;
			case Kalt:
				altdown = key.down;
				break;
			case Kctl:
				ctldown = key.down;
				break;
			}
			break;

		case Chanredraw: /* redraw */
			break;

		case Chanchar: /* key */
			switch (key.rune) {
			case 0x0b: /* C-k */
			case Kup:
				if (shiftdown)
					selh = MAX(1, selh-1);
				else {
					if (altdown || slide)
						selmove(0, -1);
					cury = MAX(0, cury-1);
				}
				break;
			case '\n': /* C-j */
			case Kdown:
				if (shiftdown)
					selh++;
				else {
					if (altdown || slide)
						selmove(0, 1);
					cury = MIN(h-1, cury+1);
				}
				break;
			case Kbs: /* C-h */
			case Kleft:
				if (shiftdown)
					selw = MAX(1, selw-1);
				else {
					if (altdown || slide)
						selmove(-1, 0);
					curx = MAX(0, curx-1);
				}
				break;
			case 0x0c: /* C-l */
				if (shiftdown) { /* FIXME this conflicts with vim keys btw */
					selmap(tolower);
					break;
				}
			case Kright:
				if (shiftdown)
					selw++;
				else {
					if (altdown || slide)
						selmove(1, 0);
					curx = MIN(w-1, curx+1);
				}
				break;
			case Khome:
				curx = 0;
				break;
			case Kend:
				curx = field.width-1;
				break;
			case Kpgup:
				cury = 0;
				break;
			case Kpgdown:
				cury = field.height-1;
				break;
			case 0x11: /* C-q */
				goto end;
			case 0x12: /* C-r */
				tick = 0;
				break;
			case 0x13: /* C-s */
				tmp[0] = 0;
				if (filename[0])
					fieldsave(filename);
				else if (enter("file path:", tmp, sizeof(tmp), mctl, &kctl, nil) > 0 && fieldsave(tmp) == 0)
					strncpy(filename, tmp, sizeof(filename));
				break;
			case 0x18: /* C-x */
				selcopy();
				selset('.');
				break;
			case Ketx: /* C-c */
				selcopy();
				break;
			case 0x16: /* C-v */
				selpaste();
				break;
			case '(':
				w = MAX(1, w-gridw);
				break;
			case ')':
				w += gridw;
				break;
			case '_':
				h = MAX(1, h-gridh);
				break;
			case '+':
				h += gridh;
				break;
			case '>':
				bpm++;
				break;
			case '<':
				bpm = MAX(1, bpm-1);
				break;
			case Kins:
				insert = !insert;
				break;
			case Kesc:
				if (slide)
					slide = 0;
				else if (!insert)
					insert = 1;
				else
					selw = selh = 1;
				break;
			case '`':
			case '~':
				slide = !slide;
				break;
			case Knack: /* C-u */
				if (shiftdown)
					selmap(toupper);
				break;
			case ' ':
				if (insert) {
					pause = !pause;
					break;
				}
			default:
				if (key.rune == Kdel || key.rune == ' ')
					key.rune = '.';
				if (orca_is_valid_glyph(key.rune))
					fieldset(key.rune);
				else
					fprint(2, "unhandled key %04x\n", key.rune);
				break;
			}

			if (w != oldw || h != oldh) {
				field_copy(&field, &copyfield);
				field_resize_raw(&field, h, w);
				memset(field.buffer, '.', w*h);
				gbuffer_copy_subrect(
					copyfield.buffer,
					field.buffer,
					copyfield.height, copyfield.width,
					field.height, field.width,
					0, 0, 0, 0,
					MIN(field.height, copyfield.height), MIN(field.width, copyfield.width)
				);
			}
		}

		if (w != oldw || h != oldh) {
			mbuf_reusable_ensure_size(&mbuf, h, w);
			linebuf = realloc(linebuf, sizeof(Rune)*MAX(w+1, 64));
		}
	}

end:
	mbuf_reusable_deinit(&mbuf);
	oevent_list_deinit(&events);
	field_deinit(&field);
	field_deinit(&copyfield);

	threadexitsall(nil);
}