shithub: orca

ref: c98d73fc2318481eeb16d90144c697e87544bf46
dir: /plan9.c/

View raw version
#include "plan9.h"
#include "field.h"
#include "gbuffer.h"
#include "sim.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,

	Msgredraw = 0,
};

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

static Rune *linebuf;
static Usz tick;
static int gridw = 8, gridh = 8;
static int bpm = 120, insert = 1, pause;
static int curx, cury;
static Image *curbg;
static int charw, charh;
static Field field;
static Mbuf_reusable mbuf;
static Oevent_list events;
static char filename[256];

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;
	int w, h, oldbpm;

	threadsetname("orca/sim");
	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);

		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 + (15000000000.0 / (double)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;
			yield();
			sleep(5);
		}
	}
}

static void
redraw(void)
{
	Point p, top;
	Rune cursor;
	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;
			if (y == cury && x == curx)
				cursor = 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 = 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);

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

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

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

	/* bpm */
	p.y -= font->height;
	p.x += charw * (len + 3);
	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);

	/* cursor bg */
	p = top;
	p.x += curx*stringwidth(font, " ");
	p.y += cury*font->height;
	runestringnbgop(screen, p, display->black, ZP, font, &cursor, 1, curbg, ZP, SoverD);

	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
fieldset(Rune key)
{
	field.buffer[curx + field.width*cury] = key;
	if (!insert && curx < field.width-1)
		curx++;
}

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)
{
}

void
threadmain(int argc, char **argv)
{
	Mousectl *mctl;
	Keyboardctl *kctl;
	Field copyfield;
	Rune key;
	Mouse m;
	char tmp[256];
	int oldw, oldh, w, h, n;
	Alt a[] = {
		{ nil, &m, CHANRCV },
		{ nil, nil, CHANRCV },
		{ nil, &key, CHANRCV },
		{ nil, nil, CHANRCV },
		{ nil, nil, CHANEND },
	};

	USED(argc, argv);

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

	if(initdraw(nil, nil, "orca") < 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;
	a[3].c = chancreate(sizeof(ulong), 0); /* FIXME should it be buffered instead? */

	curbg = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, DYellow);
	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);

	threadcreate(orcathread, a[3].c, mainstacksize);

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

		switch (alt(a)) {
		case 0: /* 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 1: /* resize */
			getwindow(display, Refnone);
			break;

		case 2: /* keyboard */
			switch (key) {
			case 0x0b: /* C-k */
			case Kup:
				cury = MAX(0, cury-1);
				break;
			case '\n': /* C-j */
			case Kdown:
				cury = MIN(h-1, cury+1);
				break;
			case Kbs: /* C-h */
			case Kleft:
				curx = MAX(0, curx-1);
				break;
			case 0x0c: /* C-l */
			case Kright:
				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 '(':
				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 (!insert)
					insert = 1;
				/* FIXME else remove selection */
				break;
			case ' ':
				pause = !pause;
				break;
			default:
				if (key == Kdel)
					key = '.';
				if (orca_is_valid_glyph(key))
					fieldset(key);
				else
					fprint(2, "unhandled key %04x\n", key);
				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)
				);
			}

		case 3: /* redraw */
			break;
		}

		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);
}