#include #include #include #include #include #include #include enum{NEXTROW, INSTR, NOTE, BEND, GLOBAL, LOOP, MARK}; typedef struct Event Event; struct Event{ Event *next; int index; /* row << 8 | col << 3 | type */ union{ struct{uchar r20, r23, r40, r43, r60, r63, r80, r83, re0, re3, rc0;}; struct{int freq; uchar ra0, rb0; char note[4];}; int bend; struct{int bpm; uchar rpb, tpr, r104, rbd;}; int loop; char *mark; }; }*song, **edit; char *keys = "\t1q2we4r5t6yu8i9op-[=]\b\\", *notes = "CCDDEFFGGAAB"; int freq[18], bend[18], ed, eds, tickopl, tpr, horiz, wid, row, col, skipoff, skip = 1; int looping, loopstart, loopend, snarfrow, snarfcol = -1; vlong tickns; uchar rb0[18], rc0[18], mute[18]; Image *bg[2], *hl, *fg; Mousectl *⌖; char *wave[] = { /* OPL2 */ "Sine", "Half Sine", "Absolute Sin", "Pulse Sine", /*OPL3 */ "Even Sine", "Even Abs Sin", "Square", "Derived Sq", nil }; Cursor skull = { {-7,-7},{ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0xe7, 0xe7, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x3f, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xef, 0xf7, 0xc7, 0xe3, 0x00, 0x00, 0x00, 0x00 },{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xE7, 0xE7, 0x3F, 0xFC, 0x0F, 0xF0, 0x0D, 0xB0, 0x07, 0xE0, 0x06, 0x60, 0x37, 0xEC, 0xE4, 0x27, 0xC3, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, audio = { {-7,-7}, {0}, { 0x00, 0x80, 0x02, 0x04, 0x06, 0x022, 0x0e, 0xf12, 0x1e, 0x91, 0xfe, 0x89, 0xfe, 0x49, 0xfe, 0x49, 0xfe, 0x49, 0xfe, 0x49, 0xfe, 0x89, 0x1e, 0x91, 0x0e, 0xf12, 0x06, 0x022, 0x02, 0x04, 0x00, 0x80 } }; int menulen; char* menugen(int n){ static char buf[6]; if(n < 0 || n >= menulen) return nil; sprint(buf, "%d", n); return buf; } int menu(int v, char **s, int but){ Menu m; m.item = s; m.gen = menugen; m.lasthit = v; menuhit(but, ⌖, &m, nil); return m.lasthit; } void reged(uchar *r, int mask, int shift){ menulen = mask + 1; *r = *r & ~(mask << shift) | menu(*r >> shift & mask, nil, 1) << shift; } void hist(int n, int obliterate){ Event **ep, *e; while(n){ if(n > 0){ /* redo */ if(ed == eds) return; e = edit[ed++]; n--; }else{ /* undo */ if(ed == 0) return; e = edit[--ed]; n++; } ep = &song; while(*ep != nil && (*ep)->index < e->index) ep = &(*ep)->next; if(*ep != e){ /* insert / undelete */ e->next = *ep; *ep = e; }else{ /* delete / uninsert */ *ep = e->next; if(obliterate){ if((e->index & 7) == MARK) free(e->mark); free(e); } } } } void newedit(Event *e){ static int max; int old; old = ed; hist(eds - ed, 0); hist(old - eds, 1); if(ed == max){ max += 1024; edit = realloc(edit, max * sizeof e); if(edit == nil) sysfatal("realloc: %r"); setrealloctag(edit, getcallerpc(&e)); } edit[ed] = e; eds = ed + 1; } void delete(Event *e){ newedit(e); hist(1, 0); } void insert(Event *new){ Event *e; for(e = song; e != nil && e->index < new->index; e = e->next) ; if(e != nil && e->index == new->index) delete(e); newedit(new); hist(1, 0); } Event* newevent(int index){ Event *e; e = malloc(sizeof(Event)); if(e == nil) sysfatal("malloc: %r"); setmalloctag(e, getcallerpc(&index)); e->index = index; return e; } void evdup(Event *e, Event *f){ switch(e->index & 7){ case INSTR: e->r20 = f->r20; e->r23 = f->r23; e->r40 = f->r40; e->r43 = f->r43; e->r60 = f->r60; e->r63 = f->r63; e->r80 = f->r80; e->r83 = f->r83; e->re0 = f->re0; e->re3 = f->re3; e->rc0 = f->rc0; break; case NOTE: e->freq = f->freq; e->ra0 = f->ra0; e->rb0 = f->rb0; e->note[0] = f->note[0]; e->note[1] = f->note[1]; e->note[2] = f->note[2]; e->note[3] = f->note[3]; break; case BEND: e->bend = f->bend; break; case GLOBAL: e->bpm = f->bpm; e->rpb = f->rpb; e->tpr = f->tpr; e->r104 = f->r104; e->rbd = f->rbd; break; case LOOP: e->loop = f->loop; break; case MARK: e->mark = strdup(f->mark); break; } } int fnum(int n){ /* fnum * 49716 = hz << 20 - block */ int f; f = 440.0 / 49716.0 * pow(2.0, n / 384.0); for(n = 1; n < 7 && f > 1023; n++) f >>= 1; return n << 10 | f; } void fmtnote(Event *e, int n){ e->note[1] = e->note[2] = e->note[3] = 0; if(n == 0xff){ e->note[0] = 'x'; return; } e->note[0] = notes[n % 12]; if(n && notes[(n - 1) % 12] == e->note[0]){ e->note[1] = '#'; e->note[2] = '0' + n / 12; }else e->note[1] = '0' + n / 12; /* internal frequency unit: 32ths of a semitone distant from A4, shifted up 19 octaves */ e->freq = n - 57 + 19 * 12 << 5; n = fnum(e->freq); e->ra0 = n; e->rb0 = 0x20 | n >> 8; } void resize(void){ getwindow(display, Refnone); horiz = screen->r.min.y + Dy(screen->r) / font->height / 3 * font->height; } void redraw(void){ Event *e; int i; Point p; char buf[8]; draw(screen, screen->r, bg[0], nil, ZP); p = Pt(screen->r.min.x + 2 * wid, screen->r.min.y); buf[1] = 0; for(buf[0] = 'A'; buf[0] <= 'R'; buf[0]++){ if(mute[buf[0] - 'A']) draw(screen, Rect(p.x, p.y, p.x + wid, p.y + font->height), hl, nil, ZP); string(screen, p, fg, ZP, font, buf); p.x += wid; } buf[0] = 'X'; string(screen, p, fg, ZP, font, buf); i = (horiz - screen->r.min.y) / font->height - 1; if(row < i) i = row; p = Pt(screen->r.min.x, horiz - i * font->height); i = row - i << 8; for(e = song; e != nil && e->index < i; e = e->next) ; while(p.y < screen->r.max.y && i < 0x7fffff00){ p.x = screen->r.min.x; draw(screen, Rect(p.x, p.y, screen->r.max.x, p.y + font->height), bg[(skipoff + (i >> 8)) % skip != 0], nil, ZP); sprint(buf, "%06ux", i >> 8); string(screen, p, fg, ZP, font, buf); i += 0x100; while(e != nil && e->index < i){ p.x = screen->r.min.x + (2 + (e->index >> 3 & 31)) * wid; switch(e->index & 7){ case INSTR: draw(screen, Rect(p.x, p.y, p.x + wid, p.y + font->height), hl, nil, ZP); break; case NOTE: string(screen, p, fg, ZP, font, e->note); break; case BEND: p.x += wid - stringwidth(font, "↓"); string(screen, p, fg, ZP, font, e->bend < 0 ? "↓" : "↑"); break; case GLOBAL: draw(screen, Rect(p.x, p.y, p.x + wid, p.y + font->height), hl, nil, ZP); break; case LOOP: if(e->loop == 1) strcpy(buf, "|: "); else sprint(buf, ":|%d ", e->loop); p = string(screen, p, fg, ZP, font, buf); if(e->next == nil || e->next->index != e->index + 1) break; e = e->next; case MARK: string(screen, p, fg, ZP, font, e->mark); break; } e = e->next; } p.y += font->height; } p = Pt(screen->r.min.x + (2 + col) * wid, screen->r.min.y); draw(screen, Rect(p.x - 2, p.y, p.x, screen->r.max.y), hl, nil, ZP); draw(screen, Rect(p.x + wid, p.y, p.x + wid + 2, screen->r.max.y), hl, nil, ZP); draw(screen, Rect(screen->r.min.x, horiz - 2, screen->r.max.x, horiz), hl, nil, ZP); draw(screen, Rect(screen->r.min.x, horiz + font->height, screen->r.max.x, horiz + font->height + 2), hl, nil, ZP); flushimage(display, 1); } char* process(Event *e, char *b){ char x; x = e->index >> 3 & 31; switch(e->index & 7){ default: sysfatal("unrecognized event type"); case INSTR: b[3] = b[4] = b[8] = b[9] = b[13] = b[14] = b[18] = b[19] = b[23] = b[24] = b[28] = b[29] = b[33] = b[34] = b[38] = b[39] = b[43] = b[44] = b[48] = b[49] = b[53] = b[54] = 0; b[1] = b[6] = b[11] = b[16] = b[21] = b[26] = b[31] = b[36] = b[41] = b[46] = b[51] = x & 1; b[50] = 0xc0 | x >> 1; rc0[x] = e->rc0; b[52] = e->rc0 & ~mute[x]; x = x / 6 << 3 | x % 6 >> 1; b[0] = 0x20 | x; b[2] = e->r20; b[5] = b[0] + 3; b[7] = e->r23; b[10] = 0x40 | x; b[12] = e->r40; b[15] = b[10] + 3; b[17] =e->r43; b[20] = 0x60 | x; b[22] = e->r60; b[25] = b[20] + 3; b[27] = e->r63; b[30] = 0x80 | x; b[32] = e->r80; b[35] = b[30] + 3; b[37] = e->r83; b[40] = 0xe0 | x; b[42] = e->re0; b[45] = b[40] + 3; b[47] = e->re3; return b + 55; case NOTE: b[1] = b[6] = b[11] = x & 1; b[3] = b[4] = b[8] = b[9] = b[13] = b[14] = 0; b[5] = 0xa0 | x >> 1; b[0] = b[10] = b[5] | 0x10; b[2] = rb0[x] &= 0xdf; if(e->note[0] == 'x') return b + 5; b[7] = e->ra0; b[12] = rb0[x] = e->rb0; freq[x] = e->freq; return b + 15; case BEND: bend[x] = e->bend; break; case GLOBAL: tpr = e->tpr; tickns = 60000000000LL / e->bpm / e->rpb / tpr; tickopl = 60 * 49716 / e->bpm / e->rpb / tpr; b[3] = b[4] = b[8] = b[9] = 0; b[0] = 0xbd; b[1] = 0; b[2] = e->rbd; b[5] = 0x04; b[6] = 1; b[7] = e->r104; return b + 10; case LOOP: if(looping == 0){ loopstart = loopend + 1; loopend = e->index >> 8; looping = e->loop; } looping--; case MARK: break; } return b; } char* bends(char *b){ int i, f; for(i = 0; i < 18; i++) if(bend[i]){ b[3] = b[4] = b[8] = b[9] = 0; b[1] = b[6] = i & 1; b[0] = 0xa0 | i >> 1; b[5] = b[0] | 0x10; f = fnum(freq[i] += bend[i]); b[2] = f; b[7] = rb0[i] = rb0[i] & 0x20 | f >> 8; b += 10; } return b; } int startopl(void){ int p[2], a; a = open("/dev/audio", OWRITE); if(a < 0) return -1; if(pipe(p) < 0){ close(a); return -1; } switch(fork()){ case -1: close(a); close(p[0]); close(p[1]); return -1; case 0: dup(p[1], 0); dup(a, 1); close(a); close(p[0]); close(p[1]); execl("/bin/games/opl3", "opl3", "-s", nil); sysfatal("execl: %r"); } close(p[1]); close(a); write(p[0], "\1\0 \0\0\5\1\1\0", 10); setcursor(⌖, &audio); for(a = 0; a < 18; a++) freq[a] = rb0[a] = rc0[a] = 0; /* default: 120 bpm, 4 rpb, 4 tpr → tick = 31.25 ms */ tpr = 4; tickns = 60000000000LL / 120 / 4 / tpr; tickopl = 60 * 49716 / 120 / 4 / tpr; return p[0]; } void threadmain(int argc, char **argv){ Keyboardctl *⌨; Point p; Rune k; Event **ep, *e, *snarf = nil; vlong t; char buf[4092], *b, *path; int fd, i, j, oct = 48; char *b2s[] = {"voice", "note", "bend", nil}; char *b2sx[] = {"global", "loop", "mark", nil}; char *b3s[] = {"undo", "jump", " skip = 1 ", "select", "paste", "select", "write", "quit", nil}; Menu b2m = {b2s, nil, 0}, b3m = {b3s, nil, 0}; enum{RESIZE, MOUSE, KEYBOARD, NOTHING}; Alt a[] = { [RESIZE]{nil, nil, CHANRCV}, [MOUSE]{nil, nil, CHANRCV}, [KEYBOARD]{nil, &k, CHANRCV}, [NOTHING]{nil, nil, CHANEND} }; if(argc > 2) sysfatal("usage: %s [file]", argv[0]); if(argc == 2){ path = argv[1]; fd = open(path, OREAD); if(fd < 0) sysfatal("open: %r"); t = j = 0; ep = &song; while(9){ b = buf + j; i = read(fd, b, sizeof buf - j); if(i <= 0) break; i += j; b = buf; while(b < buf + i){ switch(*b & 7){ default: sysfatal("bad file format: unrecognized event type 0x%02ux", *b & 0xff); case NEXTROW: t++; b++; continue; case INSTR: if(b + 12 > buf + i) break; *ep = e = newevent(t << 8 | b[0] & 0xff); ep = &e->next; e->r20 = b[1]; e->r23 = b[2]; e->r40 = b[3]; e->r43 = b[4]; e->r60 = b[5]; e->r63 = b[6]; e->r80 = b[7]; e->r83 = b[8]; e->re0 = b[9]; e->re3 = b[10]; e->rc0 = b[11]; b += 12; continue; case NOTE: if(b + 2 > buf + i) break; *ep = e = newevent(t << 8 | b[0] & 0xff); ep = &e->next; fmtnote(e, b[1] & 0xff); b += 2; continue; case BEND: if(b + 2 > buf + i) break; *ep = e = newevent(t << 8 | b[0] & 0xff); ep = &e->next; e->bend = (schar)b[1]; b += 2; continue; case GLOBAL: if(b + 5 > buf + i) break; *ep = e = newevent(t << 8 | b[0] & 0xff); ep = &e->next; e->bpm = (b[1] & 0xff) + 30; e->rpb = (b[2] & 7) + 1; e->tpr = (b[2] >> 4 & 7) + 1; e->r104 = b[3]; e->rbd = b[4]; b += 5; continue; case LOOP: if(b + 2 > buf + i) break; *ep = e = newevent(t << 8 | b[0] & 0xff); ep = &e->next; e->loop = b[1] & 0xff; b += 2; continue; case MARK: if(memchr(b, 0, buf - b + i) == nil){ if(b == buf) sysfatal("mark string too long for buffer!"); break; } *ep = e = newevent(t << 8 | b[0] & 0xff); ep = &e->next; e->mark = strdup(b + 1); b = strchr(b, 0); continue; } break; } j = buf + i - b; memmove(buf, b, j); } *ep = nil; close(fd); if(i < 0) sysfatal("read: %r"); if(i) sysfatal("bad file format: short read"); }else path = nil; sprint(buf, "%s %s", argv[0], path); if(initdraw(nil, nil, buf) < 0) sysfatal("initdraw: %r"); wid = stringwidth(font, "000"); bg[0] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DPalegreen); bg[1] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DMedgreen); hl = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DPurpleblue); fg = display->black; if(bg[0] == nil || bg[1] == nil || hl == nil || fg == nil) sysfatal ("allocimage: %r"); if((⌖ = initmouse(nil, screen)) == nil) sysfatal("initmouse: %r"); a[RESIZE].c = ⌖->resizec; a[MOUSE].c = ⌖->c; a[MOUSE].v = &⌖->Mouse; if((⌨ = initkeyboard(nil)) == nil) sysfatal("initkeyboard: %r"); a[KEYBOARD].c = ⌨->c; sendul(⌖->resizec, 1); while(9){switch(alt(a)){ case RESIZE: resize(); break; case MOUSE: switch(⌖->buttons){ default: continue; case 1: do readmouse(⌖); while(⌖->buttons); if(⌖->xy.y < horiz){ row -= 1 + (horiz - ⌖->xy.y) / font->height; if(row < 0) row = 0; }else{ row += (⌖->xy.y - horiz) / font->height; if(row > 0x7ffffe) row = 0x7ffffe; } col = (⌖->xy.x - screen->r.min.x) / wid - 2; if(col < 0) col = 0; if(col > 18) col = 18; break; case 2: if(col == 18){ b2m.item = b2sx; switch(menuhit(2, ⌖, &b2m, nil)){ case 0: k = '\n'; goto kbdcmd; case 1: k = 'l'; goto kbdcmd; case 2: k = 'm'; goto kbdcmd; } }else{ b2m.item = b2s; switch(menuhit(2, ⌖, &b2m, nil)){ case 0: k = '\n'; goto kbdcmd; case 1: *buf = 0; enter("note", buf, sizeof buf, ⌖, ⌨, nil); b = strchr(notes, buf[0]); if(b == nil) continue; i = b - notes; if(buf[1] == '#'){ if(buf[2] < '0' || buf[2] > '8' || buf[3]) continue; i += (buf[2] - '0') * 12 + 1; }else if(buf[1] == 'b'){ if(buf[2] < '0' || buf[2] > '8' || buf[3]) continue; i += (buf[2] - '0') * 12 - 1; }else{ if(buf[1] < '0' || buf[1] > '8' || buf[2]) continue; i += (buf[1] - '0') * 12; } e = newevent(row << 8 | col << 3 | NOTE); fmtnote(e, i); insert(e); break; case 2: k = 'b'; goto kbdcmd; } } break; case 4: switch(menuhit(3, ⌖, &b3m, nil)){ case 0: if(eds == 0){ enter("you haven't done anything yet!", nil, 0, ⌖, ⌨, nil); continue; } sprint(buf, "1"); enter("undo", buf, sizeof buf, ⌖, ⌨, nil); i = -atoi(buf); if(i == 0) continue; hist(i, 0); j = ed - (i > 0 && ed > 0); row = edit[j]->index >> 8; col = edit[j]->index >> 3 & 31; break; case 1: k = 'j'; goto kbdcmd; case 2: k = 'k'; goto kbdcmd; case 3: k = 'c'; goto kbdcmd; case 4: k = 'v'; goto kbdcmd; case 5: k = 's'; goto kbdcmd; case 6: k = 'W'; goto kbdcmd; case 7: k = 0; goto kbdcmd; } break; case 8: if(row < skip) continue; row -= skip; break; case 16: if(row + skip > 0x7ffffe) continue; row += skip; break; } break; case KEYBOARD: kbdcmd: switch(k){ default: if(col == 18) continue; b = strchr(keys, k); if(b == nil) break; e = newevent(row << 8 | col << 3 | NOTE); fmtnote(e, oct + b - keys); insert(e); fd = startopl(); if(fd >= 0){ b = buf; t = row + 1 << 8; for(e = song; e != nil && e->index < t; e = e->next){ i = e->index >> 3 & 31; if(i == 18 || i == col){ b = process(e, b); if(b + 55 > buf + sizeof buf){ write(fd, buf, b - buf); b = buf; } } } if(b > buf) write(fd, buf, b - buf); sleep(tickns * tpr / 1000000); close(fd); setcursor(⌖, nil); } if(row + skip < 0x7ffffe) row += skip; break; case 'a': if(oct < 7 * 12) oct += 12; continue; case 'z': if(oct) oct -= 12; continue; case 'm': if(col < 18){ mute[col] ^= 0x30; break; } moveto(⌖, Pt(screen->r.min.x + 20 * wid, horiz + font->height)); *buf = 0; if(enter("mark", buf, sizeof buf - 8, ⌖, ⌨, nil) < 1) continue; e = newevent(row << 8 | 18 << 3 | MARK); e->mark = strdup(buf); insert(e); break; case 'x': if(col == 18) break; e = newevent(row << 8 | col << 3 | NOTE); fmtnote(e, 0xff); insert(e); break; case 'k': *buf = 0; enter("skip", buf, 5, ⌖, ⌨, nil); i = atoi(buf); if(i < 1 || i > 999) continue; sprint(b3s[2], "skip = %d", skip = i); skipoff = skip - row % skip; break; case 'l': *buf = 0; enter("loop", buf, 4, ⌖, ⌨, nil); i = atoi(buf); if(i < 1 || i > 255) continue; e = newevent(row << 8 | 18 << 3 | LOOP); e->loop = atoi(buf) & 0xff; insert(e); break; case 's': case 'c': p = Pt(screen->r.min.x + 21 * wid, screen->r.min.y); draw(screen, Rect(p.x, p.y, screen->r.max.x, p.y + font->height), bg[0], nil, ZP); if(snarfcol < 0){ snarfrow = row; snarfcol = col; sprint(buf, "selected %06ux-%c", row, col == 18 ? 'X' : 'A' + col); string(screen, p, fg, ZP, font, buf); flushimage(display, 1); strcpy(b3s[3], "cut"); strcpy(b3s[5], "snarf"); continue; } while(snarf != nil){ e = snarf; snarf = snarf->next; if((e->index & 7) == MARK) free(e->mark); free(e); } if(snarfrow < row){ i = row + 1 << 8; snarfrow <<= 8; }else{ i = snarfrow + 1 << 8; snarfrow = row << 8; } if(snarfcol < col){ j = col + 1 << 3; snarfcol <<= 3; }else{ j = snarfcol + 1 << 3; snarfcol = col << 3; } for(e = song; e != nil && e->index < snarfrow; e = e->next) ; for(ep = &snarf; e != nil && e->index < i; e = e->next) if((e->index & 0xf8) >= snarfcol && (e->index & 0xf8) < j){ *ep = newevent(e->index - snarfrow - snarfcol); evdup(*ep, e); ep = &(*ep)->next; } *ep = nil; if(k == 'c'){ for(ep = &song; *ep != nil && (*ep)->index < snarfrow; ep = &(*ep)->next) ; while(*ep != nil && (*ep)->index < i) if(((*ep)->index & 0xf8) >= snarfcol && ((*ep)->index & 0xf8) < j) delete(*ep); else ep = &(*ep)->next; } redraw(); sprint(buf, "%s %06ux-%c to %06ux-%c", k == 'c' ? "cut" : "snarfed", snarfrow, snarfcol == 18 ? 'X' : 'A' + snarfcol, row, col == 18 ? 'X' : 'A' + col); string(screen, p, fg, ZP, font, buf); flushimage(display, 1); strcpy(b3s[3], "select"); strcpy(b3s[5], "select"); snarfcol = -1; continue; case 'v': for(ep = &snarf; *ep != nil && row + ((*ep)->index >> 8) < 0x7fffff; ep = &(*ep)->next){ i = col + ((*ep)->index >> 3 & 31); j = (*ep)->index & 7; if(i > 18 || i == 18 && j < GLOBAL || j >= GLOBAL && i < 18) continue; e = newevent((row << 8) + (col << 3) + (*ep)->index); evdup(e, *ep); insert(e); } break; case Kesc: case ',': if(eds == 0) break; hist(-1, 0); row = edit[ed]->index >> 8; col = edit[ed]->index >> 3 & 31; break; case '.': if(ed == eds) break; row = edit[ed]->index >> 8; col = edit[ed]->index >> 3 & 31; hist(1, 0); break; case 'b': if(col == 18) break; i = row << 8 | col << 3 | BEND; for(e = song; e != nil && e->index < i; e = e->next) ; if(e != nil && e->index == i) sprint(buf, "%+d", e->bend); else *buf = 0; if(enter("32ths of a semitone to adjust per tick", buf, 5, ⌖, ⌨, nil) < 1) continue; e = newevent(i); e->bend = atoi(buf); if(e->bend < -128) e->bend = -128; if(e->bend > 127) e->bend = 127; insert(e); break; case Kdel: i = row << 8 | col << 3; ep = &song; while(*ep != nil && (*ep)->index < i) ep = &(*ep)->next; i += 8; while(*ep != nil && (*ep)->index < i) delete(*ep); break; case '\n': if(col == 18){ k = 0; e = newevent(row << 8 | 18 << 3 | GLOBAL); e->next = nil; for(ep = &song; *ep != nil && (*ep)->index <= e->index; ep = &(*ep)->next) if(((*ep)->index & 255) == (e->index & 255)) e->next = *ep; if(e->next != nil){ /* copy last global settings */ e->bpm = e->next->bpm; e->rpb = e->next->rpb; e->tpr = e->next->tpr; e->r104 = e->next->r104; e->rbd = e->next->rbd; }else{ /* default global settings */ e->bpm = 120; e->rpb = 4; e->tpr = 4; e->r104 = 0; e->rbd = 0; } drawglobaled: draw(screen, screen->r, bg[0], nil, ZP); p = screen->r.min; string(screen, p, fg, ZP, font, "Global Settings"); p.y += font->height; sprint(buf, "beats per minute: %d", e->bpm); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "rows per beat: %d", e->rpb); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "ticks per row: %d", e->tpr); string(screen, p, fg, ZP, font, buf); for(i = j = 0; i < 3; i++, j+= 2){ p.y += font->height; sprint(buf, "%c–%c paired: %d", 'A' + j, 'G' + j, e->r104 >> i & 1); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "%c–%c paired: %d", 'B' + j, 'H' + j, e->r104 >> i + 3 & 1); string(screen, p, fg, ZP, font, buf); } p.y += font->height; sprint(buf, "deep tremolo: %d", e->rbd >> 7 & 1); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "deep vibrato: %d", e->rbd >> 6 & 1); string(screen, p, fg, ZP, font, buf); flushimage(display, 1); while(k != '\n')switch(alt(a)){ case RESIZE: resize(); goto drawglobaled; case MOUSE: if(⌖->buttons != 1) break; switch((⌖->xy.y - screen->r.min.y) / font->height){ case 1: sprint(buf, "%d", e->bpm); if(enter("BPM (assuming 4/4 time)", buf, 4, ⌖, ⌨, nil) < 1) break; i = atoi(buf); if(i >= 30 && i <= 285){ e->bpm = i; goto drawglobaled; } enter("must be between 30 and 285", nil, 0, ⌖, ⌨, nil); break; case 2: sprint(buf, "%d", e->rpb); if(enter("rows per beat", buf, 3, ⌖, ⌨, nil) < 1) break; i = atoi(buf); if(i >= 1 && i <= 8){ e->rpb = i; goto drawglobaled; } enter("must be between 1 and 8", nil, 0, ⌖, ⌨, nil); break; case 3: sprint(buf, "%d", e->tpr); if(enter("ticks per row", buf, 3, ⌖, ⌨, nil) < 1) break; i = atoi(buf); if(i >= 1 && i <= 8){ e->tpr = i; goto drawglobaled; } enter("must be between 1 and 8", nil, 0, ⌖, ⌨, nil); break; case 4: reged(&e->r104, 1, 0); goto drawglobaled; case 5: reged(&e->r104, 1, 3); goto drawglobaled; case 6: reged(&e->r104, 1, 1); goto drawglobaled; case 7: reged(&e->r104, 1, 4); goto drawglobaled; case 8: reged(&e->r104, 1, 2); goto drawglobaled; case 9: reged(&e->r104, 1, 5); goto drawglobaled; case 10: reged(&e->rbd, 1, 7); goto drawglobaled; case 11: reged(&e->rbd, 1, 6); goto drawglobaled; } } insert(e); break; } k = 0; e = newevent(row << 8 | col << 3 | INSTR); e->next = nil; for(ep = &song; *ep != nil && (*ep)->index <= e->index; ep = &(*ep)->next) if(((*ep)->index & 255) == (e->index & 255)) e->next = *ep; if(e->next != nil){ /* copy last instrument */ e->r20 = e->next->r20; e->r23 = e->next->r23; e->r40 = e->next->r40; e->r43 = e->next->r43; e->r60 = e->next->r60; e->r63 = e->next->r63; e->r80 = e->next->r80; e->r83 = e->next->r83; e->re0 = e->next->re0; e->re3 = e->next->re3; e->rc0 = e->next->rc0; }else{ /* basic default instrument */ e->r20 = 0; e->r23 = 0; e->r40 = 0x20; e->r43 = 0; e->r60 = 0xd0; e->r63 = 0xd0; e->r80 = 0x05; e->r83 = 0x05; e->re0 = 0; e->re3 = 0; e->rc0 = 0x30; } fd = startopl(); if(fd >= 0) e->next = newevent(col << 3 | NOTE); drawinstred: draw(screen, screen->r, bg[0], nil, ZP); p = screen->r.min; sprint(buf, "Instrument %c", 'A' + col); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "AM Mode: %d", e->rc0 & 1); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Left Channel: %d", e->rc0 >> 4 & 1); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Right Channel: %d", e->rc0 >> 5 & 1); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Feedback: %d", e->rc0 >> 1 & 7); string(screen, p, fg, ZP, font, buf); p.x += Dx(screen->r) / 3; p.y = screen->r.min.y; string(screen, p, fg, ZP, font, "Operator 1"); p.y += font->height; sprint(buf, "Tremolo: %d", e->r20 >> 7); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Vibrato: %d", e->r20 >> 6 & 1); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Sustaining: %d", e->r20 >> 5 & 1); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Env Scaling: %d", e->r20 >> 4 & 1); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Multiplier: %d", e->r20 & 15); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Attack: %d", e->r60 >> 4 & 15); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Decay: %d", e->r60 & 15); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Sustain: %d", e->r80 >> 4 & 15); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Release: %d", e->r80 & 15); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Waveform: %s", wave[e->re0]); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Attenuation/oct: %d", e->r40 >> 6); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Attenuation: %d", e->r40 & 63); string(screen, p, fg, ZP, font, buf); p.x += Dx(screen->r) / 3; p.y = screen->r.min.y; string(screen, p, fg, ZP, font, "Operator 1"); p.y += font->height; sprint(buf, "Tremolo: %d", e->r23 >> 7); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Vibrato: %d", e->r23 >> 6 & 1); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Sustaining: %d", e->r23 >> 5 & 1); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Env Scaling: %d", e->r23 >> 4 & 1); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Multiplier: %d", e->r23 & 15); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Attack: %d", e->r63 >> 4 & 15); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Decay: %d", e->r63 & 15); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Sustain: %d", e->r83 >> 4 & 15); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Release: %d", e->r83 & 15); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Waveform: %s", wave[e->re3]); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Attenuation/oct: %d", e->r43 >> 6); string(screen, p, fg, ZP, font, buf); p.y += font->height; sprint(buf, "Attenuation: %d", e->r43 & 63); string(screen, p, fg, ZP, font, buf); flushimage(display, 1); if(fd >= 0) write(fd, buf, process(e, buf) - buf); while(k != '\n')switch(alt(a)){ case RESIZE: resize(); goto drawinstred; case MOUSE: if(⌖->buttons != 1) break; i = (⌖->xy.x - screen->r.min.x) * 3 / Dx(screen->r); j = (⌖->xy.y - screen->r.min.y) / font->height; if(i == 0){ switch(j){ case 1: reged(&e->rc0, 1, 0); goto drawinstred; case 2: reged(&e->rc0, 1, 4); goto drawinstred; case 3: reged(&e->rc0, 1, 5); goto drawinstred; case 4: reged(&e->rc0, 7, 1); goto drawinstred; } break; } switch(j){ case 1: reged(i > 1 ? &e->r23 : &e->r20, 1, 7); goto drawinstred; case 2: reged(i > 1 ? &e->r23 : &e->r20, 1, 6); goto drawinstred; case 3: reged(i > 1 ? &e->r23 : &e->r20, 1, 5); goto drawinstred; case 4: reged(i > 1 ? &e->r23 : &e->r20, 1, 4); goto drawinstred; case 5: reged(i > 1 ? &e->r23 : &e->r20, 15, 0); goto drawinstred; case 6: reged(i > 1 ? &e->r63 : &e->r60, 15, 4); goto drawinstred; case 7: reged(i > 1 ? &e->r63 : &e->r60, 15, 0); goto drawinstred; case 8: reged(i > 1 ? &e->r83 : &e->r80, 15, 4); goto drawinstred; case 9: reged(i > 1 ? &e->r83 : &e->r80, 15, 0); goto drawinstred; case 10: if(i > 1) e->re3 = menu(e->re3, wave, 1); else e->re0 = menu(e->re0, wave, 1); goto drawinstred; case 11: reged(i > 1 ? &e->r43 : &e->r40, 3, 6); goto drawinstred; case 12: reged(i > 1 ? &e->r43 : &e->r40, 63, 0); goto drawinstred; } break; case KEYBOARD: if(fd >= 0)switch(k){ default: b = strchr(keys, k); if(b == nil) break; fmtnote(e->next, b - keys + oct); write(fd, buf, process(e->next, buf) - buf); break; case 'x': case Kesc: fmtnote(e->next, 0xff); write(fd, buf, process(e->next, buf) - buf); break; case 'a': if(oct < 7 * 12) oct += 12; break; case 'z': if(oct) oct -= 12; break; } break; } if(fd >= 0){ close(fd); free(e->next); setcursor(⌖, nil); } insert(e); break; case 'j': *buf = 0; if(enter("jump to mark or row", buf, sizeof buf, ⌖, ⌨, nil) < 1) continue; e = song; while(e != nil && ((e->index & 7) != MARK || strcmp(buf, e->mark))) e = e->next; row = e == nil ? strtoul(buf, nil, 16) : e->index >> 8; if(row > 0x7ffffe) row = 0x7ffffe; break; case Khome: row = 0; break; case Kend: if(song == nil) row = 0; else{ for(e = song; e->next != nil; e = e->next) ; row = e->index >> 8; } break; case Kleft: col = col ? col - 1 : 18; break; case Kright: col = col == 18 ? 0 : col + 1; break; case Kup: if(row < skip) continue; row -= skip; break; case Kdown: if(row + skip > 0x7ffffe) continue; row += skip; break; case Kpgup: /* jump to last event in this column */ i = row << 8; j = 0; for(e = song; e != nil && e->index < i; e = e->next) if((e->index >> 3 & 31) == col) j = e->index; row = j >> 8; break; case Kpgdown: i = row + 1 << 8; for(e = song; e != nil && e->index < i; e = e->next) ; while(e != nil && (e->index >> 3 & 31) != col) e = e->next; if(e != nil) row = e->index >> 8; break; case ' ': fd = startopl(); if(fd < 0){ seprint(buf, buf + sizeof buf, "opl pipeline: %r"); enter(buf, nil, 0, ⌖, ⌨, nil); break; } a[NOTHING].op = CHANNOBLK; j = row; e = song; loopend = -1; looping = 0; for(row = 0; e != nil && row < j; row++){ for(b = buf; e != nil && (e->index >> 8) == row; e = e->next){ for(i = 0; i < 18; i++) bend[i] = 0; b = process(e, b); for(i = 0; i < 18; i++) bend[i] *= tpr; b = bends(b); } if(b > buf) write(fd, buf, b - buf); if(looping && row == loopend){ i = loopstart << 8; for(e = song; e != nil && e->index < i; e = e->next) ; row = loopstart - 1; } } t = nsec(); while(e != nil)switch(alt(a)){ case RESIZE: resize(); redraw(); break; case KEYBOARD: switch(k){ default: if(col == 18 || (b = strchr(keys, k)) == nil) break; e = newevent(row << 8 | col << 3 | NOTE); fmtnote(e, b - keys + oct); write(fd, buf, process(e, buf) - buf); insert(e); break; case 'x': if(col == 18) break; e = newevent(row << 8 | col << 3 | NOTE); fmtnote(e, 0xff); write(fd, buf, process(e, buf) - buf); insert(e); break; case Kesc: j = row; case ' ': e = nil; break; case 'a': if(oct < 7 * 12) oct += 12; break; case 'z': if(oct) oct -= 12; break; case 'm': if(col == 18) break; mute[col] ^= 0x30; buf[0] = 0xc0 | col >> 1; buf[1] = col & 1; buf[2] = rc0[col] & ~mute[col]; buf[3] = buf[4] = 0; write(fd, buf, 5); break; case Kleft: col = col ? col - 1 : 18; break; case Kright: col = col == 18 ? 0 : col + 1; break; } break; case NOTHING: for(i = 0; i < 18; i++) bend[i] = 0; for(b = buf; e != nil && (e->index >> 8) == row; e = e->next) b = process(e, b); if(b > buf) write(fd, buf, b - buf); redraw(); for(k = 0; k < tpr; k++){ t += tickns; i = (t - nsec()) / 1000000; if(i > 1) sleep(i); b = bends(buf); if(b > buf) write(fd, buf, b - buf); } if(looping && row == loopend){ i = loopstart << 8; for(e = song; e != nil && e->index < i; e = e->next) ; row = loopstart; }else row++; break; } close(fd); row = j; a[NOTHING].op = CHANEND; setcursor(⌖, nil); break; case 'W': if(path == nil) *buf = 0; else strcpy(buf, path); if(enter("write", buf, sizeof buf, ⌖, ⌨, nil) < 1) continue; fd = create(buf, OWRITE, 0666); if(fd < 0){ seprint(buf, buf + sizeof buf, "create failed: %r"); enter(buf, nil, 0, ⌖, ⌨, nil); continue; } if(path == nil || strcmp(buf, path)){ if(path != argv[1]) free(path); path = strdup(buf); j = open("/dev/label", OWRITE); if(j >= 0){ fprint(j, "%s %s", argv[0], path); close(j); } } e = song; t = 0x100; while(9){ b = buf; while(e != nil){ while(t < e->index && b < buf + sizeof buf){ *b++ = NEXTROW; t += 0x100; } switch(e->index & 7){ case INSTR: if(b + 12 > buf + sizeof buf) break; b[0] = e->index; b[1] = e->r20; b[2] = e->r23; b[3] = e->r40; b[4] = e->r43; b[5] = e->r60; b[6] = e->r63; b[7] = e->r80; b[8] = e->r83; b[9] = e->re0; b[10] = e->re3; b[11] = e->rc0; b += 12; e = e->next; continue; case NOTE: if(b + 2 > buf + sizeof buf) break; b[0] = e->index; b[1] = e->note[0] == 'x' ? 0xff : e->freq / 32 + 57 - 19 * 12; b += 2; e = e->next; continue; case BEND: if(b + 2 > buf + sizeof buf) break; b[0] = e->index; b[1] = e->bend; b += 2; e = e->next; continue; case GLOBAL: if(b + 5 > buf + sizeof buf) break; b[0] = e->index; b[1] = e->bpm - 30; b[2] = e->tpr - 1 << 4 | e-> rpb - 1; b[3] = e->r104; b[4] = e->rbd; b += 5; e = e->next; continue; case LOOP: if(b + 2 > buf + sizeof buf) break; b[0] = e->index; b[1] = e->loop; b += 2; e = e->next; continue; case MARK: if(b + strlen(e->mark) + 2 > buf + sizeof buf) break; b[0] = e->index; strcpy(b + 1, e->mark); b = strchr(b, 0) + 1; t += 0x100; e = e->next; continue; } break; } if(b == buf) break; if(write(fd, buf, b - buf) != b - buf){ close(fd); seprint(buf, buf + sizeof buf, "write: %r"); enter(buf, nil, 0, ⌖, ⌨, nil); continue; } } close(fd); continue; case 'Q': case Keof: case 0: j = k; setcursor(⌖, &skull); do i = alt(a); while(i == MOUSE && ⌖->buttons == 0); if(j == 0 && i == MOUSE && ⌖->buttons == 4 || i == KEYBOARD && j == k) threadexitsall(nil); setcursor(⌖, nil); continue; } break; }redraw();} }