ref: 46544df18a733fdcc2e43dde43b396e719f1a0b1
dir: /plan9.c/
#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 *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);
/* 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 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;
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(©field);
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:
switch (menuhit(3, mctl, &menu3, nil)) {
case 0:
tmp[0] = 0;
if (enter("load file:", tmp, sizeof(tmp), mctl, kctl, nil) > 0) {
if (fieldload(tmp) == 0) {
w = field.width;
h = field.height;
} else {
/* FIXME display the error */
}
}
break;
}
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, ©field);
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(©field);
threadexitsall(nil);
}