#include #include #include #include #include #include enum{ TremVibSusEnvMult0, TremVibSusEnvMult1, AttenuationLevel0, AttenuationLevel1, AttackDecay0, AttackDecay1, SustainRelease0, SustainRelease1, GlideWaves, TrigPanFbSynth, Note, INSTRLEN }; char *pitch[] = {" ", "C₀ ", "C♯₀", "D₀ ", "D♯₀", "E₀ ", "F₀ ", "F♯₀", "G₀ ", "G♯₀", "A₀ ", "A♯₀", "B₀ ", "C₁ ", "C♯₁", "D₁ ", "D♯₁", "E₁ ", "F₁ ", "F♯₁", "G₁ ", "G♯₁", "A₁ ", "A♯₁", "B₁ ", "C₂ ", "C♯₂", "D₂ ", "D♯₂", "E₂ ", "F₂ ", "F♯₂", "G₂ ", "G♯₂", "A₂ ", "A♯₂", "B₂ ", "C₃ ", "C♯₃", "D₃ ", "D♯₃", "E₃ ", "F₃ ", "F♯₃", "G₃ ", "G♯₃", "A₃ ", "A♯₃", "B₃ ", "C₄ ", "C♯₄", "D₄ ", "D♯₄", "E₄ ", "F₄ ", "F♯₄", "G₄ ", "G♯₄", "A₄ ", "A♯₄", "B₄ ", "C₅ ", "C♯₅", "D₅ ", "D♯₅", "E₅ ", "F₅ ", "F♯₅", "G₅ ", "G♯₅", "A₅ ", "A♯₅", "B₅ ", "C₆ ", "C♯₆", "D₆ ", "D♯₆", "E₆ ", "F₆ ", "F♯₆", "G₆ ", "G♯₆", "A₆ ", "A♯₆", "B₆ ", "C₇ ", "C♯₇", "D₇ ", "D♯₇", "E₇ ", "F₇ ", "F♯₇", "G₇ ", "G♯₇", "A₇ ", "A♯₇", "B₇ ", "C₈ ", "C♯₈", "D₈ ", "D♯₈", "E₈ ", "F₈ ", "F♯₈", "G₈ ", nil }; uchar instr[256 - nelem(pitch) + 1 - 3][INSTRLEN], reg[18][INSTRLEN], wiggles, four; char instrname[nelem(instr)][32]; int op[] = {0, 8, 1, 9, 2, 10, 0, 8, 1, 9, 2, 10, 16, 17, 18, 16, 17, 18}; int ch[] = {0, 3, 1, 4, 2, 5, 0, 3, 1, 4, 2, 5, 6, 7, 8, 6, 7, 8}; int ext[] = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1}; Rune rowtxt[16][5 + 3 * 18 + 8]; char hex[] = "0123456789ABCDEF"; char header[] = " A B C D E F G H I J K L M N O P Q R "; char *toggle[] = {"off", "on", nil}; char *fmam[] = {"FM", "AM", nil}; char *pan[] = {"Silent", "Left", "Right", "Stereo", nil}; char *level[] = { "3F", "3E", "3D", "3C", "3B", "3A", "39", "38", "37", "36", "35", "34", "33", "32", "31", "30", "2F", "2E", "2D", "2C", "2B", "2A", "29", "28", "27", "26", "25", "24", "23", "22", "21", "20", "1F", "1E", "1D", "1C", "1B", "1A", "19", "18", "17", "16", "15", "14", "13", "12", "11", "10", "0F", "0E", "0D", "0C", "0B", "0A", "09", "08", "07", "06", "05", "04", "03", "02", "01", "00", nil }; char *nibble[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", nil}; char *mult[] = {"½", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "A", "C", "C", "F", "F", nil}; char *attenuate[] = {"no", "1.5 dB/oct", "3.0 dB/oct", "6.0 dB/oct", nil}; char *wave[] = { /* OPL2 */ "Sine", "Half Sine", "Absolute Sin", "Pulse Sine", /*OPL3 */ "Even Sine", "Even Abs Sin", "Square", "Derived Sq", nil }; char *feedback[] = {"0", "¹⁄₁₆", "⅛", "¼", "½", "1", "2", "4", nil}; char *trig[] = {"inherit", "key down", "key up", "retrigger", nil}; /*TODO "portamento", "⤵₁₂", "⤵₁₁", "⤵₁₀", "⤵₉", "⤵₈", "⤵₇", "⤵₆", "⤵₅", "⤵₄", "⤵₃", "⤵₂", "⤵₁", "", "⤴¹", "⤴²", "⤴³", "⤴⁴", "⤴⁵", "⤴⁶", "⤴⁷", "⤴⁸", "⤴⁹", "⤴¹⁰", "⤴¹¹", "⤴¹²", nil };*/ char *globals[] = { "", "§ 0", "§ 1", "§ 2", "§ 3", "§ 4", "§ 5", "§ 6", "§ 7", "§ 8", "§ 9", "§ A", "§ B", "§ C", "§ D", "§ E", "§ F", "§ G", "§ H", "§ I", "§ J", "§ K", "§ L", "§ M", "§ N", "§ O", "§ P", "§ Q", "§ R", "§ S", "§ T", "§ U", "§ V", "§ W", "§ X", "§ Y", "§ Z", [37] "", "|:", ":|", ":|³", ":|⁴", ":|⁵", ":|⁶", ":|⁷", ":|⁸", "[", [47] "", "dt off", "dt on", "dv off", "dv on", [52] "", "AB 2-op", "CD 2-op", "EF 2-op", "GH 2-op", "IJ 2-op", "KL 2-op", [59] "", "AB 4-op", "CD 4-op", "EF 4-op", "GH 4-op", "IJ 4-op", "KL 4-op", [66] "", "1 rpb", "2 rpb", "3 rpb", "4 rpb", "5 rpb", "6 rpb", "7 rpb", "8 rpb", "9 rpb", "10 rpb", "11 rpb", "12 rpb", "13 rpb", "14 rpb", "15 rpb", "16 rpb", "17 rpb", "18 rpb", "19 rpb", "20 rpb", "21 rpb", "22 rpb", "23 rpb", "24 rpb", [91] "", "fadeout", [93] "", "20 bpm", "22 bpm", "24 bpm", "26 bpm", "28 bpm", "30 bpm", "32 bpm", "34 bpm", "36 bpm", "38 bpm", "40 bpm", "42 bpm", "44 bpm", "46 bpm", "48 bpm", "50 bpm", "52 bpm", "54 bpm", "56 bpm", "58 bpm", "60 bpm", "62 bpm", "64 bpm", "66 bpm", "68 bpm", "70 bpm", "72 bpm", "74 bpm", "76 bpm", "78 bpm", "80 bpm", "82 bpm", "84 bpm", "86 bpm", "88 bpm", "90 bpm", "92 bpm", "94 bpm", "96 bpm", "98 bpm", "100 bpm", "102 bpm", "104 bpm", "106 bpm", "108 bpm", "110 bpm", "112 bpm", "114 bpm", "116 bpm", "118 bpm", "120 bpm", "122 bpm", "124 bpm", "126 bpm", "128 bpm", "130 bpm", "132 bpm", "134 bpm", "136 bpm", "138 bpm", "140 bpm", "142 bpm", "144 bpm", "146 bpm", "148 bpm", "150 bpm", "152 bpm", "154 bpm", "156 bpm", "158 bpm", "160 bpm", "162 bpm", "164 bpm", "166 bpm", "168 bpm", "170 bpm", "172 bpm", "174 bpm", "176 bpm", "178 bpm", "180 bpm", "182 bpm", "184 bpm", "186 bpm", "188 bpm", "190 bpm", "192 bpm", "194 bpm", "196 bpm", "198 bpm", "200 bpm", "202 bpm", "204 bpm", "206 bpm", "208 bpm", "210 bpm", "212 bpm", "214 bpm", "216 bpm", "218 bpm", "220 bpm", "222 bpm", "224 bpm", "226 bpm", "228 bpm", "230 bpm", "232 bpm", "234 bpm", "236 bpm", "238 bpm", "240 bpm", "242 bpm", "244 bpm", "246 bpm", "248 bpm", "250 bpm", "252 bpm", "254 bpm", "256 bpm", "258 bpm", "260 bpm", "262 bpm", "264 bpm", "266 bpm", "268 bpm", "270 bpm", "272 bpm", "274 bpm", "276 bpm", "278 bpm", "280 bpm", "282 bpm", "284 bpm", "286 bpm", "288 bpm", "290 bpm", "292 bpm", "294 bpm", "296 bpm", "298 bpm", "300 bpm", "302 bpm", "304 bpm", "306 bpm", "308 bpm", "310 bpm", "312 bpm", "314 bpm", "316 bpm", "318 bpm", "320 bpm", "322 bpm", "324 bpm", "326 bpm", "328 bpm", "330 bpm", "332 bpm", "334 bpm", "336 bpm", "338 bpm", "340 bpm", "342 bpm", nil }; short freq[] = {0, 0x159, 0x16d, 0x183, 0x19a, 0x1b2, 0x1cc, 0x1e8, 0x205, 0x224, 0x244, 0x267, 0x28b, 0x2b2, 0x2db, 0x306, 0x334, 0x365, 0x399, 0x3cf, 0x1810, 0x1811, 0x1812, 0x1813, 0x1814, 0x1816, 0x1817, 0x1818, 0x181a, 0x181b, 0x181d, 0x181e, 0x1820, 0x1822, 0x1824, 0x1826, 0x1829, 0x182b, 0x182e, 0x1830, 0x1833, 0x1836, 0x183a, 0x183d, 0x1841, 0x1844, 0x1849, 0x184d, 0x1851, 0x1856, 0x185b, 0x1861, 0x1867, 0x186d, 0x1873, 0x187a, 0x1881, 0x1889, 0x1891, 0x189a, 0x18a3, 0x18ac, 0x18b7, 0x18c2, 0x18cd, 0x18d9, 0x18e6, 0x18f4, 0x1902, 0x1912, 0x1922, 0x1933, 0x1946, 0x1959, 0x196d, 0x1983, 0x199a, 0x19b3, 0x19cc, 0x19e8, 0x1a05, 0x1a23, 0x1a44, 0x1a67, 0x1a8b, 0x1ab2, 0x1adb, 0x1b06, 0x1b34, 0x1b65, 0x1b99, 0x1bcf, 0x1e05, 0x1e23, 0x1e44, 0x1e67, 0x1e8b, 0x1eb2, 0x1edb, 0x1f06, 0x1f34, 0x1f65, 0x1f99, 0x1fcf, 0x1fff}; uchar ev[0x10000][18], global[0x1000]; /* lazy… */ Image *bg, *cursor, *fg, *grey; Rectangle horiz, vert; Point orig; int playing, bpm, rpb, instrmode, em, row, col, rows = 0x40, skip = 8, oct = 5; vlong incr; void drawrows(void){ int i; Point o; draw(screen, screen->r, bg, nil, ZP); draw(screen, horiz, cursor, nil, ZP); draw(screen, vert, cursor, nil, ZP); o = orig; string(screen, o, fg, ZP, font, header); for(i = row - 7; o.y += font->height, i < rows && i <= row + 8; i++) runestring(screen, o, fg, ZP, font, rowtxt[i & 15]); flushimage(display, 1); } void resize(void){ orig.x = screen->r.min.x + screen->r.max.x - nelem(rowtxt[0]) * em >> 1; orig.y = screen->r.min.y + screen->r.max.y - 17 * font->height >> 1; horiz.min = Pt(screen->r.min.x, orig.y + 8 * font->height); horiz.max = Pt(screen->r.max.x, horiz.min.y + font->height); vert.min = Pt(orig.x + (5 + 3 * col) * em, screen->r.min.y); vert.max = Pt(vert.min.x + 2 * em, screen->r.max.y); drawrows(); } void fmtrow(int n){ Rune *r; int i; r = rowtxt[n & 15]; if(n < 0 || n >= rows){ *r = 0; return; } r[0] = hex[n >> 12]; r[1] = hex[n >> 8 & 15]; r[2] = hex[n >> 4 & 15]; r[3] = hex[n & 15]; r[4] = ' '; for(r += 5, i = 0; i < 18; i++, r+= 3) if(ev[n][i] < nelem(pitch) - 1){ r[0] = pitch[ev[n][i]][0]; chartorune(r + 2, pitch[ev[n][i]] + 1 + chartorune(r + 1, pitch[ev[n][i]] + 1)); }else if(ev[n][i] == nelem(pitch) - 1){ r[0] = r[1] = '^'; r[2] = ' '; }else if(ev[n][i] == nelem(pitch)){ r[0] = r[1] = '-'; r[2] = ' '; }else if(ev[n][i] == nelem(pitch) + 1){ r[0] = r[1] = '+'; r[2] = ' '; }else{ r[0] = hex[ev[n][i] - nelem(pitch) - 2 >> 4]; r[1] = hex[ev[n][i] - nelem(pitch) - 2 & 15]; r[2] = ' '; } for(i = 0; globals[global[n]][i];) i += chartorune(r++, globals[global[n]] + i); *r = 0; } void fmtrows(void){ int i; for(i = row - 7; i <= row + 8; i++) fmtrow(i); } void drawinstr(int n){ char s[42]; draw(screen, screen->r, bg, nil, ZP); sprint(s, " instr %.2X %s", n, instrname[n]); string(screen, orig, fg, ZP, font, s); sprint(s, " tremolo %13-s%s", toggle[instr[n][TremVibSusEnvMult0] >> 7], toggle[instr[n][TremVibSusEnvMult1] >> 7]); string(screen, Pt(orig.x, orig.y + font->height), fg, ZP, font, s); sprint(s, " vibrato %13-s%s", toggle[instr[n][TremVibSusEnvMult0] >> 6 & 1], toggle[instr[n][TremVibSusEnvMult1] >> 6 & 1]); string(screen, Pt(orig.x, orig.y + 2 * font->height), fg, ZP, font, s); sprint(s, " sustain %13-s%s", toggle[instr[n][TremVibSusEnvMult0] >> 5 & 1], toggle[instr[n][TremVibSusEnvMult1] >> 5 & 1]); string(screen, Pt(orig.x, orig.y + 3 * font->height), fg, ZP, font, s); sprint(s, " envscale %13-s%s", toggle[instr[n][TremVibSusEnvMult0] >> 4 & 1], toggle[instr[n][TremVibSusEnvMult1] >> 4 & 1]); string(screen, Pt(orig.x, orig.y + 4 * font->height), fg, ZP, font, s); sprint(s, " multiply %13-s%s", mult[instr[n][TremVibSusEnvMult0] & 0x0F], mult[instr[n][TremVibSusEnvMult1] & 0x0F]); string(screen, Pt(orig.x, orig.y + 5 * font->height), fg, ZP, font, s); sprint(s, " attack %13-s%s", nibble[instr[n][AttackDecay0] >> 4], nibble[instr[n][AttackDecay1] >> 4]); string(screen, Pt(orig.x, orig.y + 6 * font->height), fg, ZP, font, s); sprint(s, " decay %13-s%s", nibble[instr[n][AttackDecay0] & 0x0F], nibble[instr[n][AttackDecay1] & 0x0F]); string(screen, Pt(orig.x, orig.y + 7 * font->height), fg, ZP, font, s); sprint(s, " sustain %13-s%s", nibble[instr[n][SustainRelease0] >> 4], nibble[instr[n][SustainRelease1] >> 4]); string(screen, Pt(orig.x, orig.y + 8 * font->height), fg, ZP, font, s); sprint(s, " release %13-s%s", nibble[instr[n][SustainRelease0] & 0x0F], nibble[instr[n][SustainRelease1] & 0x0F]); string(screen, Pt(orig.x, orig.y + 9 * font->height), fg, ZP, font, s); sprint(s, " waveform %13-s%s", wave[instr[n][GlideWaves] & 7], wave[instr[n][GlideWaves] >> 3 & 7]); string(screen, Pt(orig.x, orig.y + 10 * font->height), fg, ZP, font, s); sprint(s, "attenuate %13-s%s", attenuate[instr[n][AttenuationLevel0] >> 6], attenuate[instr[n][AttenuationLevel1] >> 6]); string(screen, Pt(orig.x, orig.y + 11 * font->height), fg, ZP, font, s); sprint(s, " level %13-s%s", level[instr[n][AttenuationLevel0] & 0x3F], level[instr[n][AttenuationLevel1] & 0x3F]); string(screen, Pt(orig.x, orig.y + 12 * font->height), fg, ZP, font, s); sprint(s, "synth/pan %13-s%s", fmam[instr[n][TrigPanFbSynth] & 1], pan[instr[n][TrigPanFbSynth] >> 4 & 3]); string(screen, Pt(orig.x, orig.y + 13 * font->height), fg, ZP, font, s); sprint(s, " fb/glide %13-s%s", feedback[instr[n][TrigPanFbSynth] >> 1 & 7], toggle[instr[n][GlideWaves] >> 6 & 1]); string(screen, Pt(orig.x, orig.y + 14 * font->height), fg, ZP, font, s); sprint(s, "note/trig %13-s%s", pitch[instr[n][Note]], trig[instr[n][TrigPanFbSynth] >> 6 & 3]); string(screen, Pt(orig.x, orig.y + 15 * font->height), fg, ZP, font, s); string(screen, Pt(orig.x, orig.y + 16 * font->height), fg, ZP, font, " zerox instr rename instr"); flushimage(display, 1); } char * instrmenugen(int n){ static char buf[3 + sizeof(instrname[0])] = {0, 0, ' '}; if(n < 0 || n >= nelem(instrname)) return nil; buf[0] = hex[n >> 4]; buf[1] = hex[n & 15]; strcpy(buf + 3, instrname[n]); return buf; } void sendinstr(int fd, int c, int i){ static uchar out[55]; out[0] = 0x20 | op[c]; out[1] = ext[c]; out[2] = reg[c][TremVibSusEnvMult0] = instr[i][TremVibSusEnvMult0]; out[5] = out[0] + 3; out[6] = ext[c]; out[7] = reg[c][TremVibSusEnvMult1] = instr[i][TremVibSusEnvMult1]; out[10] = 0x60 | op[c]; out[11] = ext[c]; out[12] = reg[c][AttackDecay0] = instr[i][AttackDecay0]; out[15] = out[10] + 3; out[16] = ext[c]; out[17] = reg[c][AttackDecay1] = instr[i][AttackDecay1]; out[20] = 0x80 | op[c]; out[21] = ext[c]; out[22] = reg[c][SustainRelease0] = instr[i][SustainRelease0]; out[25] = out[20] + 3; out[26] = ext[c]; out[27] = reg[c][SustainRelease1] = instr[i][SustainRelease1]; reg[c][GlideWaves] = instr[i][GlideWaves]; out[30] = 0xE0 | op[c]; out[31] = ext[c]; out[32] = reg[c][GlideWaves] & 7; out[35] = out[30] + 3; out[36] = ext[c]; out[37] = reg[c][GlideWaves] >> 3 & 7; out[40] = 0x40 | op[c]; out[41] = ext[c]; out[42] = reg[c][AttenuationLevel0] = instr[i][AttenuationLevel0]; out[45] = out[40] + 3; out[46] = ext[c]; out[47] = reg[c][AttenuationLevel1] = instr[i][AttenuationLevel1]; out[50] = 0xC0 | ch[c]; out[51] = ext[c]; reg[c][TrigPanFbSynth] = instr[i][TrigPanFbSynth] & 0xC0 ? instr[i][TrigPanFbSynth] : reg[c][TrigPanFbSynth] & 0xC0 | instr[i][TrigPanFbSynth]; out[52] = instr[i][TrigPanFbSynth] & 0x3F & (header[3 * c + 6] == 'm') - 1; write(fd, out, 55); } void sendrow(int fd){ static uchar buf[(18 * 3 + 2) * 5]; uchar *b; int i, bend, f, from[18], to[18]; b = buf; bend = 0; switch(global[row]){ case 48: /* Deep Tremolo off */ b[0] = 0xBD; b[1] = 0; b[2] = wiggles &= 0x7F; b += 5; break; case 49: /* Deep Tremolo on */ b[0] = 0xBD; b[1] = 0; b[2] = wiggles |= 0x80; b += 5; break; case 50: /* Deep Vibrato off */ b[0] = 0xBD; b[1] = 0; b[2] = wiggles &= 0xBF; b += 5; break; case 51: /* Deep Vibrato on */ b[0] = 0xBD; b[1] = 0; b[2] = wiggles |= 0x40; b += 5; break; case 53: case 54: case 55: case 56: case 57: case 58: /* ch pair 2-op */ b[0] = 0x04; b[1] = 1; b[2] = four &= 0xFF ^ 1 << global[row] - 53; b += 5; break; case 60: case 61: case 62: case 63: case 64: case 65: /* ch pair 4-op */ b[0] = 0x04; b[1] = 1; b[2] = four |= 1 << global[row] - 60; b += 5; break; } for(i = 0; i < 18; i++){ to[i] = 0; if(global[row] == 92){ /* fadeout */ if(reg[i][AttenuationLevel1] & 0x3F ^ 0x3F){ b[0] = 0x40 | op[i] + 3; b[1] = ext[i]; b[2] = ++reg[i][AttenuationLevel1]; b += 5; } if(reg[i][TrigPanFbSynth] & 1 && reg[i][AttenuationLevel0] & 0x3F ^ 0x3F){ b[0] = 0x40 | op[i]; b[1] = ext[i]; b[2] = ++reg[i][AttenuationLevel0]; b += 5; } } if(!ev[row][i]) continue; if(ev[row][i] == nelem(pitch) - 1){ /* ^^ key up */ b[0] = 0xB0 | ch[i]; b[1] = ext[i]; b[2] = freq[reg[i][Note]] >> 8; b += 5; continue; } if(ev[row][i] == nelem(pitch)){ /* -- ch off */ b[0] = 0xC0 | ch[i]; b[1] = ext[i]; b[2] = 0; b += 5; continue; } if(ev[row][i] == nelem(pitch) + 1){ /* ++ ch on */ b[0] = 0xC0 | ch[i]; b[1] = ext[i]; b[2] = reg[i][TrigPanFbSynth] & 0x3F & (header[3 * i + 6] == 'm') - 1; b += 5; continue; } from[i] = freq[reg[i][Note]]; if(ev[row][i] > nelem(pitch) + 1){ sendinstr(fd, i, ev[row][i] - nelem(pitch) - 2); if(!instr[ev[row][i] - nelem(pitch) - 2][Note]) continue; reg[i][Note] = instr[ev[row][i] - nelem(pitch) - 2][Note]; }else reg[i][Note] = ev[row][i]; if((reg[i][TrigPanFbSynth] >> 6) == 3){ b[0] = 0xB0 | ch[i]; b[1] = ext[i]; b[2] = 0; b += 5; } if(reg[i][GlideWaves] & 0x40){ bend = 1; to[i] = reg[i][TrigPanFbSynth] << 7 & 0x2000 | freq[reg[i][Note]]; if(from[i]) from[i] |= to[i] & 0x2000; else from[i] = to[i]; continue; } b[0] = 0xB0 | ch[i]; b[1] = ext[i]; b[2] = reg[i][TrigPanFbSynth] >> 1 & 0x20 | freq[reg[i][Note]] >> 8; b[5] = 0xA0 | ch[i]; b[6] = ext[i]; b[7] = freq[reg[i][Note]]; b += 10; } if(b == buf) buf[0] = buf[1] = buf[2] = 0; else b -= 5; if(global[row] >= 94){ bpm = 10 + global[row] - 94 << 1; incr = playing / bpm / rpb; } else if(global[row] > 66 && global[row] < 91){ rpb = global[row] - 66; incr = playing / bpm / rpb; } if(!bend){ b[3] = incr; b[4] = incr >> 8; write(fd, buf, b - buf + 5); b[3] = b[4] = 0; return; } write(fd, buf, b - buf + 5); for(; bend <= incr; bend++){ b = buf; for(i = 0; i < 18; i++) if(to[i]){ f = from[i] + (to[i] - from[i]) * bend / incr; b[0] = 0xB0 | ch[i]; b[1] = ext[i]; b[2] = f >> 8; b[5] = 0xA0 | ch[i]; b[6] = ext[i]; b[7] = f; b += 10; } b[-2] = !!incr; write(fd, buf, b - buf); b[-2] = 0; } } int oplinit(void){ int fd, p[2]; fd = open("/dev/audio", OWRITE); if(fd < 0 || pipe(p) < 0 && close(fd)) return -1; switch(fork()){ case -1: close(fd); close(p[0]); close(p[1]); return -1; case 0: close(p[0]); dup(p[1], 0); close(p[1]); dup(fd, 1); close(fd); execl("/bin/games/opl3", "opl3", "-r", "1000", nil); sysfatal("execl: %r"); } close(fd); close(p[1]); write(p[0], "\1\0 \0\0\5\1\1\n", 10); return p[0]; } int readinstr(char *f){ uchar *bank; int fd, n; fd = open(f, OREAD); if(fd < 0) return -1; bank = malloc(8 + 175 * 36 + 175 * 32); if(bank == nil) sysfatal("malloc: %r"); n = read(fd, bank, 8 + 175 * 36 + 175 * 32); close(fd); if(n != 8 + 175 * 36 + 175 * 32 || memcmp(bank, "#OPL_II#", 8)) sysfatal("readinstr: not an OPL2 bank?"); for(n = 0; n < nelem(instr); n++){ instr[n][TremVibSusEnvMult0] = bank[8 + n * 36 + 4]; instr[n][TremVibSusEnvMult1] = bank[8 + n * 36 + 4 + 7]; instr[n][AttackDecay0] = bank[8 + n * 36 + 5]; instr[n][AttackDecay1] = bank[8 + n * 36 + 5 + 7]; instr[n][SustainRelease0] = bank[8 + n * 36 + 6]; instr[n][SustainRelease1] = bank[8 + n * 36 + 6 + 7]; instr[n][GlideWaves] = bank[8 + n * 36 + 7] | bank[8 + n * 36 + 7 + 7] << 3; instr[n][AttenuationLevel0] = bank[8 + n * 36 + 8] | bank[8 + n * 36 + 9]; instr[n][AttenuationLevel1] = bank[8 + n * 36 + 8 + 7] | bank[8 + n * 36 + 9 + 7]; instr[n][TrigPanFbSynth] = 0xC0 | bank[8 + n * 36 + 1] << 4 & 0x30 ^ 0x30 | bank[8 + n * 36 + 10]; memcpy(instrname[n], bank + 8 + 175 * 36 + n * 32, 31); } free(bank); return 0; } void usage(void){ sysfatal("usage: %s [-i instr] [file]", argv0); } void threadmain(int argc, char **argv){ Mousectl *⌖; Keyboardctl *⌨; Menu ≡; Rune k; char path[ERRMAX] = {0}, buf[ERRMAX]; int i = -1, rep, fd; uchar *b, snarf = 0; Point snarfp = ZP; Rectangle snarfr = ZR; vlong t, zzz; enum{RESIZE, MOUSE, KEYBOARD}; Alt a[] = { [RESIZE]{nil, nil, CHANRCV}, [MOUSE]{nil, nil, CHANRCV}, [KEYBOARD]{nil, &k, CHANRCV}, {nil, nil, CHANEND} }; ARGBEGIN{ case 'i': if(i = readinstr(EARGF(usage()))) sysfatal("read: %r"); break; default: usage(); }ARGEND if(argc){ fd = open(*argv, OREAD); if(fd < 0) sysfatal("open: %r"); if(read(fd, buf, 8) != 8 || buf[0] != 'O' || buf[1] != 'P' || buf[2] != 'L' || buf[3] != 'T' || buf[4] != 'R' || buf[5] != 'K') sysfatal("bad track format"); rows = (uchar)buf[6] | (uchar)buf[7] << 8; for(i = 0; i < rows; i++) if(read(fd, ev[i], 18) != 18) sysfatal("short track read"); if(read(fd, global, rows) != rows) sysfatal("short track read"); for(i = 0; i < nelem(instr); i++) if(read(fd, instrname[i], sizeof(instrname[0])) != sizeof(instrname[0]) || read(fd, instr[i], INSTRLEN) != INSTRLEN) sysfatal("short instr read"); close(fd); strecpy(path, path + sizeof(path), *argv); } else if(i < 0 && readinstr("/mnt/wad/genmidi")) for(i = 0; i < nelem(instr); i++){ instr[i][12] = 2; sprint(instrname[i], "instrument 0x%.2X", i); } if(initdraw(nil, nil, argv0) < 0) sysfatal("initdraw: %r"); bg = allocimagemix(display, DPalebluegreen, DWhite); cursor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen); fg = display->black; grey = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DPurpleblue); if(bg == nil || cursor == nil || fg == nil || grey == 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; fmtrows(); em = stringwidth(font, "M"); /* yep, better be monospaced… */ resize(); ≡.gen = instrmenugen; while(9) switch(alt(a)){ case MOUSE: if(instrmode){ if(⌖->buttons != 1 || ⌖->xy.x < orig.x + 10 * em || ⌖->xy.x >= orig.x + 36 * em || ⌖->xy.y < orig.y) continue; switch((⌖->xy.y - orig.y) / font->height){ case 0: ≡.item = nil; ≡.lasthit = i; menuhit(1, ⌖, &≡, nil); i = ≡.lasthit; break; case 1: ≡.item = toggle; b = instr[i] + TremVibSusEnvMult0 + (⌖->xy.x >= orig.x + 23 * em); ≡.lasthit = *b >> 7; menuhit(1, ⌖, &≡, nil); *b = *b & 0x7F | ≡.lasthit << 7; break; case 2: ≡.item = toggle; b = instr[i] + TremVibSusEnvMult0 + (⌖->xy.x >= orig.x + 23 * em); ≡.lasthit = *b >> 6 & 1; menuhit(1, ⌖, &≡, nil); *b = *b & 0xBF | ≡.lasthit << 6; break; case 3: ≡.item = toggle; b = instr[i] + TremVibSusEnvMult0 + (⌖->xy.x >= orig.x + 23 * em); ≡.lasthit = *b >> 5 & 1; menuhit(1, ⌖, &≡, nil); *b = *b & 0xDF | ≡.lasthit << 5; break; case 4: ≡.item = toggle; b = instr[i] + TremVibSusEnvMult0 + (⌖->xy.x >= orig.x + 23 * em); ≡.lasthit = *b >> 4 & 1; menuhit(1, ⌖, &≡, nil); *b = *b & 0xEF | ≡.lasthit << 4; break; case 5: ≡.item = mult; b = instr[i] + TremVibSusEnvMult0 + (⌖->xy.x >= orig.x + 23 * em); ≡.lasthit = *b & 0x0F; menuhit(1, ⌖, &≡, nil); *b = *b & 0xF0 | ≡.lasthit; break; case 6: ≡.item = nibble; b = instr[i] + AttackDecay0 + (⌖->xy.x >= orig.x + 23 * em); ≡.lasthit = *b >> 4; menuhit(1, ⌖, &≡, nil); *b = *b & 0x0F | ≡.lasthit << 4; break; case 7: ≡.item = nibble; b = instr[i] + AttackDecay0 + (⌖->xy.x >= orig.x + 23 * em); ≡.lasthit = *b & 0x0F; menuhit(1, ⌖, &≡, nil); *b = *b & 0xF0 | ≡.lasthit; break; case 8: ≡.item = nibble; b = instr[i] + SustainRelease0 + (⌖->xy.x >= orig.x + 23 * em); ≡.lasthit = *b >> 4; menuhit(1, ⌖, &≡, nil); *b = *b & 0x0F | ≡.lasthit << 4; break; case 9: ≡.item = nibble; b = instr[i] + SustainRelease0 + (⌖->xy.x >= orig.x + 23 * em); ≡.lasthit = *b & 0x0F; menuhit(1, ⌖, &≡, nil); *b = *b & 0xF0 | ≡.lasthit; break; case 10: ≡.item = wave; b = instr[i] + GlideWaves; if(⌖->xy.x < orig.x + 23 * em){ ≡.lasthit = *b & 7; menuhit(1, ⌖, &≡, nil); *b = *b & 0xF8 | ≡.lasthit; }else{ ≡.lasthit = *b >> 3 & 7; menuhit(1, ⌖, &≡, nil); *b = *b & 0xC7 | ≡.lasthit << 3; } break; case 11: ≡.item = attenuate; b = instr[i] + AttenuationLevel0 + (⌖->xy.x >= orig.x + 23 * em); ≡.lasthit = *b >> 6; menuhit(1, ⌖, &≡, nil); *b = *b & 0x3F | ≡.lasthit << 6; break; case 12: ≡.item = level; b = instr[i] + AttenuationLevel0 + (⌖->xy.x >= orig.x + 23 * em); ≡.lasthit = *b & 0x3F; menuhit(1, ⌖, &≡, nil); *b = *b & 0xC0 | ≡.lasthit; break; case 13: b = instr[i] + TrigPanFbSynth; if(⌖->xy.x < orig.x + 23 * em){ ≡.item = fmam; ≡.lasthit = *b & 1; menuhit(1, ⌖, &≡, nil); *b = *b & 0xFE | ≡.lasthit; }else{ ≡.item = pan; ≡.lasthit = *b >> 4 & 3; menuhit(1, ⌖, &≡, nil); *b = *b & 0xCF | ≡.lasthit << 4; } break; case 14: if(⌖->xy.x < orig.x + 23 * em){ b = instr[i] + TrigPanFbSynth; ≡.item = feedback; ≡.lasthit = *b >> 1 & 7; menuhit(1, ⌖, &≡, nil); *b = *b & 0xF1 | ≡.lasthit << 1; }else{ b = instr[i] + GlideWaves; ≡.item = toggle; ≡.lasthit = *b >> 6 & 1; menuhit(1, ⌖, &≡, nil); *b = *b & 0xBF | ≡.lasthit << 6; } break; case 15: if(⌖->xy.x < orig.x + 23 * em){ ≡.item = pitch; ≡.lasthit = instr[i][Note]; menuhit(1, ⌖, &≡, nil); instr[i][Note] = ≡.lasthit; }else{ b = instr[i] + TrigPanFbSynth; ≡.item = trig; ≡.lasthit = *b >> 6 & 3; menuhit(1, ⌖, &≡, nil); *b = *b & 0x3F | ≡.lasthit << 6; } break; case 16: if(⌖->xy.x < orig.x + 23 * em){ ≡.item = nil; ≡.lasthit = i; if(menuhit(1, ⌖, &≡, nil) < 0) continue; memmove(instr[i], instr[≡.lasthit], INSTRLEN); strcpy(instrname[i], instrname[≡.lasthit]); }else enter("rename", instrname[i], sizeof(instrname[i]), ⌖, ⌨, nil); break; default: continue; } drawinstr(i); break; } switch(⌖->buttons){ case 1: while(readmouse(⌖), ⌖->buttons); i = ⌖->xy.x - orig.x - 9 * em / 2; if(i >= 0 && i < 3 * 18 * em){ col = i / 3 / em; vert.min.x = orig.x + (5 + 3 * col) * em; vert.max.x = vert.min.x + 2 * em; } i = row - 8 + (⌖->xy.y - orig.y) / font->height; if(i >= 0 && i < rows) row = i; fmtrows(); break; case 4: ≡.item = globals; ≡.lasthit = global[row]; menuhit(3, ⌖, &≡, nil); global[row] = ≡.lasthit; fmtrow(row); break; default: continue; } drawrows(); break; case RESIZE: if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); if(instrmode){ orig.x = screen->r.min.x + screen->r.max.x - 36 * em >> 1; orig.y = screen->r.min.y + screen->r.max.y - 17 * font->height >> 1; drawinstr(i); }else resize(); break; case KEYBOARD: if(instrmode){ switch(k){/*TODO: notes?*/ case Kdel: case Kbs: instr[i][Note] = 0; break; case 'c': instr[i][Note] = oct * 12 + 1; goto testnote; case 'C': instr[i][Note] = oct * 12 + 2; goto testnote; case 'd': instr[i][Note] = oct * 12 + 3; goto testnote; case 'D': instr[i][Note] = oct * 12 + 4; goto testnote; case 'e': instr[i][Note] = oct * 12 + 5; goto testnote; case 'f': instr[i][Note] = oct * 12 + 6; goto testnote; case 'F': instr[i][Note] = oct * 12 + 7; goto testnote; case 'g': instr[i][Note] = oct * 12 + 8; goto testnote; case 'G': instr[i][Note] = oct * 12 + 9; goto testnote; case 'a': instr[i][Note] = oct * 12 + 10; goto testnote; case 'A': instr[i][Note] = oct * 12 + 11; goto testnote; case 'b': instr[i][Note] = oct * 12 + 12; goto testnote; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': oct = k - '0'; if(!instr[i][Note] || oct == 8 && (instr[i][Note] - 1) % 12 > 7){ sprint(buf, "input octave set to %d", oct); string(screen, screen->r.min, fg, ZP, font, buf); flushimage(display, 1); continue; } instr[i][Note] = 12 * oct + (instr[i][Note] - 1) % 12 + 1; case ' ': testnote: fd = oplinit(); if(fd < 0){uhoh: *buf = 0; errstr(buf, ERRMAX); enter(buf, nil, 0, ⌖, ⌨, nil); continue; } sendinstr(fd, 0, i); buf[0] = 0xA0; buf[1] = 0; buf[2] = freq[instr[i][Note]]; buf[3] = 0; buf[4] = 0; buf[5] = 0xB0; buf[6] = 0; buf[7] = 0x20 | freq[instr[i][Note]] >> 8; buf[8] = 0; buf[9] = 2 * 4; /* ~ 2 seconds */ write(fd, buf, 10); close(fd); break; case '\n': ev[row][col] = nelem(pitch) + 2 + i; fmtrow(row); instrmode = 0; resize(); continue; } drawinstr(i); break; } switch(k){ case 'q': draw(screen, screen->r, bg, nil, ZP); string(screen, Pt(screen->r.min.x + screen->r.max.x - em >> 1, screen->r.min.y + screen->r.max.y - 12 * font->height >> 1), fg, ZP, font, "☠"); string(screen, Pt(screen->r.min.x + screen->r.max.x - 12 * em >> 1, screen->r.min.y + screen->r.max.y - 8 * font->height >> 1), fg, ZP, font, "Say q again."); string(screen, Pt(screen->r.min.x + screen->r.max.x - 23 * em >> 1, screen->r.min.y + screen->r.max.y - 4 * font->height >> 1), fg, ZP, font, "Say q again! I dare ya!"); string(screen, Pt(screen->r.min.x + screen->r.max.x - 32 * em >> 1, screen->r.min.y + screen->r.max.y >> 1), fg, ZP, font, "I double dare you, motherfucker!"); string(screen, Pt(screen->r.min.x + screen->r.max.x - 29 * em >> 1, screen->r.min.y + screen->r.max.y + 4 * font->height >> 1), fg, ZP, font, "Say q one more goddamn time!"); string(screen, Pt(screen->r.min.x + screen->r.max.x - em >> 1, screen->r.min.y + screen->r.max.y + 8 * font->height >> 1), fg, ZP, font, "☠"); flushimage(display, 1); recv(⌨->c, &k); if(k == 'q') threadexitsall(nil); break; case 'w': enter("write", path, sizeof(path), ⌖, ⌨, nil); fd = create(path, OWRITE, 0666); if(fd < 0)goto uhoh; buf[0] = 'O'; buf[1] = 'P'; buf[2] = 'L'; buf[3] = 'T'; buf[4] = 'R'; buf[5] = 'K'; buf[6] = rows; buf[7] = rows >> 8; if(write(fd, buf, 8) != 8){close(fd); goto uhoh;} for(i = 0; i < rows; i++) if(write(fd, ev[i], 18) != 18){close(fd); goto uhoh;} if(write(fd, global, rows) != rows){close(fd); goto uhoh;} for(i = 0; i < nelem(instr); i++) if(write(fd, instrname[i], sizeof(instrname[0])) != sizeof(instrname[0]) || write(fd, instr[i], INSTRLEN) != INSTRLEN){ close(fd); goto uhoh; } close(fd); break; case ']': *buf = 0; enter("insert how many rows?", buf, sizeof(buf), ⌖, ⌨, nil); k = atoi(buf); if(k < 1) continue; if(rows + k >= sizeof(global)){ enter("can't fit 'em!", nil, 0, ⌖, ⌨, nil); continue; } for(i = rows - 1; i >= row; i--){ memmove(ev[i + k], ev[i], 18); global[i + k] = global[i]; } rows += k; for(k += row; row < k; row++){ memset(ev[row], 0, 18); global[row] = 0; } fmtrows(); break; case '[': *buf = 0; enter("delete how many rows?", buf, sizeof(buf), ⌖, ⌨, nil); k = atoi(buf); if(k < 1) continue; rows -= k; if(row >= rows){ row -= !!row; rows = row + 1; }else for(i = row; i < rows; i++){ memmove(ev[i], ev[i + k], 18); global[i] = global[i + k]; } if(snarfr.max.y >= rows){ snarfp = ZP; snarfr = ZR; } fmtrows(); break; case 's': snarfr = canonrect(Rect(snarfp.x, snarfp.y, col, row)); snarfp = Pt(col, row); case 'S': snprint(buf, sizeof(buf), "snarf rect: rows %.4X–%.4X, cols %c–%c", snarfr.min.y, snarfr.max.y, 'A' + snarfr.min.x, 'A' + snarfr.max.x); stringbg(screen, screen->r.min, fg, ZP, font, buf, bg, ZP); flushimage(display, 1); continue; case 'P': case 'p': t = snarfr.min.y - row; zzz = Dx(snarfr) + 1; if(col + zzz > 18) zzz = 18 - col; if(t < 0){ i = row + Dy(snarfr) + 1; if(i >= rows) i = rows; while(--i >= row){ memmove(ev[i] + col, ev[i + t] + snarfr.min.x, zzz); if(zzz == 18) global[i] = global[i + t]; } }else{ for(i = snarfr.min.y; i <= snarfr.max.y; i++){ memmove(ev[i - t] + col, ev[i] + snarfr.min.x, zzz); if(zzz == 18) global[i - t] = global[i]; } } if(k == 'P'){ zzz = Dx(snarfr) + 1; for(i = snarfr.min.y; i <= snarfr.max.y; i++) memset(ev[i] + snarfr.min.x, 0, zzz); if(zzz == 18) for(i = snarfr.min.y; i <= snarfr.max.y; i++) global[i] = 0; } fmtrows(); break; case Kdel: case Kbs: snarf = ev[row][col]; ev[row][col] = 0; fmtrow(row); break; case Kesc: ev[row][col] = snarf; fmtrow(row); break; case 'x': case '^': snarf = ev[row][col]; ev[row][col] = nelem(pitch) - 1; fmtrow(row); break; case '-': snarf = ev[row][col]; ev[row][col] = nelem(pitch); fmtrow(row); break; case '+': case '=': snarf = ev[row][col]; ev[row][col] = nelem(pitch) + 1; fmtrow(row); break; case 'c': snarf = ev[row][col]; ev[row][col] = oct * 12 + 1; fmtrow(row); break; case 'C': snarf = ev[row][col]; ev[row][col] = oct * 12 + 2; fmtrow(row); break; case 'd': snarf = ev[row][col]; ev[row][col] = oct * 12 + 3; fmtrow(row); break; case 'D': snarf = ev[row][col]; ev[row][col] = oct * 12 + 4; fmtrow(row); break; case 'e': snarf = ev[row][col]; ev[row][col] = oct * 12 + 5; fmtrow(row); break; case 'f': snarf = ev[row][col]; ev[row][col] = oct * 12 + 6; fmtrow(row); break; case 'F': snarf = ev[row][col]; ev[row][col] = oct * 12 + 7; fmtrow(row); break; case 'g': snarf = ev[row][col]; ev[row][col] = oct * 12 + 8; fmtrow(row); break; case 'G': if(oct == 8) continue; snarf = ev[row][col]; ev[row][col] = oct * 12 + 9; fmtrow(row); break; case 'a': if(oct == 8) continue; snarf = ev[row][col]; ev[row][col] = oct * 12 + 10; fmtrow(row); break; case 'A': if(oct == 8) continue; snarf = ev[row][col]; ev[row][col] = oct * 12 + 11; fmtrow(row); break; case 'b': if(oct == 8) continue; snarf = ev[row][col]; ev[row][col] = oct * 12 + 12; fmtrow(row); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': oct = k - '0'; if(!ev[row][col] || ev[row][col] >= nelem(pitch) - 1 || oct & 8 && (ev[row][col] + 1) % 12 > 7){ sprint(buf, "input octave set to %d", oct); string(screen, screen->r.min, fg, ZP, font, buf); flushimage(display, 1); continue; } snarf = ev[row][col]; ev[row][col] = 12 * oct + (ev[row][col] - 1) % 12 + 1; fmtrow(row); break; case '\n': case 'i': snarf = ev[row][col]; i = ev[row][col] >= nelem(pitch) + 2 ? ev[row][col] - nelem(pitch) - 2 : 0; instrmode = 1; orig.x = screen->r.min.x + screen->r.max.x - 36 * em >> 1; orig.y = screen->r.min.y + screen->r.max.y - 19 * font->height >> 1; drawinstr(i); continue; case Kleft: col = (col + 17) % 18; vert.min.x = orig.x + (5 + 3 * col) * em; vert.max.x = vert.min.x + 2 * em; break; case Kright: col = (col + 1) % 18; vert.min.x = orig.x + (5 + 3 * col) * em; vert.max.x = vert.min.x + 2 * em; break; case Kup: if(row == 0) continue; fmtrow(--row - 7); break; case Kdown: if(row + 1 == rows) continue; fmtrow(++row + 8); break; case Kpgup: if(row - skip < 0) continue; row -= skip; fmtrows(); break; case Kpgdown: if(row + skip >= rows) continue; row += skip; fmtrows(); break; case Khome: row = 0; fmtrows(); break; case Kend: row = rows - 1; fmtrows(); break; case 'm': header[3 * col + 6] ^= 'm' ^ ' '; break; case 'k': stringbg(screen, screen->r.min, fg, ZP, font, "press an alphanum to set mark", bg, ZP); flushimage(display, 1); recv(⌨->c, &k); if(k >= '0' && k <= '9') global[row] = k - '0' + 1; else if(k >= 'a' && k <= 'z') global[row] = k - 'a' + 11; else if(global[row] < 11 + 26) global[row] = 0; fmtrow(row); break; case 'j': stringbg(screen, screen->r.min, fg, ZP, font, "press an alphanum to jump to mark", bg, ZP); flushimage(display, 1); recv(⌨->c, &k); if(k >= '0' && k <= '9') k = k - '0' + 1; else if(k >= 'a' && k <= 'z') k = k - 'a' + 11; else continue; for(i = row + 1; i < rows && global[i] != k; i++); if(i == rows) for(i = 0; i < row && global[i] != k; i++); if(global[i] != k) continue; row = i; fmtrows(); break; case KF | 1: case KF | 2: case KF | 3: case KF | 4: case KF | 5: case KF | 6: case KF | 7: case KF | 8: case KF | 9: case KF | 10: case KF | 11: case KF | 12: skip = k & 15; snprint(buf, sizeof(buf), "PgUp/PgDn = %2.-d", skip); stringbg(screen, screen->r.min, fg, ZP, font, buf, bg, ZP); flushimage(display, 1); continue; case ' ': fd = oplinit(); if(fd < 0) goto uhoh; for(i = 0; i < 18; i++) memset(reg[i], 0, INSTRLEN); bpm = 120; rpb = 4; playing = incr = rep = wiggles = four = 0; i = row; for(row = 0; row < i; row++) sendrow(fd); playing = 60000; incr = playing / bpm / rpb; t = nsec(); for(sendrow(fd); row < rows; drawrows()){ while(nbrecv(⌖->c, &⌖->Mouse) && !⌖->buttons); if(⌖->buttons) break; if(nbrecv(⌨->c, &k)){ if(k == Kleft){ col = (col + 17) % 18; vert.min.x = orig.x + (5 + 3 * col) * em; vert.max.x = vert.min.x + 2 * em; }else if(k == Kright){ col = (col + 1) % 18; vert.min.x = orig.x + (5 + 3 * col) * em; vert.max.x = vert.min.x + 2 * em; }else if(k == 'm'){ header[3 * col + 6] ^= 'm' ^ ' '; buf[0] = 0xC0 | ch[col]; buf[1] = ext[col]; buf[2] = 0x30 & (header[3 * col + 6] == 'm') - 1; buf[3] = buf[4] = 0; write(fd, buf, 5); }else break; } t += incr * 1000000; if(global[row] == 46){ if(rep == 1){ do ++row; while(global[row] < 39 || global[row] > 45); row++; rep = 0; fmtrows(); }else fmtrow(++row + 8); } else if(global[row] >= 39 && global[row] <= 45){ if(rep == 1){ fmtrow(++row + 8); rep = 0; }else{ rep += rep ? -1 : global[row] - 39 + 1; for(row -= !!row; row && (global[row] < 38 || global[row] > 45); row--); fmtrows(); } } else fmtrow(++row + 8); if(row < rows) sendrow(fd); zzz = (t - nsec()) / 1000000; if(zzz > 1) sleep(zzz); } if(k != Kesc) row = i; fmtrows(); close(fd); break; } drawrows(); break; default: sysfatal("labyrinthus: hic habitat minotaurus"); } }