ref: 9bcd2b4ba984c92709794baa422695f873f8e828
dir: /plan9.c/
#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(©field, 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(©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); 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, ©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) ); } } 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); }