#include #include #include #include #include #include /* drum machine / instrument editor for opl3(1) in percussion mode * http://www.fit.vutbr.cz/~arnost/opl/opl3.html * http://midibox.org/forums/topic/18625-opl3-percussion-mode-map/ */ uchar reg[] = { 0xBD, 0xA6, 0xA7, 0xA8, 0xB6, 0xB7, 0xB8, 0xC6, 0xC7, 0xC8, 0x30, 0x33, 0x34, 0x32, 0x35, 0x31, 0x70, 0x73, 0x74, 0x72, 0x75, 0x71, 0x90, 0x93, 0x94, 0x92, 0x95, 0x91, 0xF0, 0xF3, 0xF4, 0xF2, 0xF5, 0xF1, 0x50, 0x53, 0x54, 0x52, 0x55, 0x51 }; uchar val[] = { 0x20, /* global tremolo & vibrato depth, percussion mode enable */ /* Channel Settings (BD, SD, TT) */ 0x04, 0x04, 0xb1, /* pitch: lower bits */ 0x06, 0x16, 0x0e, /* pitch: upper bits */ 0x30, 0x30, 0x30, /* feedback, synth type */ /* Operator Settings (BDmod, BDcar, SD, TT, CY, HH) */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* trem, vib, sus, mult */ 0xF0, 0xF0, 0xD0, 0xF0, 0xC0, 0xF0, /* attack, decay */ 0x06, 0x06, 0x06, 0x05, 0x05, 0x07, /* sustain, release */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* wave */ 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00 /* vol */ }; int tee, oplon, seqpos, seqlen = 16, bpm = 80; Keyboardctl *kbd; Mousectl *⌖; Image *bg, *fg; Point seqpt; Rectangle box, seqr, cell; uchar seq[128] = {0}; char err[ERRMAX]; char *mb3[] = {"write pattern", "write drum settings", "quit", nil}; char *toggle[] = {"off", "on", nil}; char *synth[] = {"FM Mode", "AM Mode", nil}; char *tremdepth[] = {"Reg Trem", "Deep Trem", nil}; char *vibdepth[] = {"Reg Vib", "Deep Vib", 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 *wave[] = { /* OPL2 */ "Sine", "Half Sine", "Absolute Sine", "Pulse Sine", /*OPL3 */ "Even Sine", "Even Abs. Sine", "Square", "Derived Square", nil }; char *feedback[] = { "0 Feedback", "¹⁄₁₆ Feedback", "⅛ Feedback", "⅙ Feedback", "½ Feedback", "1x Feedback", "2x Feedback", "4x Feedback", nil }; char *pitch[] = { "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 }; Menu b3 = {mb3, nil, 0}, m[] = { /* Operator Settings */ {toggle}, {toggle}, {toggle}, {toggle}, {toggle}, {toggle}, /* Tremolo */ {toggle}, {toggle}, {toggle}, {toggle}, {toggle}, {toggle}, /* Vibrato */ {toggle}, {toggle}, {toggle}, {toggle}, {toggle}, {toggle}, /* Sustaining */ {mult}, {mult}, {mult}, {mult}, {mult}, {mult}, /* Multiplier */ {nibble}, {nibble}, {nibble}, {nibble}, {nibble}, {nibble}, /* Attack */ {nibble}, {nibble}, {nibble}, {nibble}, {nibble}, {nibble}, /* Decay */ {nibble}, {nibble}, {nibble}, {nibble}, {nibble}, {nibble}, /* Sustain */ {nibble}, {nibble}, {nibble}, {nibble}, {nibble}, {nibble}, /* Release */ {wave}, {wave}, {wave}, {wave}, {wave}, {wave}, /* Wave */ {vol}, {vol}, {vol}, {vol}, {vol}, {vol}, /* Volume */ /* Channel Settings, Global Settings */ {nil}, {pitch}, {pitch}, {pitch}, {nil}, {nil}, {synth}, {feedback}, {tremdepth}, {vibdepth}, {nil}, {nil} }; 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 }; void oplproc(void*){ vlong t, incr, zzz; int fd, p[2], i; long n; uchar *buf, s[nelem(reg) * 5] = {0x01, 0, 0x20}; fd = open("/dev/audio", OWRITE); if(fd < 0){ *err = 0; errstr(err, ERRMAX); enter(err, nil, 0, ⌖, kbd, nil); exits(nil); } if(pipe(p) < 0){ *err = 0; errstr(err, ERRMAX); enter(err, nil, 0, ⌖, kbd, nil); exits(nil); } switch(fork()){ case -1: *err = 0; errstr(err, ERRMAX); enter(err, nil, 0, ⌖, kbd, nil); exits(nil); 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", "1000", nil); sysfatal("execl: %r"); } close(fd); close(p[1]); for(i = 1; i < nelem(reg); i++){ s[5 * i] = reg[i]; s[5 * i + 2] = val[i]; } write(p[0], s, sizeof(s)); s[0] = s[5] = 0xBD; incr = 60000 / bpm / 4; /* milliseconds */ s[7] = val[0] | seq[0]; s[8] = incr; s[9] = incr >> 8; incr *= 1000000; /* nanoseconds */ write(p[0], s + 5, 5); t = nsec(); for(oplon = 1; oplon; flushimage(display, 1)){ t += incr; zzz = (t - nsec()) / 1000000; if(zzz > 1) sleep(zzz - 1); seqpos = (seqpos + 1) % seqlen; s[2] = s[7] ^ seq[seqpos]; s[7] = val[0] | seq[seqpos]; write(p[0], s, 10); draw(screen, rectaddpt(cell, seqpt), bg, nil, ZP); seqpt.x = seqpos ? seqpt.x + cell.max.x : seqr.min.x; draw(screen, rectaddpt(cell, seqpt), fg, nil, ZP); } close(p[0]); draw(screen, rectaddpt(cell, seqpt), bg, nil, ZP); seqpos = 0; seqpt.x = seqr.min.x; draw(screen, rectaddpt(cell, seqpt), fg, nil, ZP); flushimage(display, 1); exits(nil); } void usage(void){ sysfatal("usage: %s [-r file] [-d druminfo] [-p pattern]", argv0); } void threadmain(int argc, char **argv){ Point pt; Rune k; char lens[] = "Len: 16\0", bpms[] = "BPM: 80\0"; int 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; case 'd': if((i = open(EARGF(usage), OREAD)) < 0) sysfatal("open: %r"); j = read(i, val, sizeof(val)); close(i); if(j != sizeof(val)) sysfatal("short read: wanted %d bytes, got %d: %r", sizeof(val), j); break; case 'p': if((i = open(EARGF(usage), OREAD)) < 0) sysfatal("open: %r"); seqlen = read(i, seq, sizeof(seq)); close(i); if(seqlen < 1) sysfatal("short read: %r"); break; default: usage(); }ARGEND if(initdraw(nil, nil, argv0) < 0) sysfatal("initdraw: %r"); box = Rect(0, 0, Dx(screen->r) / 7, font->height); bg = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, DDarkgreen); fg = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, DPaleyellow); if(bg == 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((kbd = initkeyboard(nil)) == nil) sysfatal("initkeyboard: %r"); a[KEYBOARD].c = kbd->c; for(i = 0; i < 6; i++){ m[i].lasthit = val[i + 10] >> 7; /* tremolo */ m[6 + i].lasthit = val[i + 10] >> 6 & 1; /* vibrato */ m[12 + i].lasthit = val[i + 10] >> 5 & 1; /* sustaining */ m[18 + i].lasthit = val[i + 10] & 0xF; /* multiplier */ m[24 + i].lasthit = val[i + 16] >> 4 & 0xF; /* attack */ m[30 + i].lasthit = val[i + 16] & 0xF; /* decay */ m[36 + i].lasthit = val[i + 22] >> 4 & 0xF; /* sustain */ m[42 + i].lasthit = val[i + 22] & 0xF; /* release */ m[48 + i].lasthit = val[i + 28]; /* wave */ m[54 + i].lasthit = val[i + 34]; /* volume */ } /* pitches */ j = val[4] << 8 | val[1]; for(i = nelem(freq) - 1; i && j != freq[i]; i--) ; m[61].lasthit = i; j = val[5] << 8 | val[2]; for(i = nelem(freq) - 1; i && j != freq[i]; i--) ; m[62].lasthit = i; j = val[6] << 8 | val[3]; for(i = nelem(freq) - 1; i && j != freq[i]; i--) ; m[63].lasthit = i; m[66].lasthit = val[7] & 1; /* synth type */ m[67].lasthit = val[7] >> 1 & 7; /* multiplier */ m[68].lasthit = val[0] >> 7; /* tremolo depth */ m[69].lasthit = val[0] >> 6 & 1; /* vibrato depth */ sendul(⌖->resizec, 1); for(;;) switch(alt(a)){ case MOUSE: if(!⌖->buttons) break; if(ptinrect(⌖->xy, seqr)){ i = (⌖->xy.x - seqr.min.x) / cell.max.x; j = (⌖->xy.y - seqr.min.y) / font->height; if(i < 0 || i >= seqlen || j < 0 || j >= 5) break; do readmouse(⌖); while(⌖->buttons); if((⌖->xy.x - seqr.min.x) / cell.max.x == i && (⌖->xy.y - seqr.min.y) / font->height == j) seq[i] ^= 1 << j; draw(screen, rectaddpt(cell, addpt(seqr.min, Pt(i * cell.max.x, j * font->height))), seq[i] >> j & 1 ? fg : bg, nil, ZP); flushimage(display, 1); break; } if(oplon){ oplon = 0; break; } if(⌖->buttons == 4){ j = menuhit(3, ⌖, &b3, nil); if(j < 0) break; if(j == 2) threadexitsall(nil); *err = 0; enter(mb3[0], err, ERRMAX, ⌖, kbd, nil); if((i = create(err, OWRITE, 0666)) < 0){ *err = 0; errstr(err, ERRMAX); enter(err, nil, 0, ⌖, kbd, nil); break; } if(j) write(i, val, sizeof(val)); else write(i, seq, seqlen); close(i); break; } if(⌖->buttons != 1) break; i = (⌖->xy.x - screen->r.min.x) / box.max.x - 1; j = (⌖->xy.y - screen->r.min.y) / font->height - 1; if(i < 0 || j < 0 || i >= 6 || j >= nelem(m) / 6 || j == 10 && (i == 0 || i > 3)) break; pt.x = screen->r.min.x + (i + 1) * box.max.x; pt.y = screen->r.min.y + (j + 1) * font->height; if(j == 11 && i == 4){ strcpy(err, bpms + 5); enter("BPM", err, 4, ⌖, kbd, nil); i = atoi(err); if(i < 1 || i > 999) break; bpm = i; strcpy(bpms + 5, err); draw(screen, rectaddpt(box, pt), bg, nil, ZP); string(screen, pt, fg, ZP, font, bpms); flushimage(display, 1); break; } else if(j == 11 && i == 5){ strcpy(err, lens + 5); enter("Len", err, 4, ⌖, kbd, nil); i = atoi(err); if(i < 1 || i >= sizeof(seq)) break; seqlen = i; strcpy(lens + 5, err); sendul(⌖->resizec, 1); break; } if(menuhit(1, ⌖, m + j * 6 + i, nil) < 0) break; draw(screen, rectaddpt(box, pt), bg, nil, ZP); string(screen, pt, fg, ZP, font, m[j * 6 + i].item[m[j * 6 + i].lasthit]); flushimage(display, 1); switch(j){ case 0: case 1: case 2: case 3: /* trem, vib, sus, mult */ val[i + 10] = m[i].lasthit << 7 | m[i + 6].lasthit << 6 | m[i + 12].lasthit << 5 | m[i + 18].lasthit; break; case 4: case 5: /* attack, decay */ val[i + 16] = m[i + 24].lasthit << 4 | m[i + 30].lasthit; break; case 6: case 7: /* sustain, release */ val[i + 22] = m[i + 36].lasthit << 4 | m[i + 42].lasthit; break; case 8: /* wave */ val[i + 28] = m[i + 48].lasthit; break; case 9: /* volume */ val[i + 34] = m[i + 54].lasthit; break; default: switch(j * 6 + i){ case 61: /* BD pitch */ val[1] = freq[m[61].lasthit]; val[4] = freq[m[61].lasthit] >> 8; break; case 62: /* SD pitch */ val[2] = freq[m[62].lasthit]; val[5] = freq[m[62].lasthit] >> 8; break; case 63: /* TT pitch */ val[3] = freq[m[63].lasthit]; val[6] = freq[m[63].lasthit] >> 8; break; case 66: case 67: /* BD synth type & mult */ val[7] = 0x30 | m[67].lasthit << 1 | m[66].lasthit; break; case 68: case 69: /* tremolo & vibrato depth */ val[0] = 0x20 | m[68].lasthit << 7 | m[69].lasthit << 6; break; } break; } break; case RESIZE: oplon = 0; if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); draw(screen, screen->r, bg, nil, ZP); box = Rect(0, 0, Dx(screen->r) / 7, font->height); pt = Pt(screen->r.min.x + box.max.x, screen->r.min.y); string(screen, pt, fg, ZP, font, "KICK Mod"); pt.x += box.max.x; string(screen, pt, fg, ZP, font, "KICK Car"); pt.x += box.max.x; string(screen, pt, fg, ZP, font, "SNARE"); pt.x += box.max.x; string(screen, pt, fg, ZP, font, "TOM TOM"); pt.x += box.max.x; string(screen, pt, fg, ZP, font, "CYMBAL"); pt.x += box.max.x; string(screen, pt, fg, ZP, font, "HI HAT"); pt.x = screen->r.min.x; pt.y += font->height; string(screen, pt, fg, ZP, font, "Tremolo:"); pt.y += font->height; string(screen, pt, fg, ZP, font, "Vibrato:"); pt.y += font->height; string(screen, pt, fg, ZP, font, "Sustaining:"); pt.y += font->height; string(screen, pt, fg, ZP, font, "Multiplier:"); pt.y += font->height; string(screen, pt, fg, ZP, font, "Attack:"); pt.y += font->height; string(screen, pt, fg, ZP, font, "Decay:"); pt.y += font->height; string(screen, pt, fg, ZP, font, "Sustain:"); pt.y += font->height; string(screen, pt, fg, ZP, font, "Release:"); pt.y += font->height; string(screen, pt, fg, ZP, font, "Wave:"); pt.y += font->height; string(screen, pt, fg, ZP, font, "Volume:"); pt.y += font->height; string(screen, pt, fg, ZP, font, "Pitch:"); pt.y += font->height; string(screen, pt, fg, ZP, font, "Misc:"); seqr.min.y = pt.y += 2 * font->height; string(screen, pt, fg, ZP, font, "HI HAT"); pt.y += font->height; string(screen, pt, fg, ZP, font, "CYMBAL"); pt.y += font->height; seqpt = string(screen, pt, fg, ZP, font, "TOM TOM "); pt.y += font->height; string(screen, pt, fg, ZP, font, "SNARE"); pt.y += font->height; string(screen, pt, fg, ZP, font, "KICK"); seqr.min.x = seqpt.x; seqr.max.y = pt.y + font->height + 1; cell = Rect(1, 1, (screen->r.max.x - seqr.min.x) / seqlen, font->height); seqr.max.x = seqr.min.x + seqlen * cell.max.x + 1; draw(screen, seqr, display->black, nil, ZP); for(pt.y = seqr.min.y, j = 0; j < 5; j++, pt.y += font->height) for(pt.x = seqr.min.x, i = 0; i < seqlen; i++, pt.x += cell.max.x) draw(screen, rectaddpt(cell, pt), seq[i] >> j & 1 ? fg : bg, nil, ZP); seqpt = Pt(seqr.min.x + seqpos * cell.max.x, seqr.max.y); draw(screen, rectaddpt(cell, seqpt), fg, nil, ZP); pt = Pt(screen->r.min.x, screen->r.min.y + font->height); for(i = 0; i < nelem(m) - 2; i++){ pt.x += box.max.x; if(m[i].item) string(screen, pt, fg, ZP, font, m[i].item[m[i].lasthit]); if(i % 6 == 5){ pt.x = screen->r.min.x; pt.y += font->height; } } pt.x += box.max.x; string(screen, pt, fg, ZP, font, bpms); pt.x += box.max.x; string(screen, pt, fg, ZP, font, lens); flushimage(display, 1); break; case KEYBOARD: if(k == Kdel) threadexitsall(nil); if(oplon) oplon = 0; else if(proccreate(oplproc, nil, mainstacksize) < 0){ *err = 0; errstr(err, ERRMAX); enter(err, nil, 0, ⌖, kbd, nil); } break; default: sysfatal("Floronius binet ac miles leg VII hic fuit\n" "neque mulieres scierunt nisi paucae et ses erunt"); } }