#include #include #include #include #include #include /* instrument editor/player for OP2 instrument banks * (plus some opl3 extensions). See also: opl3(1), dmid(1) * http://www.fit.vutbr.cz/~arnost/opl/opl3.html * https://moddingwiki.shikadi.net/wiki/OP2_Bank_Format */ uchar bank[8 + 175 * 36 + 175 * 32], *instr; char *tee, *keys = "\t1q2we4r5t6yu8i9op-[=]\b\\\n"; int oplon, snarf; Image *bg, *fg, *grey, *mask[25]; Rectangle keyr, box; Channel *oplchan; Mousectl *⌖; enum{TEE = 8192, NOTEON = 0xA0, NOTEOFF = 0xB0, INSTR = 0xC0, VOICE = 0xD0, STOP = 0xF0}; char *mb3[] = {"shut up", "write bank", "read instr", "write instr", "snarf", "paste", "zero", "quit", nil}; char *tremdepth[] = {"Std Tremolo", "Deep Tremolo", nil}; char *vibdepth[] = {"Std Vibrato", "Deep Vibrato", nil}; char *toggle[] = {"off", "on", nil}; char *fmam[] = {"Modulator", "Carrier", nil}; char *octs[] = {"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", nil}; char *mode[] = { "4-Op FMFM", "4-Op AMFM", "4-Op FMAM", "4-Op AMAM", "FM + FM", "AM + FM", "FM + AM", "AM + AM", "2-Op FM", "2-Op AM", nil }; char *pan[] = {"Stereo", "Right", "Left", "Silent", nil}; char *nibble[] = {"0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15",nil}; char *mult[] = {"½", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "10", "12", "12", "15", "15", nil}; char *ksl[] = {"none", "1.5 dB/oct", "3.0 dB/oct", "6.0 dB/oct", nil}; char *wave[] = { /* OPL2 */ "Sine", "Half Sine", "Absolute Sine", "Pulse Sine", /*OPL3 */ "Even Sine", "Even Abs. Sine", "Square", "Derived Square", nil }; char *feedback[] = {"0", "¹⁄₁₆", "⅛", "¼", "½", "1", "2", "4", nil}; char *pitch[] = { "n/a", "C0","C#0","D0","D#0","E0","F0","F#0","G0","G#0","A0","A#0","B0", "C1","C#1","D1","D#1","E1","F1","F#1","G1","G#1","A1","A#1","B1", "C2","C#2","D2","D#2","E2","F2","F#2","G2","G#2","A2","A#2","B2", "C3","C#3","D3","D#3","E3","F3","F#3","G3","G#3","A3","A#3","B3", "C4","C#4","D4","D#4","E4","F4","F#4","G4","G#4","A4","A#4","B4", "C5","C#5","D5","D#5","E5","F5","F#5","G5","G#5","A5","A#5","B5", "C6","C#6","D6","D#6","E6","F6","F#6","G6","G#6","A6","A#6","B6", "C7","C#7","D7","D#7","E7","F7","F#7","G7","G#7","A7","A#7","B7", "C8","C#8","D8","D#8","E8","F8","F#8",nil }; char *vol[] = { "63","62","61","60","59","58","57","56","55","54","53","52","51","50","49","48", "47","46","45","44","43","42","41","40","39","38","37","36","35","34","33","32", "31","30","29","28","27","26","25","24","23","22","21","20","19","18","17","16", "15","14","13","12","11","10","9","8","7","6","5","4","3","2","1","0", nil }; char *tuning[] = { "-64","-63","-62","-61","-60","-59","-58","-57","-56","-55","-54","-53","-52","-51","-50","-49", "-48","-47","-46","-45","-44","-43","-42","-41","-40","-39","-38","-37","-36","-35","-34","-33", "-32","-31","-30","-29","-28","-27","-26","-25","-24","-23","-22","-21","-20","-19","-18","-17", "-16","-15","-14","-13","-12","-11","-10","-9","-8","-7","-6","-5","-4","-3","-2","-1", "0","+1","+2","+3","+4","+5","+6","+7","+8","+9","+10","+11","+12","+13","+14","+15", "+16","+17","+18","+19","+20","+21","+22","+23","+24","+25","+26","+27","+28","+29","+30","+31", "+32","+33","+34","+35","+36","+37","+38","+39","+40","+41","+42","+43","+44","+45","+46","+47", "+48","+49","+50","+51","+52","+53","+54","+55","+56","+57","+58","+59","+60","+61","+62","+63", nil }; char* namemenugen(int); char instrstr[38]; Menu b3 = {mb3, nil, 0}, oct = {octs, nil, 4}, m[] = { {pan}, {tuning}, {feedback}, {fmam}, {toggle}, {toggle}, {toggle}, {toggle}, {mult}, {nibble}, {nibble}, {nibble}, {nibble}, {wave}, {ksl}, {vol}, {mode}, {nil, namemenugen}, {tremdepth}, {fmam}, {toggle}, {toggle}, {toggle}, {toggle}, {mult}, {nibble}, {nibble}, {nibble}, {nibble}, {wave}, {ksl}, {vol}, {pan}, {tuning}, {feedback}, {fmam}, {toggle}, {toggle}, {toggle}, {toggle}, {mult}, {nibble}, {nibble}, {nibble}, {nibble}, {wave}, {ksl}, {vol}, {vibdepth}, {pitch}, {tuning}, {fmam, nil, 1}, {toggle}, {toggle}, {toggle}, {toggle}, {mult}, {nibble}, {nibble}, {nibble}, {nibble}, {wave}, {ksl}, {vol} }; /* 0xA0, 0xB0 bits: 00kbbbff ffffffff, k = key on, b = Block, f = F-Number F-Number = Music Frequency * 2^(20-Block) / 49716 Hz */ short freq[] = { 0x158, 0x16d, 0x183, 0x19a, 0x1b2, 0x1cc, 0x1e7, 0x204, 0x223, 0x244, 0x266, 0x28b, 0x2b1, 0x2da, 0x306, 0x334, 0x365, 0x398, 0x3cf, 0x604, 0x623, 0x644, 0x666, 0x68b, 0x6b1, 0x6da, 0x706, 0x734, 0x765, 0x798, 0x7cf, 0xa04, 0xa23, 0xa44, 0xa66, 0xa8b, 0xab1, 0xada, 0xb06, 0xb34, 0xb65, 0xb98, 0xbcf, 0xe04, 0xe23, 0xe44, 0xe66, 0xe8b, 0xeb1, 0xeda, 0xf06, 0xf34, 0xf65, 0xf98, 0xfcf, 0x1204, 0x1223, 0x1244, 0x1266, 0x128b, 0x12b1, 0x12da, 0x1306, 0x1334, 0x1365, 0x1398, 0x13cf, 0x1604, 0x1623, 0x1644, 0x1666, 0x168b, 0x16b1, 0x16da, 0x1706, 0x1734, 0x1765, 0x1798, 0x17cf, 0x1a04, 0x1a23, 0x1a44, 0x1a66, 0x1a8b, 0x1ab1, 0x1ada, 0x1b06, 0x1b34, 0x1b65, 0x1b98, 0x1bcf, 0x1e04, 0x1e23, 0x1e44, 0x1e66, 0x1e8b, 0x1eb1, 0x1eda, 0x1f06, 0x1f34, 0x1f65, 0x1f98, 0x1fcf /* can't store frequencies higher than this… */ }; void oplproc(void *arg){ Channel *ok; ulong reg; long n; int fd, p[2]; char buf[] = { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0x20, 0, 0, 5, 1, 1, 0, 0 }; ok = arg; fd = open("/dev/audio", OWRITE); if(fd < 0){ sendp(ok, "/dev/audio busy"); exits(nil); } if(pipe(p) < 0){ sendp(ok, "pipe failed"); exits(nil); } switch(fork()){ case -1: sendp(ok, "fork failed"); exits(nil); case 0: close(p[0]); dup(p[1], 0); close(p[1]); if(tee != nil){ if(pipe(p) < 0) sysfatal("pipe: %r"); switch(fork()){ case -1: sysfatal("fork: %r"); case 0: close(p[1]); while((n = read(p[0], tee, TEE)) > 1){ write(1, tee, n); write(fd, tee, n); } close(p[0]); exits(nil); } close(p[0]); dup(p[1], 1); close(p[1]); } else{ dup(fd, 1); close(fd); } execl("/bin/games/opl3", "opl3", "-s", nil); sysfatal("execl: %r"); } close(fd); close(p[1]); write(p[0], buf + 50, 10); sendp(ok, nil); while(9){ reg = recvul(oplchan); switch(reg & 0xF0){ case STOP: close(p[0]); wait(); sendp(ok, nil); exits(nil); case INSTR: buf[0] = buf[15] = 0x20; buf[5] = buf[20] = 0x21; buf[10] = buf[25] = 0x22; buf[2] = buf[7] = buf[12] = buf[17] = buf[22] = buf[27] = instr[4]; buf[30] = buf[45] = 0x23; buf[35] = buf[50] = 0x24; buf[40] = buf[55] = 0x25; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = instr[11]; write(p[0], buf, 60); buf[0] = buf[15] = 0x28; buf[5] = buf[20] = 0x29; buf[10] = buf[25] = 0x2A; buf[2] = buf[7] = buf[12] = buf[17] = buf[22] = buf[27] = instr[20]; buf[30] = buf[45] = 0x2B; buf[35] = buf[50] = 0x2C; buf[40] = buf[55] = 0x2D; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = instr[27]; write(p[0], buf, 60); buf[0] = buf[15] = 0x40; buf[5] = buf[20] = 0x41; buf[10] = buf[25] = 0x42; buf[2] = buf[7] = buf[12] = buf[17] = buf[22] = buf[27] = instr[8] | instr[9]; buf[30] = buf[45] = 0x43; buf[35] = buf[50] = 0x44; buf[40] = buf[55] = 0x45; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = instr[15] | instr[16]; write(p[0], buf, 60); buf[0] = buf[15] = 0x48; buf[5] = buf[20] = 0x49; buf[10] = buf[25] = 0x4A; buf[2] = buf[7] = buf[12] = buf[17] = buf[22] = buf[27] = instr[24] | instr[25]; buf[30] = buf[45] = 0x4B; buf[35] = buf[50] = 0x4C; buf[40] = buf[55] = 0x4D; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = instr[31] | instr[32]; write(p[0], buf, 60); buf[0] = buf[15] = 0x60; buf[5] = buf[20] = 0x61; buf[10] = buf[25] = 0x62; buf[2] = buf[7] = buf[12] = buf[17] = buf[22] = buf[27] = instr[5]; buf[30] = buf[45] = 0x63; buf[35] = buf[50] = 0x64; buf[40] = buf[55] = 0x65; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = instr[12]; write(p[0], buf, 60); buf[0] = buf[15] = 0x68; buf[5] = buf[20] = 0x69; buf[10] = buf[25] = 0x6A; buf[2] = buf[7] = buf[12] = buf[17] = buf[22] = buf[27] = instr[21]; buf[30] = buf[45] = 0x6B; buf[35] = buf[50] = 0x6C; buf[40] = buf[55] = 0x6D; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = instr[28]; write(p[0], buf, 60); buf[0] = buf[15] = 0x80; buf[5] = buf[20] = 0x81; buf[10] = buf[25] = 0x82; buf[2] = buf[7] = buf[12] = buf[17] = buf[22] = buf[27] = instr[6]; buf[30] = buf[45] = 0x83; buf[35] = buf[50] = 0x84; buf[40] = buf[55] = 0x85; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = instr[13]; write(p[0], buf, 60); buf[0] = buf[15] = 0x88; buf[5] = buf[20] = 0x89; buf[10] = buf[25] = 0x8A; buf[2] = buf[7] = buf[12] = buf[17] = buf[22] = buf[27] = instr[22]; buf[30] = buf[45] = 0x8B; buf[35] = buf[50] = 0x8C; buf[40] = buf[55] = 0x8D; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = instr[29]; write(p[0], buf, 60); buf[0] = buf[15] = 0xE0; buf[5] = buf[20] = 0xE1; buf[10] = buf[25] = 0xE2; buf[2] = buf[7] = buf[12] = buf[17] = buf[22] = buf[27] = instr[7]; buf[30] = buf[45] = 0xE3; buf[35] = buf[50] = 0xE4; buf[40] = buf[55] = 0xE5; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = instr[14]; write(p[0], buf, 60); buf[0] = buf[15] = 0xE8; buf[5] = buf[20] = 0xE9; buf[10] = buf[25] = 0xEA; buf[2] = buf[7] = buf[12] = buf[17] = buf[22] = buf[27] = instr[23]; buf[30] = buf[45] = 0xEB; buf[35] = buf[50] = 0xEC; buf[40] = buf[55] = 0xED; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = instr[30]; write(p[0], buf, 60); case VOICE: buf[0] = 0xBD; buf[2] = instr[1] & 0xC0; /* deep vibrations */ buf[5] =0x04; buf[7] = m[16].lasthit < 4 ? 0x3F : 0; /* 4-Op mode */ write(p[0], buf, 10); buf[0] = buf[5] = 0xC0; buf[10] = buf[15] = 0xC1; buf[20] = buf[25] = 0xC2; buf[2] = buf[7] = buf[12] = buf[17] = buf[22] = buf[27] = ~instr[1] << 4 & 0x30 | instr[10]; buf[30] = buf[35] = 0xC3; buf[40] = buf[45] = 0xC4; buf[50] = buf[55] = 0xC5; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = ~instr[1] << 2 & 0x30 | instr[26]; write(p[0], buf, 60); break; case NOTEON: buf[50] = reg; buf[55] = reg | 0x10; buf[51] = buf[56] = reg >> 8; buf[52] = reg >> 16; buf[57] = 0x20 | reg >> 24; write(p[0], buf + 50, 10); buf[51] = 0; buf[56] = 1; break; case NOTEOFF: buf[50] = reg; buf[55] = reg + 3; buf[51] = buf[56] = reg >> 8; buf[52] = reg >> 16; buf[57] = reg >> 24; write(p[0], buf + 50, 10); buf[51] = 0; buf[56] = 1; break; case 0x20: case 0x40: case 0x60: case 0x80: case 0xE0: buf[30] = buf[35] = reg; buf[40] = buf[45] = reg + 1; buf[50] = buf[55] = reg + 2; buf[32] = buf[37] = buf[42] = buf[47] = buf[52] = buf[57] = reg >> 8; write(p[0], buf + 30, 30); break; } } } char* namemenugen(int n){ if(n == m[17].lasthit) return "~~RENAME~~"; if(n >= 0 && n < 175) return (char*)bank + 8 + 175 * 36 + n * 32; return nil; } void drawkey(int i, Image *colour){ if(colour == nil) colour = mask[i]->r.max.y == font->height << 1 ? display->white : display->black; draw(screen, keyr, colour, mask[i], ZP); } void redraw(void){ Point o; Image *two; int i; two = m[16].lasthit & 8 ? grey : fg; draw(screen, screen->r, bg, nil, ZP); o.x = screen->r.min.x + 2 * box.max.x + (stringwidth(font, pan[0]) - 181 >> 1); o.y = screen->r.min.y + 3 * font->height / 2; keyr = rectaddpt(Rect(0, 0, 181, 2 * font->height), o); draw(screen, keyr, display->black, nil, ZP); for(i = 0; i < 25; i++) drawkey(i, nil); o.x += 181 - stringwidth(font, instrstr) >> 1; o.y = screen->r.min.y; string(screen, o, fg, ZP, font, instrstr); o.x = screen->r.min.x; string(screen, o, fg, ZP, font, mode[m[16].lasthit]); o.y += font->height; string(screen, o, fg, ZP, font, "Panning: "); o.y += font->height; string(screen, o, fg, ZP, font, "Note Offset: "); o.y += font->height; string(screen, o, fg, ZP, font, "Feedback: "); o.y += font->height; string(screen, o, fg, ZP, font, "Operator: "); o.y += font->height; string(screen, o, fg, ZP, font, "Tremolo: "); o.y += font->height; string(screen, o, fg, ZP, font, "Vibrato: "); o.y += font->height; string(screen, o, fg, ZP, font, "Sustaining: "); o.y += font->height; string(screen, o, fg, ZP, font, "Env Scaling: "); o.y += font->height; string(screen, o, fg, ZP, font, "Multiplier: "); o.y += font->height; string(screen, o, fg, ZP, font, "Attack: "); o.y += font->height; string(screen, o, fg, ZP, font, "Decay: "); o.y += font->height; string(screen, o, fg, ZP, font, "Sustain: "); o.y += font->height; string(screen, o, fg, ZP, font, "Release: "); o.y += font->height; string(screen, o, fg, ZP, font, "Wave: "); o.y += font->height; string(screen, o, fg, ZP, font, "Attenuation: "); o.y += font->height; string(screen, o, fg, ZP, font, "Volume: "); o.x += box.max.x; o.y = screen->r.min.y + font->height; for(i = 0; i < 16; i++, o.y += font->height) string(screen, o, fg, ZP, font, m[i].item[m[i].lasthit]); o.x += box.max.x; o.y = screen->r.min.y + 4 * font->height; for(i = 19; i < 32; i++, o.y += font->height) string(screen, o, fg, ZP, font, m[i].item[m[i].lasthit]); o.x += box.max.x; o.y = screen->r.min.y + font->height; for(i = 32; i < 48; i++, o.y += font->height) string(screen, o, two, ZP, font, m[i].item[m[i].lasthit]); o.x += box.max.x; o.y = screen->r.min.y; string(screen, o, fg, ZP, font, tremdepth[m[18].lasthit]); o.y += font->height; string(screen, o, fg, ZP, font, vibdepth[m[48].lasthit]); o.y += font->height; string(screen, string(screen, o, fg, ZP, font, "Fixed Pitch: "), fg, ZP, font, pitch[m[49].lasthit]); o.y += font->height; string(screen, string(screen, o, fg, ZP, font, "Fine Tune: "), fg, ZP, font, tuning[m[50].lasthit]); o.y += font->height; for(i = 51; i < 64; i++, o.y += font->height) string(screen, o, two, ZP, font, m[i].item[m[i].lasthit]); flushimage(display, 1); } ulong keydown(int n, int i){ int f1, f2, addr; addr = (i > 2) << 8 | i % 3; i = n + m[1].lasthit - 64; f1 = i <= 0 ? freq[0] : i >= nelem(freq) ? freq[nelem(freq) - 1] : freq[i]; sendul(oplchan, f1 << 16 | NOTEON | addr); if(!(m[16].lasthit & 4)) /* single voice */ return f1 << 8 & 0x1F0000 | NOTEOFF | addr; i = n + m[33].lasthit - 64; if(i < 0) i = 0; if(i >= nelem(freq)) i = nelem(freq) - 1; /* not sure exactly how finetune works... */ n = m[50].lasthit - 64; if(n < 0 && i) f2 = freq[i] + (freq[i] - freq[i - 1]) * n / 64; else if(n > 0 && i < 127) f2 = freq[i] + (freq[i + 1] - freq[i]) * n / 64; else f2 = freq[i]; sendul(oplchan, f2 << 16 | NOTEON | addr + 3); return f2 << 16 & 0x1F000000 | f1 << 8 & 0x1F0000 | NOTEOFF | addr; } int keyevents(char *down){ static char k[6]; static int off[6], v; char *key; int i; /* special case for spacebar one-shot */ if(down == nil){ v = (v + 1) % 6; k[v] = ' '; off[v] = keydown(instr[0] & 1 ? m[49].lasthit + 1 : oct.lasthit * 12, v); return 1; } /* first release any keys that were down */ for(i = 0; i < 6; i++) if(!strchr(down, k[i])){ key = strchr(keys, k[i]); k[i] = 0; sendul(oplchan, off[i]); if(key) drawkey(key - keys, nil); } /* then trigger any new notes */ for(; down && *down; down++) if(key = strchr(keys, *down)){ for(i = 0; i < 6 && k[i] != *down; i++) ; if(i < 6) /* key is already down */ continue; i = (v + 1) % 6; while(k[i] && i != v) i = (i + 1) % 6; if(k[i]) /* all six voices have keys down */ break; k[v = i] = *down; off[v] = keydown(12 * oct.lasthit + key - keys, v); drawkey(key - keys, fg); } flushimage(display, 1); return k[0] || k [1] || k[2] || k[3] || k[4] || k[5]; } void initinstr(void){ instr = bank + 8 + m[17].lasthit * 36; sprint(instrstr, "[%d] %.31s", m[17].lasthit, (char*)bank + 8 + 175 * 36 + m[17].lasthit * 32); m[49].lasthit = instr[0] & 1 && instr[3] >= 12 && instr[3] <= 114 ? instr[3] - 11 : 0; if(instr[2] < 64 || instr[2] >= 192){ fprint(2, "ignoring finetune >semitone: 0x%x\n", instr[2]); m[50].lasthit = 64; } else m[50].lasthit = instr[2] - 64; m[4].lasthit = instr[4] >> 7; m[5].lasthit = instr[4] >> 6 & 1; m[6].lasthit = instr[4] >> 5 & 1; m[7].lasthit = instr[4] >> 4 & 1; m[8].lasthit = instr[4] & 0xF; m[9].lasthit = instr[5] >> 4; m[10].lasthit = instr[5] & 0xF; m[11].lasthit = instr[6] >> 4; m[12].lasthit = instr[6] & 0xF; m[13].lasthit = instr[7]; m[14].lasthit = instr[8] >> 6; m[15].lasthit = instr[9] & 0x3F; m[2].lasthit = instr[10] >> 1 & 7; m[20].lasthit = instr[11] >> 7; m[21].lasthit = instr[11] >> 6 & 1; m[22].lasthit = instr[11] >> 5 & 1; m[23].lasthit = instr[11] >> 4 & 1; m[24].lasthit = instr[11] & 0xF; m[25].lasthit = instr[12] >> 4; m[26].lasthit = instr[12] & 0xF; m[27].lasthit = instr[13] >> 4; m[28].lasthit = instr[13] & 0xF; m[29].lasthit = instr[14]; m[30].lasthit = instr[15] >> 6; m[31].lasthit = instr[16] & 0x3F; if(instr[19] && instr[19] != 0xFF || instr[18] >= 64 && instr[18] < 0xC0){ fprint(2, "ignoring first voice note offset >64 semitones: 0x%x%x\n", instr[19], instr[18]); m[1].lasthit = 0; } else m[1].lasthit = (schar)instr[18] + 64; m[36].lasthit = instr[20] >> 7; m[37].lasthit = instr[20] >> 6 & 1; m[38].lasthit = instr[20] >> 5 & 1; m[39].lasthit = instr[20] >> 4 & 1; m[40].lasthit = instr[20] & 0xF; m[41].lasthit = instr[21] >> 4; m[42].lasthit = instr[21] & 0xF; m[43].lasthit = instr[22] >> 4; m[44].lasthit = instr[22] & 0xF; m[45].lasthit = instr[23]; m[46].lasthit = instr[24] >> 6; m[47].lasthit = instr[25] & 0x3F; m[34].lasthit = instr[26] >> 1 & 7; m[52].lasthit = instr[27] >> 7; m[53].lasthit = instr[27] >> 6 & 1; m[54].lasthit = instr[27] >> 5 & 1; m[55].lasthit = instr[27] >> 4 & 1; m[56].lasthit = instr[27] & 0xF; m[57].lasthit = instr[28] >> 4; m[58].lasthit = instr[28] & 0xF; m[59].lasthit = instr[29] >> 4; m[60].lasthit = instr[29] & 0xF; m[61].lasthit = instr[30]; m[62].lasthit = instr[31] >> 6; m[63].lasthit = instr[32] & 0x3F; if(instr[35] && instr[35] != 0xFF || instr[34] >= 64 && instr[34] < 0xC0){ fprint(2, "ignoring second voice note offset >64 semitones: 0x%x%x\n", instr[35], instr[34]); m[33].lasthit = 0; } else m[33].lasthit = (schar)instr[34] + 64; /* I stash some EXTENSIONS to the spec in the second byte of instrument flags */ /* EXTENSION 1: Panning */ m[0].lasthit = instr[1] & 3; m[32].lasthit = instr[1] >> 2 & 3; /* EXTENSION 2: Deep vibrations */ m[18].lasthit = instr[1] >> 7; m[48].lasthit = instr[1] >> 6 & 1; /* EXTENSION 3: 4-op mode */ m[16].lasthit = /* four op */ instr[1] & 0x10 ? instr[26] << 1 & 2 | instr[10] & 1 : /* two voices */ instr[0] & 0x04 ? 4 | instr[26] << 1 & 2 | instr[10] & 1 : /* one voice */ 8 | instr[10] & 1; m[3].lasthit = m[16].lasthit & 1; m[19].lasthit = m[16].lasthit == 2 || m[16].lasthit > 3; m[35].lasthit = m[16].lasthit >> 1 & 1 ^ m[16].lasthit == 2; if(oplon) sendul(oplchan, INSTR); } void usage(void){ sysfatal("usage: %s [-r file] [bank]", argv0); } void threadmain(int argc, char **argv){ Keyboardctl *kbd; Channel *oplok; Point o; Rune k; char *s, buf[128], path[128] = "/mnt/wad/genmidi", one[] = " "; int n, i, j; enum{RESIZE, MOUSE, KEYBOARD}; Alt a[] = { [RESIZE]{nil, nil, CHANRCV}, [MOUSE]{nil, nil, CHANRCV}, [KEYBOARD]{nil, &k, CHANRCV}, {nil, nil, CHANEND} }; ARGBEGIN{ case 'r': if(tee == nil) tee = malloc(TEE); if(tee == nil) sysfatal("malloc: %r"); break; default: usage(); }ARGEND switch(argc){ case 0: if((i = open(path, OREAD)) >= 0){ n = read(i, bank, sizeof(bank)); close(i); if(n == sizeof(bank) && !memcmp(bank, "#OPL_II#", 8)) break; } memset(bank, 0, sizeof(bank)); for(instr = bank + 10, i = 0; i < 175; i++, instr += 36) *instr = 0x80; /* finetune */ break; case 1: if(strlen(*argv) >= 128) sysfatal("That's a long path!"); strcpy(path, *argv); if((i = open(path, OREAD)) < 0) sysfatal("open: %r"); if(read(i, bank, sizeof(bank)) != sizeof(bank) || memcmp(bank, "#OPL_II#", 8)) sysfatal("not an OP2 bank!"); close(i); break; default: usage(); } if(initdraw(nil, nil, argv0) < 0) sysfatal("initdraw: %r"); bg = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, DDarkgreen); fg = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, DPaleyellow); grey = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, DPalegreen); if(bg == nil || fg == nil || grey == nil) sysfatal("allocimage: %r"); i = 2 * font->height; j = 4 * font->height / 3; mask[0] = allocimage(display, Rect(1, 1, 12, i), GREY1, 0, DWhite); mask[1] = allocimage(display, Rect(9, 1, 16, j), GREY1, 0, DWhite); mask[2] = allocimage(display, Rect(13, 1, 24, i), GREY1, 0, DWhite); mask[3] = allocimage(display, Rect(21, 1, 28, j), GREY1, 0, DWhite); mask[4] = allocimage(display, Rect(25, 1, 36, i), GREY1, 0, DWhite); mask[5] = allocimage(display, Rect(37, 1, 48, i), GREY1, 0, DWhite); mask[6] = allocimage(display, Rect(45, 1, 52, j), GREY1, 0, DWhite); mask[7] = allocimage(display, Rect(49, 1, 60, i), GREY1, 0, DWhite); mask[8] = allocimage(display, Rect(57, 1, 64, j), GREY1, 0, DWhite); mask[9] = allocimage(display, Rect(61, 1, 72, i), GREY1, 0, DWhite); mask[10] = allocimage(display, Rect(69, 1, 77, j), GREY1, 0, DWhite); mask[11] = allocimage(display, Rect(73, 1, 84, i), GREY1, 0, DWhite); mask[12] = allocimage(display, Rect(85, 1, 96, i), GREY1, 0, DWhite); mask[13] = allocimage(display, Rect(93, 1, 100, j), GREY1, 0, DWhite); mask[14] = allocimage(display, Rect(97, 1, 108, i), GREY1, 0, DWhite); mask[15] = allocimage(display, Rect(105, 1, 112, j), GREY1, 0, DWhite); mask[16] = allocimage(display, Rect(109, 1, 120, i), GREY1, 0, DWhite); mask[17] = allocimage(display, Rect(121, 1, 132, i), GREY1, 0, DWhite); mask[18] = allocimage(display, Rect(129, 1, 136, j), GREY1, 0, DWhite); mask[19] = allocimage(display, Rect(133, 1, 144, i), GREY1, 0, DWhite); mask[20] = allocimage(display, Rect(141, 1, 148, j), GREY1, 0, DWhite); mask[21] = allocimage(display, Rect(145, 1, 156, i), GREY1, 0, DWhite); mask[22] = allocimage(display, Rect(153, 1, 160, j), GREY1, 0, DWhite); mask[23] = allocimage(display, Rect(157, 1, 168, i), GREY1, 0, DWhite); mask[24] = allocimage(display, Rect(169, 1, 180, i), GREY1, 0, DWhite); for(i = 0; i < 25; i++) if(mask[i] == nil) sysfatal("allocimage: %r"); draw(mask[0], insetrect(mask[1]->r, -1), display->black, nil, ZP); draw(mask[2], insetrect(mask[1]->r, -1), display->black, nil, ZP); draw(mask[2], insetrect(mask[3]->r, -1), display->black, nil, ZP); draw(mask[4], insetrect(mask[3]->r, -1), display->black, nil, ZP); draw(mask[5], insetrect(mask[6]->r, -1), display->black, nil, ZP); draw(mask[7], insetrect(mask[6]->r, -1), display->black, nil, ZP); draw(mask[7], insetrect(mask[8]->r, -1), display->black, nil, ZP); draw(mask[9], insetrect(mask[8]->r, -1), display->black, nil, ZP); draw(mask[9], insetrect(mask[10]->r, -1), display->black, nil, ZP); draw(mask[11], insetrect(mask[10]->r, -1), display->black, nil, ZP); draw(mask[12], insetrect(mask[13]->r, -1), display->black, nil, ZP); draw(mask[14], insetrect(mask[13]->r, -1), display->black, nil, ZP); draw(mask[14], insetrect(mask[15]->r, -1), display->black, nil, ZP); draw(mask[16], insetrect(mask[15]->r, -1), display->black, nil, ZP); draw(mask[17], insetrect(mask[18]->r, -1), display->black, nil, ZP); draw(mask[19], insetrect(mask[18]->r, -1), display->black, nil, ZP); draw(mask[19], insetrect(mask[20]->r, -1), display->black, nil, ZP); draw(mask[21], insetrect(mask[20]->r, -1), display->black, nil, ZP); draw(mask[21], insetrect(mask[22]->r, -1), display->black, nil, ZP); draw(mask[23], insetrect(mask[22]->r, -1), display->black, nil, ZP); if((⌖ = initmouse(nil, screen)) == nil) sysfatal("initmouse: %r"); a[RESIZE].c = ⌖->resizec; a[MOUSE].c = ⌖->c; a[MOUSE].v = &⌖->Mouse; if((kbd = initkeyboard(nil)) == nil) sysfatal("initkeyboard: %r"); a[KEYBOARD].c = kbd->c; oplchan = chancreate(sizeof(ulong), 0); oplok = chancreate(sizeof(char*), 0); if(oplchan == nil || oplok == nil) sysfatal("chancreate: %r"); initinstr(); sendul(⌖->resizec, 1); for(;;) switch(alt(a)){ case MOUSE: if(⌖->buttons == 4){ s = (char*)bank + 8 + 175 * 36 + m[17].lasthit * 32; switch(menuhit(3, ⌖, &b3, nil)){ case 0: if(oplon){ sendul(oplchan, STOP); recvp(oplok); oplon = 0; } break; case 1: strcpy(buf, path); enter(mb3[1], buf, sizeof(buf), ⌖, kbd, nil); if((i = create(buf, OWRITE, 0666)) < 0){ err: errstr(buf, sizeof(buf)); enter(buf, nil, 0, ⌖, kbd, nil); break; } strcpy(path, buf); j = write(i, bank, sizeof(bank)); close(i); if(j != sizeof(bank)) goto err; break; case 2: strcpy(buf, s); enter(mb3[2], buf, sizeof(buf), ⌖, kbd, nil); if((i = open(buf, OREAD)) < 0) goto err; strncpy(s, buf, 31); j = read(i, instr, 36); close(i); initinstr(); redraw(); if(j != 36) goto err; break; case 3: strcpy(buf, s); enter(mb3[3], buf, sizeof(buf), ⌖, kbd, nil); if(create(buf, OWRITE, 0666) < 0) goto err; j = write(i, instr, 36); close(i); if(j != 36) goto err; break; case 4: snarf = m[17].lasthit; break; case 5: if(snarf == m[17].lasthit) break; memcpy(instr, bank + 8 + 36 * snarf, 36); memcpy(s, bank + 8 + 175 * 36 + 32 * snarf, 32); initinstr(); redraw(); break; case 6: memset(instr, 0, 36); memset(s, 0, 32); instr[2] = 0x80; /* finetune */ initinstr(); redraw(); break; case 7: threadexitsall(nil); } break; } if(⌖->buttons != 1) break; if(ptinrect(⌖->xy, keyr)){ menuhit(1, ⌖, &oct, nil); break; } i = (⌖->xy.x - screen->r.min.x) / box.max.x; j = (⌖->xy.y - screen->r.min.y) / font->height; if(j == 0){ o = screen->r.min; switch(i){ case 0: j = m[16].lasthit; i = menuhit(1, ⌖, m + 16, nil); if(i < 0 || i == j) break; instr[0] = instr[0] & 0xFB | i & 4; instr[1] = instr[1] & 0xEF | (i < 4) << 4; instr[10] = instr[10] & 0xFE | i & 1; instr[26] = instr[26] & 0xFE | i >> 1 & 1; m[3].lasthit = i & 1; m[19].lasthit = i == 2 || i > 3; m[35].lasthit = i >> 1 & 1 ^ i == 2; redraw(); if(oplon) sendul(oplchan, VOICE); break; case 4: j = m[18].lasthit; i = menuhit(1, ⌖, m + 18, nil); if(i < 0 || i == j) break; instr[1] = instr[1] & 0x7F | i << 7; o.x += 4 * box.max.x; draw(screen, rectaddpt(box, o), bg, nil, ZP); string(screen, o, fg, ZP, font, tremdepth[i]); if(oplon) sendul(oplchan, VOICE); break; default: j = m[17].lasthit; i = menuhit(1, ⌖, m + 17, nil); if(i == j){ s = (char*)bank + 8 + 175 * 36 + i * 32; enter("rename instrument", s, 32, ⌖, kbd, nil); sprint(instrstr, "[%d] %.31s", i, s); o.x += box.max.x; draw(screen, Rpt(o, addpt(o, Pt(3 * box.max.x, font->height))), bg, nil, ZP); o.x = screen->r.min.x + screen->r.max.x - stringwidth(font, instrstr) >> 1; string(screen, o, fg, ZP, font, instrstr); } else if(i >= 0){ initinstr(); redraw(); } break; } flushimage(display, 1); break; } o = Pt(screen->r.min.x + i * box.max.x, screen->r.min.y + j * font->height); if(i < 1 || i > 4 || j < 1 || j > 16 || j == 4 || i == 2 && j < 4) break; n = (i - 1 << 4) + j - 1; j = m[n].lasthit; i = menuhit(1, ⌖, m + n, nil); if(i < 0 || i == j) break; if(n < 3) draw(screen, Rect(o.x, o.y, keyr.min.x, o.y + font->height), bg, nil, ZP); else draw(screen, rectaddpt(box, o), bg, nil, ZP); if(n == 49) string(screen, string(screen, o, fg, ZP, font, "Fixed Pitch: "), fg, ZP, font, pitch[m[49].lasthit]); else if(n == 50) string(screen, string(screen, o, fg, ZP, font, "Fine Tune: "), fg, ZP, font, tuning[m[50].lasthit]); else string(screen, o, fg, ZP, font, m[n].item[m[n].lasthit]); j = 0; switch(n){ case 0: instr[1] = instr[1] & 0xFC | i; j = VOICE; break; case 1: instr[18] = i - 64; instr[19] = i - 64 >> 8; break; case 2: instr[10] = instr[10] & 0x01 | i << 1; j = VOICE; break; case 4: case 5: case 6: case 7: case 8: instr[4] = m[4].lasthit << 7 | m[5].lasthit << 6 | m[6].lasthit << 5 | m[7].lasthit << 4 | m[8].lasthit; j = instr[4] << 8 | 0x20; break; case 9: case 10: instr[5] = m[9].lasthit << 4 | m[10].lasthit; j = instr[5] << 8 | 0x60; break; case 11: case 12: instr[6] = m[11].lasthit << 4 | m[12].lasthit; j = instr[6] << 8 | 0x80; break; case 13: instr[7] = i; j = i << 8 | 0xE0; break; case 14: instr[8] = i << 6; j = (instr[8] | instr[9]) << 8 | 0x40; break; case 15: instr[9] = i; j = (instr[8] | i) << 8 | 0x40; break; case 20: case 21: case 22: case 23: case 24: instr[11] = m[20].lasthit << 7 | m[21].lasthit << 6 | m[22].lasthit << 5 | m[23].lasthit << 4 | m[24].lasthit; j = instr[11] << 8 | 0x23; break; case 25: case 26: instr[12] = m[25].lasthit << 4 | m[26].lasthit; j = instr[12] << 8 | 0x63; break; case 27: case 28: instr[13] = m[27].lasthit << 4 | m[28].lasthit; j = instr[13] << 8 | 0x83; break; case 29: instr[14] = i; j = i << 8 | 0xE3; break; case 30: instr[15] = i << 6; j = (instr[15] | instr[16]) << 8 | 0x43; break; case 31: instr[16] = i; j = (instr[15] | i) << 8 | 0x43; break; case 32: instr[1] = instr[1] & 0xF3 | i << 2; j = VOICE; break; case 33: instr[34] = i - 64; instr[35] = i - 64 >> 8; break; case 34: instr[26] = instr[26] & 0x01 | i << 1; j = VOICE; break; case 36: case 37: case 38: case 39: case 40: instr[20] = m[36].lasthit << 7 | m[37].lasthit << 6 | m[38].lasthit << 5 | m[39].lasthit << 4 | m[40].lasthit; j = instr[20] << 8 | 0x28; break; case 41: case 42: instr[21] = m[41].lasthit << 4 | m[42].lasthit; j = instr[21] << 8 | 0x68; break; case 43: case 44: instr[22] = m[43].lasthit << 4 | m[44].lasthit; j = instr[22] << 8 | 0x88; break; case 45: instr[23] = i; j = i << 8 | 0xE8; break; case 46: instr[24] = i << 6; j = (instr[24] | instr[25]) << 8 | 0x48; break; case 47: instr[25] = i; j = (instr[24] | i) << 8 | 0x48; break; case 48: instr[1] = instr[1] & 0xBF | i << 6; j = VOICE; break; case 49: instr[0] = instr[0] & 0xFE | !!i; instr[3] = i + 11; break; case 50: instr[2] = i + 64; break; case 52: case 53: case 54: case 55: case 56: instr[27] = m[52].lasthit << 7 | m[53].lasthit << 6 | m[54].lasthit << 5 | m[55].lasthit << 4 | m[56].lasthit; j = instr[27] << 8 | 0x2B; break; case 57: case 58: instr[28] = m[57].lasthit << 4 | m[58].lasthit; j = instr[28] << 8 | 0x6B; break; case 59: case 60: instr[29] = m[59].lasthit << 4 | m[60].lasthit; j = instr[29] << 8 | 0x8B; break; case 61: instr[30] = i; j = i << 8 | 0xEB; break; case 62: instr[31] = i << 6; j = (instr[31] | instr[32]) << 8 | 0x4B; break; case 63: instr[32] = i; j = (instr[31] | i) << 8 | 0x4B; break; } if(j && oplon) sendul(oplchan, j); flushimage(display, 1); break; case RESIZE: if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); box = Rect(0, 0, Dx(screen->r) / 5, font->height); redraw(); break; case KEYBOARD: if(k == Kesc){ if(oplon){ sendul(oplchan, STOP); recvp(oplok); oplon = 0; } break; } if(k != ' ' && !strchr(keys, k)) break; if(!oplon){ if(s = proccreate(oplproc, oplok, mainstacksize) < 0 ? "proccreate failed" : recvp(oplok)){ enter(s, nil, 0, ⌖, kbd, nil); break; } sendul(oplchan, INSTR); oplon = 1; } if(k == ' '){ keyevents(nil); sleep(100); *one = 0; keyevents(one); break; } if((i = open("/dev/kbd", OREAD)) < 0){ fprint(2, "open: %r"); break; } *one = k; keyevents(one); for(n = 0; n >= 0;){ if((n = read(i, buf, sizeof(buf) - 1)) < 0){ fprint(2, "read: %r"); break; } buf[n] = '\0'; for(s = buf; s < buf + n; s = strchr(s, 0) + 1) if(*s != 'c' && !keyevents(s + 1)) n = -1; } close(i); break; default: sysfatal("uae!"); } }