shithub: orca

ref: 98ffa7f810093bbffc0ccb24bb38a6992bacfdb0
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>

typedef struct Key Key;

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

enum {
	Txtoff = 16,
	Coloff = 2,

	Cchar = 0,
	Ckey,
	Cmouse,
	Cresize,
	Credraw,
	Numchan,

	Sfancy = 0,
	Splain,
	Snone,
	Numstyles,

	Minsert = 0,
	Mappend,
	Mslide,
	Mselect,
	Nummodes,

	Menu3load = 0,
	Menu3save,
	Menu3dotstyle,
	Menu3rulerstyle,
	Nummenu3,

	Dback = 0,
	Dfhigh,
	Dfmed,
	Dflow,
	Dfinv,
	Dbhigh,
	Dbmed,
	Dblow,
	Dbinv,
	Numcolors,
};

struct Key {
	int down;
	Rune rune;
};

static Rune *linebuf;
static Usz tick;
static int gridw = 8, gridh = 8;
static int rulerstyle = Sfancy, dotstyle = Sfancy;
static int bpm = 120, pause;
static int curx, cury;
static int selw = 1, selh = 1;
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;
static int mode = Minsert;
static long framedev; /* frame deviation >= 1µs */

static char *style[Numstyles] = {
	[Sfancy] = "fancy",
	[Splain] = "plain",
	[Snone] = "none",
};

static Rune dot[Numstyles] = {
	[Sfancy] = L'·',
	[Splain] = '.',
	[Snone] = ' ',
};

static Rune ruler[Numstyles][9] = {
	[Sfancy] = {
		L'┌', L'┬', L'┐',
		L'├', L'┼', L'┤',
		L'└', L'┴', L'┘',
	},
	[Splain] = {
		'+', '+', '+',
		'+', '+', '+',
		'+', '+', '+',
	},
	[Snone] = {
		' ', ' ', ' ',
		' ', ' ', ' ',
		' ', ' ', ' ',
	},
};

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

static u32int theme[Numcolors] = {
	[Dback] = 0x000000ff,
	[Dfhigh] = 0xffffffff,
	[Dfmed] = 0x777777ff,
	[Dflow] = 0x444444ff,
	[Dfinv] = 0x000000ff,
	[Dbhigh] = 0xddddddff,
	[Dbmed] = 0x72dec2ff,
	[Dblow] = 0x222222ff,
	[Dbinv] = 0xffb545ff,
};

static Image *color[Numcolors];

static char *modes[Nummodes] = {
	[Minsert] = "insert",
	[Mappend] = "append",
	[Mslide] = "slide",
	[Mselect] = "select",
};

static char *menu3i[Nummenu3+1] = {
	[Menu3load] = "load",
	[Menu3save] = "save",
};

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, 0);

		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, bot;
	int x, y, len, rx, ry;

	draw(screen, screen->r, color[Dback], nil, ZP);
	p = screen->r.min;
	p.x += Txtoff;
	p.y += Txtoff;
	top = p;
	bot.x = top.x;
	bot.y = screen->r.max.y - Txtoff - charh*2;

	for (y = 0; y < field.height && p.y < bot.y-charh; y++) {
		for (x = 0; x < field.width; x++) {
			Rune c = field.buffer[field.width*y + x];
			if (c == '.') {
				c = dot[dotstyle];
				if ((x % gridw) == 0 && (y % gridh) == 0) {
					rx = !!x + (x + 1) / field.width;
					ry = !!y + (y + 1) / field.height;
					c = ruler[rulerstyle][ry*3+rx];
				}
			}
			linebuf[x] = c;
		}
		linebuf[x] = 0;
		runestring(screen, p, color[Dflow], ZP, font, linebuf);
		p.y += font->height;
	}

	p = bot;

	/* 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, color[Dfhigh], ZP, font, linebuf);

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

	/* selection size */
	p.x += charw * (len + Coloff);
	len = MAX(2+1+2, runesprint(linebuf, "%d:%d", selw, selh));
	runestring(screen, p, color[Dfhigh], ZP, font, linebuf);

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

	/* ticks */
	p.x += charw * (len + Coloff);
	runesprint(linebuf, "%ludf", tick);
	runestring(screen, p, color[Dfhigh], ZP, font, linebuf);

	/* insert/append mode */
	p.y += font->height;
	len = MAX(6, runesprint(linebuf, "%s", modes[altdown ? Mslide : mode]));
	runestring(screen, p, color[Dfhigh], ZP, font, linebuf);

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

	/* filename */
	p.y += font->height;
	string(screen, p, color[Dfhigh], 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, color[Dfhigh], 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, color[Dbinv], 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 (mode == Minsert) {
		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 int
snaplow(int n, int gridn)
{
	n--;
	n -= (n % gridn) > 0 ? (n % gridn) : gridn;
	return MAX(1, n+1);
}

static int
snaphigh(int n, int gridn)
{
	n += gridn;
	n -= n % gridn - 1;
	return n;
}

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] = {
		[Cchar] = { nil, &key.rune, CHANRCV },
		[Ckey] = { nil, &key, CHANRCV },
		[Cmouse] = { nil, &m, CHANRCV },
		[Cresize] = { nil, nil, CHANRCV },
		[Credraw] = { 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[Cchar].c = cchan;
	a[Ckey].c = kchan;
	a[Cmouse].c = mctl->c;
	a[Cresize].c = mctl->resizec;
	a[Credraw].c = chancreate(sizeof(ulong), 1);

	for (n = 0; n < Numcolors; n++)
		color[n] = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, setalpha(theme[n] & ~0xff, theme[n] & 0xff));
	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[Credraw].c, mainstacksize);
	shiftdown = 0;
	altdown = 0;

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

		switch (alt(a)) {
		case Cmouse:
			switch (m.buttons & 7) {
			case 1:
				select();
				break;
			case 2:
				break;
			case 4:
				menu3i[Menu3dotstyle] = tmp;
				menu3i[Menu3rulerstyle] = 1 + menu3i[Menu3dotstyle] + sprintf(tmp, "%s dots", style[(dotstyle+1) % Numstyles]);
				sprintf(menu3i[Menu3rulerstyle], "%s rulers", style[(rulerstyle+1) % Numstyles]);
				n = menuhit(3, mctl, &menu3, nil);
				if (n == Menu3load || n == Menu3save) {
					strncpy(tmp, filename, sizeof(tmp));
					if (enter("file path:", tmp, sizeof(tmp), mctl, &kctl, nil) > 0) {
						if (n == Menu3load && fieldload(tmp) == 0) {
							w = field.width;
							h = field.height;
							strncpy(filename, tmp, sizeof(filename));
						} else if (n == Menu3save && fieldsave(tmp) == 0) {
							strncpy(filename, tmp, sizeof(filename));
						}
					}
				} else if (n == Menu3dotstyle) {
					dotstyle = ++dotstyle % Numstyles;
				} else if (n == Menu3rulerstyle) {
					rulerstyle = ++rulerstyle % Numstyles;
				}
				break;
			}
			break;

		case Cresize:
			getwindow(display, Refnone);
			break;

		case Ckey:
			switch (key.rune) {
			case Kshift:
				shiftdown = key.down;
				break;
			case Kalt:
				altdown = key.down;
				break;
			}
			break;

		case Credraw:
			break;

		case Cchar:
			switch (key.rune) {
			case 0x0b: /* C-k */
			case Kup:
				if (shiftdown || mode == Mselect)
					selh = MAX(1, selh-1);
				else {
					if (altdown || mode == Mslide)
						selmove(0, -1);
					cury = MAX(0, cury-1);
				}
				break;
			case '\n': /* C-j */
			case Kdown:
				if (shiftdown || mode == Mselect)
					selh++;
				else {
					if (altdown || mode == Mslide)
						selmove(0, 1);
					cury = MIN(h-1, cury+1);
				}
				break;
			case Kbs: /* C-h */
			case Kleft:
				if (shiftdown || mode == Mselect)
					selw = MAX(1, selw-1);
				else {
					if (altdown || mode == Mslide)
						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 || mode == Mselect)
					selw++;
				else {
					if (altdown || mode == Mslide)
						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 '[':
				gridw = MAX(4, gridw-1);
				break;
			case ']':
				gridw = MIN(16, gridw+1);
				break;
			case '{':
				gridh = MAX(4, gridh-1);
				break;
			case '}':
				gridh = MIN(16, gridh+1);
				break;
			case '(':
				w = snaplow(w, gridw);
				break;
			case ')':
				w = snaphigh(w, gridw);
				break;
			case '_':
				h = snaplow(h, gridh);
				break;
			case '+':
				h = snaphigh(h, gridh);
				break;
			case '>':
				bpm++;
				break;
			case '<':
				bpm = MAX(1, bpm-1);
				break;
			case 0x09: /* C-i */
			case Kins:
				mode = mode != Mappend ? Mappend : Minsert;
				break;
			case Kesc:
				if (mode == Mslide || mode != Minsert)
					mode = Minsert;
				else
					selw = selh = 1;
				break;
			case '`':
			case '~':
			case L'´':
				mode = mode != Mslide ? Mslide : Minsert;
				break;
			case '\'':
				mode = mode != Mselect ? Mselect : Minsert;
				break;
			case Knack: /* C-u */
				if (shiftdown)
					selmap(toupper);
				break;
			case ' ':
				if (mode != Mappend) {
					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);
}