#include #include #include /* * something to sit between orca(1) and opl3(1) for FM mayhem * http://www.fit.vutbr.cz/~arnost/opl/opl3.html * * There are 18 channels available on the opl3. Percussion mode is not supported. * The first six channels may be in either 2-Op or 4-Op mode. * Enabling 4-Op mode disables one of the last six channels, * eg. If Channel 0 is in 4-Op mode, Channel c is disabled. * * Initial channel settings are loaded from the first 18 instruments * in the OP2-format instrument bank specified on the command line. * Double-voiced instruments are not supported, but 4-Op are: see op2ed.c * * Run something like "orca -u /fd/1 | orcopl bank" and send commands with the ; operator. ☞ There is one Global Command: [tv][01] Deep Tremolo/Vibrato off/on ☞ Channel Commands are a channel identifier [0-h], followed by: [CcDdEFfGgAaBx][0-8] Note Trigger: note (x = off), octave b[ud][0-z] Bend Note Frequency: up/down, amount (in internal freq increments) p[0123lrbs] Panning: 0 = Silent, 1 = l = Left, 2 = r = Right, 3 = b = s = Stereo fb[0-7] Feedback: 0, ¹⁄₁₆, ⅛, ¼, ½, 1x, 2x, 4x ☞ Operator Commands are a ch. ident. [0-h], followed by op. ident. [0-3], followed by: [tvhe][01] Operator Toggles: Tremolo, Vibrato, Hold sustain while key on, Envelope scaling [adsr][0-f] Envelope Settings: Attack, Decay, Sustain, Release m[0-f] Multiplier: 0 = ½, b = a, d = c, e = f w[0-7] Wave Form: Sine, ½Sine, Abs. Sine, Pulse Sine, Even Sine, Even Abs. Sine, Sq., Der. Sq. k[0-3] Key Scaling: 0 dB/oct, 1½ dB/oct, 3 dB/oct, 6 dB/oct l[0-v] Level (in 2x internal increments) ☞ Multiple commands may be strung together after the same ; operator. */ int tee, note[18], 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 }; int ch[18] = { 0x00000, 0x00100, 0x00200, 0x10000, 0x10100, 0x10200, 0x00600, 0x00700, 0x00800, 0x10600, 0x10700, 0x10800, 0x00300, 0x00400, 0x00500, 0x10300, 0x10400, 0x10500 }; int op[36] = { 0x00000, 0x00100, 0x00200, 0x10000, 0x10100, 0x10200, 0x01000, 0x01100, 0x01200, 0x11000, 0x11100, 0x11200, 0x00800, 0x00900, 0x00A00, 0x10800, 0x10900, 0x10A00, 0x00300, 0x00400, 0x00500, 0x10300, 0x10400, 0x10500, 0x01300, 0x01400, 0x01500, 0x11300, 0x11400, 0x11500, 0x00B00, 0x00C00, 0x00D00, 0x10B00, 0x10C00, 0x10D00 }; uchar reg104, regBD, regC0[18], reg20[36], reg40[36], reg60[36], reg80[36], regE0[36]; void opl(void *arg){ Channel *c; vlong t, zzz; int fd, p[2], i; uchar *buf, *e, s[36 * 5] = {1, 0, 0x20, 0, 0, 5, 1, 1, 0, 0, 0x04, 1, 0, 0, 0, 0xBD}; ulong u; long n; c = arg; if((fd = open("/dev/audio", OWRITE)) < 0) sysfatal("open: %r"); if(pipe(p) < 0) sysfatal("pipe: %r"); switch(fork()){ case -1: sysfatal("fork: %r"); case 0: close(p[0]); dup(p[1], 0); close(p[1]); if(tee){ if((buf = 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], buf, sizeof(buf))){ write(1, buf, n); write(fd, buf, 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", "100", nil); sysfatal("execl: %r"); } close(fd); close(p[1]); s[12] = reg104; s[17] = regBD; for(i = 0; i < 18; i++){ s[5 * i + 20] = ch[i] >> 8 | 0xC0; s[5 * i + 21] = ch[i] >> 16; s[5 * i + 22] = regC0[i]; } write(p[0], s, 20 + 18 * 5); for(i = 0; i < 36; i++){ s[5 * i] = op[i] >> 8 | 0x20; s[5 * i + 1] = op[i] >> 16; s[5 * i + 2] = reg20[i]; } write(p[0], s, 36 * 5); for(i = 0; i < 36; i++){ s[5 * i] ^= 0x60; s[5 * i + 2] = reg40[i]; } write(p[0], s, 36 * 5); for(i = 0; i < 36; i++){ s[5 * i] |= 0x20; s[5 * i + 2] = reg60[i]; } write(p[0], s, 36 * 5); for(i = 0; i < 36; i++){ s[5 * i] ^= 0xE0; s[5 * i + 2] = reg80[i]; } write(p[0], s, 36 * 5); for(i = 0; i < 36; i++){ s[5 * i] |= 0x60; s[5 * i + 2] = regE0[i]; } write(p[0], s, 36 * 5); t = nsec(); for(;;){ for(e = s; e + 5 < s + sizeof(s) && (u = nbrecvul(c)); e += 5){ if(u == 1) exits(nil); e[0] = u >> 8; e[1] = u >> 16; e[2] = u; } e[0] = e[1] = e[2] = 0; e[3] = 1; write(p[0], s, e - s + 5); e[3] = 0; t += 10000000; /* hundredth of a second */ zzz = (t - nsec()) / 1000000; if(zzz > 1) sleep(zzz - 1); } } void threadmain(int argc, char *argv[]){ char buf[256], *b; Channel *c; int i, n; ARGBEGIN{ case 'r': tee = 1; break; default: sysfatal("usage: %s [-r] [bank]", argv0); }ARGEND if(argc != 1) sysfatal("usage: %s [-r] [bank]", argv0); if((b = malloc(8 + 36 * 18)) == nil) sysfatal("malloc: %r"); if((i = open(*argv, OREAD)) < 0) sysfatal("open: %r"); if(read(i, b, 8 + 36 * 18) != 8 + 36 * 18 || memcmp(b, "#OPL_II#", 8)) sysfatal("not an OP2 bank!"); close(i); regBD = b[9] & 0xC0; /* first instr sets global wiggle depths */ for(i = 0; i < 18; i++){ regC0[i] = ~b[36 * i + 9] << 4 & 0x30 | b[36 * i + 18]; reg20[i] = b[36 * i + 12]; reg40[i] = b[36 * i + 16] | b[36 * i + 17]; reg60[i] = b[36 * i + 13]; reg80[i] = b[36 * i + 14]; regE0[i] = b[36 * i + 15]; reg20[i + 18] = b[36 * i + 19]; reg40[i + 18] = b[36 * i + 23] | b[36 * i + 24]; reg60[i + 18] = b[36 * i + 20]; reg80[i + 18] = b[36 * i + 21]; regE0[i + 18] = b[36 * i + 22]; } for(i = 0; i < 6; i++) if(b[36 * i + 9] & 0x10){ reg104 |= 1 << i; regC0[i + 12] = ~b[36 * i + 9] << 2 & 0x30 | b[36 * i + 34]; reg20[i + 12] = b[36 * i + 28]; reg40[i + 12] = b[36 * i + 32] | b[36 * i + 33]; reg60[i + 12] = b[36 * i + 29]; reg80[i + 12] = b[36 * i + 30]; regE0[i + 12] = b[36 * i + 31]; reg20[i + 30] = b[36 * i + 35]; reg40[i + 30] = b[36 * i + 39] | b[36 * i + 40]; reg60[i + 30] = b[36 * i + 36]; reg80[i + 30] = b[36 * i + 37]; regE0[i + 30] = b[36 * i + 38]; } free(b); if((c = chancreate(sizeof(ulong), 8)) == nil) sysfatal("chancreate: %r"); if(proccreate(opl, c, mainstacksize) < 0) sysfatal("proccreate: %r"); while((n = read(0, buf, sizeof(buf))) > 0) for(buf[n] = 0, b = buf; *b; b += 4){ i = -1; switch(b[0]){ case 'v': i = -2; case 't': if(b[1] != '0' && b[1] != '1'){ fprint(2, "unrecognised wiggle arg %c: %s\n", b[1], b); break; } regBD = regBD & 0x80 | (b[1] == '1') << 8 + i; sendul(c, 0xBD00 | regBD); b -= 2; continue; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': i = b[0] - '0'; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': i = b[0] - 'a' + 10; break; default: fprint(2, "unrecognised cmd: %s\n", b); break; } if(i < 0) break; switch(b[1]){ case 'p': switch(b[2]){ case '0': n = 0x00; break; case '1': case 'l': n = 0x10; break; case '2': case 'r': n = 0x20; break; case '3': case 'b': case 's': n = 0x30; break; default: n = -1; break; } if(n == -1) break; regC0[i] = regC0[i] & 0x0F | n; sendul(c, 0xC000 | ch[i] | regC0[i]); b--; continue; case 'C': n = 0; goto note; case 'c': n = 1; goto note; case 'D': n = 2; goto note; case 'd': n = 3; goto note; case 'E': n = 4; goto note; case 'F': n = 5; goto note; case 'f': if(b[2] == 'b'){ if(b[3] < '0' || b[3] > '7') break; regC0[i] = regC0[i] & 0x31 | b[3] - '0' << 1; sendul(c, 0xC000 | ch[i] | regC0[i]); continue; } n = 6; goto note; case 'G': n = 7; goto note; case 'g': n = 8; goto note; case 'A': n = 9; goto note; case 'a': n = 10; goto note; case 'B': n = 11; note: if(b[2] < '0' || b[2] > '8') break; n += 12 * (b[2] - '0'); if(n >= nelem(freq)) n = nelem(freq) - 1; sendul(c, 0xB000 | ch[i] | note[i] >> 8 & 0x1F); note[i] = freq[n] | 0x2000; sendul(c, 0xA000 | ch[i] | note[i] & 0xFF); sendul(c, 0xB000 | ch[i] | note[i] >> 8); b--; continue; case 'x': note[i] &= 0x1FFF; sendul(c, 0xB000 | ch[i] | note[i] >> 8); b--; continue; case 'b': if(b[3] >= '0' && b[3] <= '9') n = b[3] - '0'; else if(b[3] >= 'a' && b[3] <= 'z') n = b[3] - 'a' + 10; else break; if(b[2] == 'd'){ n = (note[i] & 0x1FFF) - n; if(n < 0) n = 0; } else if(b[2] == 'u'){ n += note[i] & 0x1FFF; if(n > 0x1FFF) n = 0x1FFF; } else break; note[i] = note[i] & 0x2000 | n; sendul(c, 0xA000 | ch[i] | note[i] & 0xFF); sendul(c, 0xB000 | ch[i] | note[i] >> 8); continue; case '2': case '3': if(!(reg104 >> i & 1)) break; i += 12; if(b[1] == '3') case '1': i += 18; case '0': switch(b[2]){ case 't': if(b[3] != '0' && b[3] != '1') break; reg20[i] = reg20[i] & 0x7F | (b[3] == '1') << 7; sendul(c, 0x2000 | op[i] | reg20[i]); continue; case 'v': if(b[3] != '0' && b[3] != '1') break; reg20[i] = reg20[i] & 0xBF | (b[3] == '1') << 6; sendul(c, 0x2000 | op[i] | reg20[i]); continue; case 'h': if(b[3] != '0' && b[3] != '1') break; reg20[i] = reg20[i] & 0xDF | (b[3] == '1') << 5; sendul(c, 0x2000 | op[i] | reg20[i]); continue; case 'e': if(b[3] != '0' && b[3] != '1') break; reg20[i] = reg20[i] & 0xEF | (b[3] == '1') << 4; sendul(c, 0x2000 | op[i] | reg20[i]); continue; case 'm': if(b[3] >= '0' && b[3] <= '9') n = b[3] - '0'; else if(b[3] >= 'a' && b[3] <= 'f') n = b[3] - 'a' + 10; else break; reg20[i] = reg20[i] & 0xF0 | n; sendul(c, 0x2000 | op[i] | reg20[i]); continue; case 'a': if(b[3] >= '0' && b[3] <= '9') n = b[3] - '0'; else if(b[3] >= 'a' && b[3] <= 'f') n = b[3] - 'a' + 10; else break; reg60[i] = reg60[i] & 0x0F | n << 4; sendul(c, 0x6000 | op[i] | reg60[i]); continue; case 'd': if(b[3] >= '0' && b[3] <= '9') n = b[3] - '0'; else if(b[3] >= 'a' && b[3] <= 'f') n = b[3] - 'a' + 10; else break; reg60[i] = reg60[i] & 0xF0 | n; sendul(c, 0x6000 | op[i] | reg60[i]); continue; case 's': if(b[3] >= '0' && b[3] <= '9') n = b[3] - '0'; else if(b[3] >= 'a' && b[3] <= 'f') n = b[3] - 'a' + 10; else break; reg80[i] = reg80[i] & 0x0F | n << 4; sendul(c, 0x8000 | op[i] | reg80[i]); continue; case 'r': if(b[3] >= '0' && b[3] <= '9') n = b[3] - '0'; else if(b[3] >= 'a' && b[3] <= 'f') n = b[3] - 'a' + 10; else break; reg80[i] = reg80[i] & 0xF0 | n; sendul(c, 0x8000 | op[i] | reg80[i]); continue; case 'w': if(b[3] < '0' && b[3] > '7') break; regE0[i] = b[3] - '0'; sendul(c, 0xE000 | op[i] | regE0[i]); continue; case 'k': if(b[3] < '0' && b[3] > '3') break; reg40[i] = reg40[i] & 0x3F | b[3] - '0' << 6; sendul(c, 0x4000 | op[i] | reg40[i]); continue; case 'l': if(b[3] >= '0' && b[3] <= '9') n = b[3] - '0' << 1; else if(b[3] >= 'a' && b[3] <= 'v') n = b[3] - 'a' + 10 << 1; else break; reg40[i] = reg40[i] & 0xC0 | 0x3F - n; sendul(c, 0x4000 | op[i] | reg40[i]); continue; } break; } fprint(2, "bad cmd: %s\n", b); break; } sendul(c, 1); exits(nil); }