πŸ’Ύ Archived View for runjimmyrunrunyoufuckerrun.com β€Ί src β€Ί op2ed.c captured on 2021-12-17 at 13:26:06.

View Raw

More Information

-=-=-=-=-=-=-

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <tos.h>
/* 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 *keys = "\t1q2we4r5t6yu8i9op-[=]\b\\\n";
int oplon, tee, snarf;
Image *bg, *fg, *grey, *mask[25];
Rectangle keyr, box;
Channel *oplchan;
Mousectl *βŒ–;
enum{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;
	vlong t, zzz;
	long n;
	int fd, p[2];
	char *teebuf;
	char nilbuf[] = {0, 0, 0, 10, 0};
	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, 10, 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){
			if((teebuf = malloc(8192)) == nil)
				sysfatal("malloc: %r");
			if(pipe(p) < 0)
				sysfatal("pipe: %r");
			switch(fork()){
			case -1:
				sysfatal("fork: %r");
			case 0:
				close(p[1]);
				while(n = read(p[0], teebuf, sizeof(teebuf))){
					write(1, teebuf, n);
					write(fd, teebuf, 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", "-r", "1000", nil);
		sysfatal("execl: %r");
	}
	close(fd);
	close(p[1]);
	write(p[0], buf + 50, 10);
	sendp(ok, nil);
	t = nsec();
	for(;;){
		reg = nbrecvul(oplchan);
		switch(reg & 0xF0){
		case 0:
			write(p[0], nilbuf, 5);
			break;
		case STOP:
			close(p[0]);
			wait();
			sendp(ok, nil);
			exits(nil);
		case INSTR:
			buf[58] = buf[59] = 0;
			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);
			buf[58] = nilbuf[3];
			buf[59] = nilbuf[4];
		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;
		}
		t += 10000000;
		zzz = (t - nsec()) / 1000000;
		if(zzz > 1)
			sleep(zzz - 1);	/* try to get back in time */
	}
}
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':
		tee = 1;
		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 if(m[16].lasthit & 8 && (n >= 32 && n < 48 || n > 51))
				string(screen, o, grey, ZP, font, m[n].item[m[n].lasthit]);
			else
				string(screen, o, grey, 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!");
		}
}