desc:GFX G Chip 48 //tags: instrument synth chiptune 8-bit beeper psg fm retro //author: JGWizrad / Garden FX (GFX) // // Requires gfx-drift, gfx-output, gfx-ui, gfx-panel, gfx-instrument, gfx-voice and gfx-amp (.jsfx-inc) // in the same folder. // // Chiptune synth: the sound chips of the 8-bit home computers and early consoles (personal use; the sounds // are designed from scratch). A rack sound module: the rubber keys pick the chip and the patch, the knobs shape it. // CHIP (keys 1-5) - BEEPER (48K one-bit speaker), AY (128K three-voice chip with its buzzer envelope), // PSG (SN-style squares, white / periodic noise), FM (16-bit console, four operators), // PULSE (8-bit console: 12.5 / 25 / 50 % pulses and a stepped triangle). // Pitches snap to each chip's own tuning steps and volume to its own level steps. // TONE / NOISE - DUTY (beeper and PULSE), MIXER, NOISE pitch and type. BUZZ - the AY envelope at audio // rate: saw, triangle or synced to the tone, tuned an octave down, unison or up. // ENVELOPE - ATTACK, DECAY, SUSTAIN (0-15, as the chips), RELEASE, stepped once a frame. // ARP / VIBRATO / PITCH - tracker-style: chord ARPs (or HELD: every held note in turn on one voice), // delayed vibrato, GLIDE and a pitch SWEEP (lasers, drums). PAL / NTSC sets the frame rate. // FM - ALGO (the eight four-operator algorithms), FEEDBACK, MOD level, MOD DECAY, DETUNE and // the DAC LADDER grit of the early chip. // DRUMS - each chip's own kit on the GM drum notes (36 kick, 38 / 40 snare, 42 / 44 / 46 hats, // 41-50 toms, 49 / 57 crash, 51 ride); other notes play toms. // CHANNELS - AUTHENTIC keeps each chip's voice count (beeper 1, AY / PSG 3, FM 6, PULSE 2 - 1 on the // triangle); 6 VOICES lifts it. LO-FI - the machine's own speaker / output filter. // MIDI: pitch bend +/-2, CC1 vibrato, CC64 sustain, CC123 all notes off. // IN / OUT: the strip along the bottom holds SAT, the OUT stage (OFF / CLIP / LIM) and STEREO (MONO, WIDTH). import gfx-drift.jsfx-inc import gfx-output.jsfx-inc import gfx-ui.jsfx-inc import gfx-panel.jsfx-inc import gfx-instrument.jsfx-inc import gfx-voice.jsfx-inc import gfx-amp.jsfx-inc slider1:0<0,4,1{Beeper (48K),AY (128K),PSG,FM,Pulse}>-Chip slider2:0<0,40,1{48K Lead,Thin Pulse,Phaser Lead,Beeper Bass,Chord Arp,Laser Zap,Beeper Drums,Soft Hum,AY Lead,Buzzer Bass,Buzz Tri Bass,Sync Lead,Arp Chords,Soft Bell,Steam Lead,AY Drums,PSG Lead,PSG Bass,Periodic Bass,Major Arp,Minor Arp,PSG Bell,Vib Square,PSG Drums,16-Bit Bass,FM Brass,FM E.Piano,FM Lead,FM Strings,FM Marimba,FM Organ,FM Drums,Pulse 12.5,Pulse 25 Lead,Square 50,Triangle Bass,Echo Arp,Vibrato Lead,Slide Lead,8-Bit Drums,Init}>-Patch slider3:50<5,50,1>-Duty (%) slider4:0<0,3,1{Tone,Tone+Noise,Noise,Triangle}>-Mixer slider5:70<0,100,1>-Noise pitch slider6:0<0,1,1{White,Periodic}>-Noise type slider7:0<0,2000,1>-Attack (ms) slider8:400<0,5000,1>-Decay (ms) slider9:12<0,15,1>-Sustain (0-15) slider10:120<0,5000,1>-Release (ms) slider11:0<0,3,1{Off,Saw,Triangle,Sync}>-Buzz slider12:1<0,2,1{-1 oct,Unison,+1 oct}>-Buzz tune slider13:0<0,7,1{Off,Major,Minor,Octave,Fifth,Maj7,Min7,Held}>-Arp slider14:1<1,8,1>-Arp speed (frames) slider15:0<0,100,1>-Vibrato (cents) slider16:250<0,1000,1>-Vibrato delay (ms) slider17:6<1,12,0.1>-Vibrato rate (Hz) slider18:0<0,1000,1>-Glide (ms) slider19:0<-48,48,1>-Sweep (semitones) slider20:150<10,2000,1>-Sweep time (ms) slider21:4<0,7,1>-FM algorithm slider22:4<0,7,1>-FM feedback slider23:100<0,200,1>-FM mod (%) slider24:100<10,400,1>-FM mod decay (%) slider25:0<0,100,1>-Detune / phaser (%) slider26:50<0,100,1>-DAC ladder (%) slider27:60<0,100,1>-Lo-fi (%) slider28:40<0,100,1>-Stereo (%) slider29:30<0,100,1>-Velocity (%) slider30:0<-24,6,0.1>-Volume (dB) slider31:0<0,16,1>-MIDI channel (0 = omni) slider32:0<0,1,1{50 Hz PAL,60 Hz NTSC}>-Frame rate slider33:0<0,1,1{Off,On}>-Drums slider34:0<0,1,1{Authentic,6 voices}>-Channels slider35:0<0,100,1>-Band sat (%) slider36:30<0,100,1>-Bias (%) slider37:120<40,400,1>-Sat low band (Hz) slider38:6000<2000,16000,10>-Sat high band (Hz) slider39:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider40:1<0,2,1{Off,Clip,Limit}>-Output stage slider41:20<20,500,1>-Mono below (Hz, 20 = off) slider42:0<0,100,1>-Stereo width (%) in_pin:none out_pin:left output out_pin:right output @init #ui_man = "gfx-g-chip"; // the ? pop-up links to this manual NV = 6; NP = 41; VN = 50000; VG = 50010; VT = 50020; VS = 50030; // per voice: note (-1 free), gate, age, held by sustain HN = 50100; // held notes, in the order played (128) EQO = 50300; EQT = 50600; EQN = 50900; EQV = 51200; // note event queue (300 each) FMD = 52000; // FM operator data: 16 per patch AYV = 53000; // AY volume table (16) gi = 0; loop(NV, VN[gi] = -1; VG[gi] = 0; VT[gi] = 0; VS[gi] = 0; gi += 1; ); // AY chip output levels (logarithmic, about 3 dB a step) AYV[0] = 0; AYV[1] = 0.0137; AYV[2] = 0.0205; AYV[3] = 0.0291; AYV[4] = 0.0423; AYV[5] = 0.0618; AYV[6] = 0.0847; AYV[7] = 0.1369; AYV[8] = 0.1691; AYV[9] = 0.2647; AYV[10] = 0.3527; AYV[11] = 0.4499; AYV[12] = 0.5704; AYV[13] = 0.6873; AYV[14] = 0.8482; AYV[15] = 1; // ---------- the chips' own steps ---------- // pitch: each chip divides a fixed clock by a whole number, so notes land on its own grid function chip_freq(f) local(n) ( chip == 0 ? ( n = max(1, floor(3500000 / (32 * f) + 0.5)); 3500000 / (32 * n); ) : // Z80 loop, 16 T-state steps chip == 1 ? ( n = min(max(floor(1773400 / (16 * f) + 0.5), 1), 4095); 1773400 / (16 * n); ) : // AY tone period chip == 2 ? ( n = min(max(floor(3579545 / (32 * f) + 0.5), 1), 1023); 3579545 / (32 * n); ) : // PSG divider chip == 4 ? ( n = min(max(floor(1789773 / (tri ? 32 : 16) / f + 0.5) - 1, 8), 2047); 1789773 / ((tri ? 32 : 16) * (n + 1)); ) : f; // FM: fine enough ); // volume: 0..1 -> the chip's level steps function chip_vol(l) local(k) ( k = floor(min(max(l, 0), 1) * 15 + 0.5); chip == 1 ? AYV[k] : chip == 2 ? ( k ? 10 ^ (-(15 - k) * 2 / 20) : 0 ) : chip == 3 ? l : k / 15; ); function ftri(ph) local(s) ( s = floor(ph * 32); (s < 16 ? s : 31 - s) / 7.5 - 1; ); // 4-bit stepped triangle // ---------- one voice ---------- function vinit(p) instance(pan, cur, tgt, lfsr) ( pan = p; cur = 60; tgt = 60; lfsr = 1; ); function vstart(n, v) instance(note, cur, tgt, vel, est, elv, ftm, aph, swo, dr, dkind, dnote, mt, mrel, played) ( note = n; tgt = n; vel = v / 127; glide_c >= 1 || !played ? cur = n; dr = 0; drums ? ( dr = 1; n == 35 || n == 36 ? ( dkind = 0; dnote = 33; ) : n == 38 || n == 40 || n == 37 || n == 39 ? ( dkind = 1; dnote = 55; ) : n == 42 || n == 44 ? ( dkind = 2; dnote = 0; ) : n == 46 ? ( dkind = 3; dnote = 0; ) : n == 49 || n == 57 || n == 52 || n == 55 ? ( dkind = 4; dnote = 0; ) : n == 51 || n == 59 || n == 53 ? ( dkind = 5; dnote = 0; ) : ( dkind = 6; dnote = n; ); // toms (41-50 and the rest) cur = tgt = dkind == 6 ? n : dnote; ); est = 1; elv = dr || attack_f <= 0 ? 1 : 0; // attack 0: straight in ftm = 0; aph = 0; mt = 0; mrel = 1; swo = dr ? ( dkind == 0 ? 30 : dkind == 1 ? 12 : dkind == 6 ? 14 : 0 ) : sweep; played = 1; ); function vstop() instance(est) ( est > 0 && est < 4 ? est = 4; ); function vidle() instance(est) ( est == 0; ); function vglide(n) instance(tgt) ( tgt = n; ); // once a frame (50 / 60 Hz): envelope, arp, vibrato, sweep, glide -> pitch and level for the next frame function vframe() instance(note, cur, tgt, vel, est, elv, ftm, aph, swo, dr, dkind, inc, inc2, lvl, flv, nrate, bph, binc, mrel) local(a, d, s, r, semis, f, off, step, vb, ac, dt, l) ( est > 0 ? ( dr ? ( a = 0; d = dkind == 0 ? 0.18 : dkind == 1 ? 0.16 : dkind == 2 ? 0.05 : dkind == 3 ? 0.3 : dkind == 4 ? 0.9 : dkind == 5 ? 0.45 : 0.22; s = 0; r = d; ) : ( a = attack_f; d = decay_f; s = sus_l; r = release_f; ); // envelope (per frame) dt = frame_s; est == 1 ? ( elv = a <= 0 ? 1 : elv + dt / a; elv >= 1 ? ( elv = 1; est = 2; ); ) : est == 2 ? ( elv = d <= 0 ? s : elv - dt / d * (1 - s); elv <= s ? ( elv = s; est = 3; ); s <= 0 && elv <= 0 ? est = 0; ) : est == 3 ? ( elv = s; s <= 0 ? est = 0; ) : est == 4 ? ( l = max(s, 0.25); elv = r <= 0 ? 0 : elv - dt / r * l; elv <= 0 ? ( elv = 0; est = 0; ); mrel = elv / l; ); // arp: chord steps off = 0; !dr && arp > 0 && arp < 7 ? ( step = floor(ftm / arpspd); ac = arp == 1 ? 3 : arp == 2 ? 3 : arp == 3 ? 2 : arp == 4 ? 2 : 4; step %= ac; off = arp == 1 ? (step == 1 ? 4 : step == 2 ? 7 : 0) : arp == 2 ? (step == 1 ? 3 : step == 2 ? 7 : 0) : arp == 3 ? step * 12 : arp == 4 ? step * 7 : arp == 5 ? (step == 1 ? 4 : step == 2 ? 7 : step == 3 ? 11 : 0) : (step == 1 ? 3 : step == 2 ? 7 : step == 3 ? 10 : 0); ); // vibrato after its delay, stepped per frame vb = 0; !dr && ftm * frame_s >= vibdel ? ( aph += vibrate * frame_s; vb = sin(2 * $pi * aph) * vibd / 100; ); // sweep back to the note swo != 0 ? ( l = (dr ? (dkind == 0 ? 0.07 : 0.1) : sweept); swo -= sign(swo) * min(abs(swo), (dr ? abs(swo) : abs(sweep)) * frame_s / l + 0.0001); ); cur += (tgt - cur) * glide_c; semis = cur + off + vb + swo + vib_mw; f = 440 * 2 ^ ((semis - 69) / 12) * bend; tri = mixer == 3; f = chip_freq(f); inc = min(f / srate, 0.45); inc2 = min(f * (1 + detune * 0.006) / srate, 0.45); // AY buzz: the envelope period also snaps to the chip's grid binc = buzz ? min(f * (btune == 0 ? 0.5 : btune == 2 ? 2 : 1) / srate, 0.45) : 0; chip == 1 && buzz ? binc = 1773400 / (256 * max(1, floor(1773400 / (256 * binc * srate) + 0.5))) / srate; // noise rate: knob, or the drum's own nrate = dr ? ( dkind == 2 || dkind == 3 ? 110000 : dkind == 1 ? 26000 : dkind == 4 ? 60000 : dkind == 5 ? 85000 : dkind == 0 ? 6000 : 9000 ) : ( ntype && chip == 2 ? f * 16 : 110000 * 2 ^ (-(100 - npitch) / 100 * 9) ); l = elv * (1 - velsens + velsens * vel); flv = l; // FM: smooth lvl = chip == 0 ? l : chip_vol(l); // the chip's own level steps ftm += 1; ); ); // one sample of the voice function vrun() instance(est, dr, dkind, inc, inc2, lvl, flv, nrate, nph, lfsr, nz, o1, o2, ph3, bph, binc, p1, p2, p3, p4, fb1, fb2, mt, mrel, vel, ls, fs) local(y, t, nb, e, m1, m2, m3, m4, i1, k, a, w, ex) ( est == 0 ? ( ls = 0; fs = 0; 0; ) : ( ls += (lvl - ls) * lv_slew; fs += (flv - fs) * fm_slew; // noise: a shift register clocked at the noise rate nph += nrate / srate; while (nph >= 1) ( nph -= 1; chip == 4 ? ( nb = (lfsr ~ (lfsr >> (ntype ? 6 : 1))) & 1; lfsr = (lfsr >> 1) | (nb << 14); ) : chip == 2 && ntype && !dr ? ( lfsr = lfsr >> 1; lfsr == 0 ? lfsr = 32768; ) : // periodic: one pulse in 16 ( nb = (lfsr ~ (lfsr >> 3)) & 1; lfsr = (lfsr >> 1) | (nb << 16); ); nz = (lfsr & 1) * 2 - 1; ); dr ? ( // drums: tone + noise per kind chip == 3 ? ( ph3 += inc; ph3 -= floor(ph3); t = sin(2 * $pi * ph3); ) : ( t = o1.vo_pulse(inc, 0.5); ); y = dkind == 0 ? t * 0.9 + nz * 0.15 * ls : dkind == 1 ? t * 0.35 + nz * 0.7 : dkind == 6 ? t * 0.85 : nz * 0.6; y * ls; ) : chip == 3 ? ( // FM: four operators, eight algorithms, operator 1 feeds back on itself mt += 1 / srate; e = (fm_sl + (1 - fm_sl) * exp(-mt / fm_dk)) * mrel; ex = fmmod * e; a = 2 * $pi; w = (fb1 + fb2) * 0.5 * fbk; p1 += inc * fmp[0] * (1 - detune * 0.0004); p1 -= floor(p1); p2 += inc * fmp[1] * (1 + detune * 0.0004); p2 -= floor(p2); p3 += inc * fmp[2] * (1 - detune * 0.0002); p3 -= floor(p3); p4 += inc * fmp[3] * (1 + detune * 0.0002); p4 -= floor(p4); m1 = sin(a * p1 + w); fb2 = fb1; fb1 = m1; k = fmp[4] * ex * (exp(-mt / (fmp[8] * fmdec)) * (1 - fmp[12]) + fmp[12]); // op 1 as modulator algo == 0 ? ( m2 = sin(a * p2 + m1 * k * 6); m3 = sin(a * p3 + m2 * fmp[5] * ex * 6); y = sin(a * p4 + m3 * fmp[6] * ex * 6) * fmp[7]; ) : algo == 1 ? ( m2 = sin(a * p2); m3 = sin(a * p3 + (m1 * k + m2 * fmp[5] * ex) * 6); y = sin(a * p4 + m3 * fmp[6] * ex * 6) * fmp[7]; ) : algo == 2 ? ( m2 = sin(a * p2); m3 = sin(a * p3 + m2 * fmp[5] * ex * 6); y = sin(a * p4 + (m1 * k + m3 * fmp[6] * ex) * 6) * fmp[7]; ) : algo == 3 ? ( m2 = sin(a * p2 + m1 * k * 6); m3 = sin(a * p3); y = sin(a * p4 + (m2 * fmp[5] * ex + m3 * fmp[6] * ex) * 6) * fmp[7]; ) : algo == 4 ? ( m2 = sin(a * p2 + m1 * k * 6) * fmp[5]; m3 = sin(a * p3); y = (m2 + sin(a * p4 + m3 * fmp[6] * ex * 6) * fmp[7]) * 0.6; ) : algo == 5 ? ( y = (sin(a * p2 + m1 * k * 6) * fmp[5] + sin(a * p3 + m1 * k * 6) * fmp[6] + sin(a * p4 + m1 * k * 6) * fmp[7]) * 0.42; ) : algo == 6 ? ( y = (sin(a * p2 + m1 * k * 6) * fmp[5] + sin(a * p3) * fmp[6] + sin(a * p4) * fmp[7]) * 0.42; ) : ( y = (m1 * fmp[4] + sin(a * p2) * fmp[5] + sin(a * p3) * fmp[6] + sin(a * p4) * fmp[7]) * 0.32; ); y *= fs; // the early DAC: 9 bits, with a step at the zero crossing ladder > 0 ? ( y = floor(y * 256 + 0.5) / 256; y += (y >= 0 ? 1 : -1) * ladder * 0.006 * (fs > 0.001); ); y; ) : ( // the square-wave chips t = tri ? ftri(o1.ph += inc; o1.ph -= floor(o1.ph); o1.ph) : chip == 0 ? o1.vo_pulse(inc, max(duty * (0.15 + 0.85 * ls), 0.01)) : // beeper: loudness by pulse width chip == 4 ? o1.vo_pulse(inc, duty < 0.1875 ? 0.125 : duty < 0.375 ? 0.25 : 0.5) : o1.vo_pulse(inc, 0.5); chip == 0 && detune > 0 ? t *= o2.vo_pulse(inc2, 0.5); // the beeper phaser y = mixer == 0 || mixer == 3 ? t : mixer == 1 ? (t > 0 && nz > 0 ? 1 : -1) * 0.8 + t * 0.2 : nz; // AY buzzer: the envelope generator run at audio rate chip == 1 && buzz ? ( bph += binc; bph -= floor(bph); k = buzz == 2 ? ( bph < 0.5 ? floor(bph * 32) : 31 - floor(bph * 32) ) : 15 - floor(bph * 16); e = AYV[min(max(k, 0), 15)]; y = (buzz == 3 ? (t > 0) : 1) * e * 2 - (buzz == 3 ? 0.25 : 0.55); y * min(ls * 4, 1); ) : ( chip == 0 ? y * (ls > 0.001) : y * ls; ); ); ); ); // dispatch to a voice by number function v_start(i, n, v) ( // start, then work out its pitch and level straight away i == 0 ? ( v0.vstart(n, v); v0.vframe(); ) : i == 1 ? ( v1.vstart(n, v); v1.vframe(); ) : i == 2 ? ( v2.vstart(n, v); v2.vframe(); ) : i == 3 ? ( v3.vstart(n, v); v3.vframe(); ) : i == 4 ? ( v4.vstart(n, v); v4.vframe(); ) : ( v5.vstart(n, v); v5.vframe(); ); ); function v_stop(i) ( i == 0 ? v0.vstop() : i == 1 ? v1.vstop() : i == 2 ? v2.vstop() : i == 3 ? v3.vstop() : i == 4 ? v4.vstop() : v5.vstop(); ); function v_idle(i) ( i == 0 ? v0.vidle() : i == 1 ? v1.vidle() : i == 2 ? v2.vidle() : i == 3 ? v3.vidle() : i == 4 ? v4.vidle() : v5.vidle(); ); function v_glide(i, n) ( i == 0 ? v0.vglide(n) : i == 1 ? v1.vglide(n) : i == 2 ? v2.vglide(n) : i == 3 ? v3.vglide(n) : i == 4 ? v4.vglide(n) : v5.vglide(n); ); // ---------- notes ---------- function chans() ( slider34 || drums ? NV : chip == 0 ? 1 : chip == 3 ? 6 : chip == 4 ? (mixer == 3 ? 1 : 2) : 3; ); function all_off() local(i) ( i = 0; loop(NV, v_stop(i); VG[i] = 0; VS[i] = 0; VN[i] = -1; i += 1; ); hcount = 0; ); function hold_add(n) local(i, f) ( f = -1; i = 0; loop(hcount, HN[i] == n ? f = i; i += 1; ); f < 0 && hcount < 127 ? ( HN[hcount] = n; hcount += 1; ); ); function hold_del(n) local(i, f) ( f = -1; i = 0; loop(hcount, HN[i] == n ? f = i; i += 1; ); f >= 0 ? ( i = f; loop(hcount - f - 1, HN[i] = HN[i + 1]; i += 1; ); hcount -= 1; ); ); function poly_on(n, v) local(i, best, ba, nv) ( nv = chans(); best = -1; i = 0; loop(nv, VN[i] == n ? best = i; i += 1; ); best < 0 ? ( ba = -1; i = 0; loop(nv, v_idle(i) && (best < 0 || VT[i] < ba) ? ( best = i; ba = VT[i]; ); i += 1; ); ); best < 0 ? ( ba = -1; i = 0; loop(nv, !VG[i] && (best < 0 || VT[i] < ba) ? ( best = i; ba = VT[i]; ); i += 1; ); ); best < 0 ? ( ba = -1; i = 0; loop(nv, best < 0 || VT[i] < ba ? ( best = i; ba = VT[i]; ); i += 1; ); ); vage += 1; VT[best] = vage; VN[best] = n; VG[best] = 1; VS[best] = 0; v_start(best, n, v); ); function poly_off(n) local(i) ( i = 0; loop(NV, VN[i] == n && VG[i] ? ( pedal ? VS[i] = 1 : ( v_stop(i); VG[i] = 0; ); ); i += 1; ); ); // HELD arp: every held note in turn on voice 1 function held_on(n, v) ( hold_add(n); hvel = v; hcount == 1 ? ( hstep = 0; hfr = 0; v0.vstart(n, v); v0.vframe(); VN[0] = n; VG[0] = 1; ); ); function held_off(n) ( hold_del(n); hcount == 0 ? ( pedal ? VS[0] = 1 : ( v0.vstop(); VG[0] = 0; ); ); ); function note_on(n, v) ( ins_midi_hit(); arp == 7 && !drums ? held_on(n, v) : poly_on(n, v); ); function note_off(n) ( arp == 7 && !drums ? held_off(n) : poly_off(n); ); function pedal_up() local(i) ( i = 0; loop(NV, VS[i] ? ( v_stop(i); VG[i] = 0; VS[i] = 0; ); i += 1; ); ); function q_push(ofs, t, n, v) local(i) ( qcount < 300 ? ( i = qcount; while (i > 0 && EQO[i - 1] > ofs) ( EQO[i] = EQO[i - 1]; EQT[i] = EQT[i - 1]; EQN[i] = EQN[i - 1]; EQV[i] = EQV[i - 1]; i -= 1; ); EQO[i] = ofs; EQT[i] = t; EQN[i] = n; EQV[i] = v; qcount += 1; ); ); // ---------- names ---------- function chip_name(c) ( c == 0 ? "BEEPER" : c == 1 ? "AY" : c == 2 ? "PSG" : c == 3 ? "FM" : "PULSE"; ); function patch_name(p) ( p == 0 ? "48K LEAD" : p == 1 ? "THIN PULSE" : p == 2 ? "PHASER LEAD" : p == 3 ? "BEEPER BASS" : p == 4 ? "CHORD ARP" : p == 5 ? "LASER ZAP" : p == 6 ? "BEEPER DRUMS" : p == 7 ? "SOFT HUM" : p == 8 ? "AY LEAD" : p == 9 ? "BUZZER BASS" : p == 10 ? "BUZZ TRI BASS" : p == 11 ? "SYNC LEAD" : p == 12 ? "ARP CHORDS" : p == 13 ? "SOFT BELL" : p == 14 ? "STEAM LEAD" : p == 15 ? "AY DRUMS" : p == 16 ? "PSG LEAD" : p == 17 ? "PSG BASS" : p == 18 ? "PERIODIC BASS" : p == 19 ? "MAJOR ARP" : p == 20 ? "MINOR ARP" : p == 21 ? "PSG BELL" : p == 22 ? "VIB SQUARE" : p == 23 ? "PSG DRUMS" : p == 24 ? "16-BIT BASS" : p == 25 ? "FM BRASS" : p == 26 ? "FM E.PIANO" : p == 27 ? "FM LEAD" : p == 28 ? "FM STRINGS" : p == 29 ? "FM MARIMBA" : p == 30 ? "FM ORGAN" : p == 31 ? "FM DRUMS" : p == 32 ? "PULSE 12.5" : p == 33 ? "PULSE 25 LEAD" : p == 34 ? "SQUARE 50" : p == 35 ? "TRIANGLE BASS" : p == 36 ? "ECHO ARP" : p == 37 ? "VIBRATO LEAD" : p == 38 ? "SLIDE LEAD" : p == 39 ? "8-BIT DRUMS" : "INIT"; ); function mix_name(m) ( m == 0 ? "TONE" : m == 1 ? "T+N" : m == 2 ? "NOISE" : "TRI"; ); function buzz_name(b) ( b == 0 ? "OFF" : b == 1 ? "SAW" : b == 2 ? "TRI" : "SYNC"; ); function arp_name(a) ( a == 0 ? "OFF" : a == 1 ? "MAJ" : a == 2 ? "MIN" : a == 3 ? "OCT" : a == 4 ? "5TH" : a == 5 ? "MAJ7" : a == 6 ? "MIN7" : "HELD"; ); // ---------- FM operator data: ratios 1-4, levels 1-4 (op 4 always a carrier), mod decay (s) 1-4, mod sustain 1-4 ---------- function fmset(p, r1, r2, r3, r4, l1, l2, l3, l4, d1, d2, d3, d4, s1, s2, s3, s4) local(b) ( b = FMD + (p - 24) * 16; b[0] = r1; b[1] = r2; b[2] = r3; b[3] = r4; b[4] = l1; b[5] = l2; b[6] = l3; b[7] = l4; b[8] = d1; b[9] = d2; b[10] = d3; b[11] = d4; b[12] = s1; b[13] = s2; b[14] = s3; b[15] = s4; ); // patch ratios levels decays (s) sustains fmset(24, 1, 1, 3, 1, 0.9, 1, 0.5, 0.75, 0.15, 1, 0.08, 1, 0.2, 1, 0, 1); // 16-bit bass fmset(25, 1, 1, 1, 1, 0.6, 0.9, 0.45, 0.8, 0.6, 1, 0.5, 1, 0.6, 1, 0.5, 1); // brass fmset(26, 14, 1, 1, 1, 0.3, 0.8, 0.5, 0.9, 0.12, 1, 0.9, 1, 0, 1, 0.1, 1); // e.piano fmset(27, 1, 1, 2, 1.005, 0.7, 0.9, 0.6, 0.8, 0.8, 1, 1, 1, 0.8, 1, 1, 1); // lead fmset(28, 1, 1, 2, 1, 0.35, 0.8, 0.3, 0.8, 2, 1, 2, 1, 0.4, 1, 0.4, 1); // strings fmset(29, 4, 1, 10, 1, 0.6, 0.9, 0.3, 0.9, 0.06, 1, 0.03, 1, 0, 1, 0, 1); // marimba fmset(30, 0.5, 1, 2, 3, 0.7, 1, 0.55, 0.4, 1, 1, 1, 1, 1, 1, 1, 1); // organ (all carriers) fmset(31, 1, 1, 1, 1, 0.5, 1, 0.3, 1, 0.05, 1, 0.05, 1, 0, 1, 0, 1); // drums // the machine's own output: speaker / filters function machine_set() ( chip == 0 ? ( mf_h = 280; mf_l = 4200; mf_q = 1.1; ) : // the little speaker chip == 1 ? ( mf_h = 40; mf_l = 9000; mf_q = 0.7; ) : chip == 2 ? ( mf_h = 40; mf_l = 11000; mf_q = 0.7; ) : chip == 3 ? ( mf_h = 30; mf_l = 3400; mf_q = 0.6; ) : // the first console model's output filter ( mf_h = 90; mf_l = 14000; mf_q = 0.7; ); // 90 Hz / 14 kHz mhL.am_bq(1, mf_h, 0.7, 0, srate); mhR.am_bq(1, mf_h, 0.7, 0, srate); mlL.am_bq(0, mf_l, mf_q, 0, srate); mlR.am_bq(0, mf_l, mf_q, 0, srate); ); // ---------- parameters ---------- function update() ( chip = slider1; duty = slider3 / 100; mixer = slider4; npitch = slider5; ntype = slider6; attack_f = slider7 / 1000; decay_f = slider8 / 1000; sus_l = slider9 / 15; release_f = slider10 / 1000; buzz = slider11; btune = slider12; arp = slider13; arpspd = slider14; vibd = slider15; vibdel = slider16 / 1000; vibrate = slider17; frate = slider32 ? 60 : 50; frame_s = 1 / frate; fsamp = srate / frate; glide_c = slider18 > 0 ? min(1, 1 - exp(-4.6 / (slider18 / 1000 * frate))) : 1; sweep = slider19; sweept = slider20 / 1000; algo = slider21; fbk = slider22 > 0 ? $pi * 2 ^ (slider22 - 7) * 2 : 0; fmmod = slider23 / 100; fmdec = slider24 / 100; detune = slider25; ladder = slider26 / 100; fmp = FMD + fm_patch * 16; fm_sl = fmp[12]; fm_dk = max(fmp[8] * fmdec, 0.005); lofi = slider27 / 100; stereo = slider28 / 100; velsens = slider29 / 100; vol_t = 10 ^ (slider30 / 20); chan = slider31; drums = slider33; ins_output_setup(35); ins_stereo_setup(41); chip != last_chip ? ( machine_set(); last_chip = chip; ); ); // ---------- patches (volume, channel, stereo and output are left alone) ---------- function pt(c, du, mx, np, nt, bz, bt) ( slider1 = c; slider3 = du; slider4 = mx; slider5 = np; slider6 = nt; slider11 = bz; slider12 = bt; ); function pe(a, d, s, r) ( slider7 = a; slider8 = d; slider9 = s; slider10 = r; ); function pm(ar, asp, vb, vd, vr, gl, sw, swt) ( slider13 = ar; slider14 = asp; slider15 = vb; slider16 = vd; slider17 = vr; slider18 = gl; slider19 = sw; slider20 = swt; ); function pf(al, fb, md, mdc, dt, ld) ( slider21 = al; slider22 = fb; slider23 = md; slider24 = mdc; slider25 = dt; slider26 = ld; ); function load_patch(p) ( pf(4, 4, 100, 100, 0, 50); slider33 = 0; p == 0 ? ( pt(0, 50, 0, 70, 0, 0, 1); pe(0, 300, 12, 80); pm(0, 1, 15, 300, 6, 0, 0, 150); ) : p == 1 ? ( pt(0, 10, 0, 70, 0, 0, 1); pe(0, 500, 14, 80); pm(0, 1, 10, 250, 6, 0, 0, 150); ) : p == 2 ? ( pt(0, 50, 0, 70, 0, 0, 1); pe(0, 600, 13, 120); pm(0, 1, 0, 250, 6, 0, 0, 150); slider25 = 30; ) : p == 3 ? ( pt(0, 30, 0, 70, 0, 0, 1); pe(0, 200, 10, 60); pm(0, 1, 0, 250, 6, 40, 0, 150); ) : p == 4 ? ( pt(0, 40, 0, 70, 0, 0, 1); pe(0, 400, 12, 100); pm(7, 1, 0, 250, 6, 0, 0, 150); ) : p == 5 ? ( pt(0, 50, 0, 70, 0, 0, 1); pe(0, 250, 0, 50); pm(0, 1, 0, 250, 6, 0, 36, 200); ) : p == 6 ? ( pt(0, 50, 0, 70, 0, 0, 1); pe(0, 300, 0, 80); pm(0, 1, 0, 250, 6, 0, 0, 150); slider33 = 1; ) : p == 7 ? ( pt(0, 25, 0, 70, 0, 0, 1); pe(200, 800, 10, 400); pm(0, 1, 12, 400, 4, 0, 0, 150); ) : p == 8 ? ( pt(1, 50, 0, 70, 0, 0, 1); pe(0, 400, 11, 150); pm(0, 1, 20, 250, 6, 0, 0, 150); ) : p == 9 ? ( pt(1, 50, 0, 70, 0, 1, 1); pe(0, 300, 13, 80); pm(0, 1, 0, 250, 6, 0, 0, 150); ) : p == 10 ? ( pt(1, 50, 0, 70, 0, 2, 1); pe(0, 300, 13, 80); pm(0, 1, 0, 250, 6, 0, 0, 150); ) : p == 11 ? ( pt(1, 50, 0, 70, 0, 3, 2); pe(0, 500, 12, 150); pm(0, 1, 15, 300, 6, 0, 0, 150); ) : p == 12 ? ( pt(1, 50, 0, 70, 0, 0, 1); pe(0, 600, 11, 200); pm(7, 1, 0, 250, 6, 0, 0, 150); ) : p == 13 ? ( pt(1, 50, 0, 70, 0, 0, 1); pe(0, 700, 0, 400); pm(0, 1, 6, 150, 5, 0, 0, 150); ) : p == 14 ? ( pt(1, 50, 1, 85, 0, 0, 1); pe(0, 500, 11, 200); pm(0, 1, 20, 200, 6, 0, 0, 150); ) : p == 15 ? ( pt(1, 50, 0, 70, 0, 0, 1); pe(0, 300, 0, 80); pm(0, 1, 0, 250, 6, 0, 0, 150); slider33 = 1; ) : p == 16 ? ( pt(2, 50, 0, 70, 0, 0, 1); pe(0, 400, 13, 120); pm(0, 1, 15, 200, 6, 0, 0, 150); ) : p == 17 ? ( pt(2, 50, 0, 70, 0, 0, 1); pe(0, 150, 10, 60); pm(0, 1, 0, 250, 6, 0, 0, 150); ) : p == 18 ? ( pt(2, 50, 2, 70, 1, 0, 1); pe(0, 300, 12, 80); pm(0, 1, 0, 250, 6, 0, 0, 150); ) : p == 19 ? ( pt(2, 50, 0, 70, 0, 0, 1); pe(0, 500, 11, 150); pm(1, 2, 0, 250, 6, 0, 0, 150); ) : p == 20 ? ( pt(2, 50, 0, 70, 0, 0, 1); pe(0, 500, 11, 150); pm(2, 2, 0, 250, 6, 0, 0, 150); ) : p == 21 ? ( pt(2, 50, 0, 70, 0, 0, 1); pe(0, 450, 0, 300); pm(0, 1, 0, 250, 6, 0, 0, 150); ) : p == 22 ? ( pt(2, 50, 0, 70, 0, 0, 1); pe(0, 400, 13, 150); pm(0, 1, 40, 0, 7, 0, 0, 150); ) : p == 23 ? ( pt(2, 50, 0, 70, 0, 0, 1); pe(0, 300, 0, 80); pm(0, 1, 0, 250, 6, 0, 0, 150); slider33 = 1; ) : p == 24 ? ( pt(3, 50, 0, 70, 0, 0, 1); pe(0, 600, 9, 80); pm(0, 1, 0, 250, 6, 0, 0, 150); pf(4, 5, 100, 100, 0, 60); ) : p == 25 ? ( pt(3, 50, 0, 70, 0, 0, 1); pe(60, 500, 12, 200); pm(0, 1, 10, 400, 5, 0, 0, 150); pf(4, 4, 100, 100, 10, 50); ) : p == 26 ? ( pt(3, 50, 0, 70, 0, 0, 1); pe(0, 1800, 0, 400); pm(0, 1, 0, 250, 6, 0, 0, 150); pf(4, 0, 100, 100, 15, 40); ) : p == 27 ? ( pt(3, 50, 0, 70, 0, 0, 1); pe(0, 500, 13, 150); pm(0, 1, 18, 300, 6, 0, 0, 150); pf(5, 6, 90, 100, 10, 50); ) : p == 28 ? ( pt(3, 50, 0, 70, 0, 0, 1); pe(300, 800, 12, 600); pm(0, 1, 8, 500, 5, 0, 0, 150); pf(4, 3, 100, 100, 50, 40); ) : p == 29 ? ( pt(3, 50, 0, 70, 0, 0, 1); pe(0, 400, 0, 200); pm(0, 1, 0, 250, 6, 0, 0, 150); pf(4, 0, 100, 100, 0, 50); ) : p == 30 ? ( pt(3, 50, 0, 70, 0, 0, 1); pe(5, 300, 15, 60); pm(0, 1, 6, 300, 6, 0, 0, 150); pf(7, 2, 100, 100, 5, 40); ) : p == 31 ? ( pt(3, 50, 0, 70, 0, 0, 1); pe(0, 300, 0, 80); pm(0, 1, 0, 250, 6, 0, 0, 150); slider33 = 1; ) : p == 32 ? ( pt(4, 12, 0, 70, 0, 0, 1); pe(0, 400, 12, 100); pm(0, 1, 10, 250, 6, 0, 0, 150); ) : p == 33 ? ( pt(4, 25, 0, 70, 0, 0, 1); pe(0, 400, 12, 100); pm(0, 1, 15, 250, 6, 0, 0, 150); ) : p == 34 ? ( pt(4, 50, 0, 70, 0, 0, 1); pe(0, 400, 12, 100); pm(0, 1, 0, 250, 6, 0, 0, 150); ) : p == 35 ? ( pt(4, 50, 3, 70, 0, 0, 1); pe(0, 200, 15, 40); pm(0, 1, 0, 250, 6, 0, 0, 150); ) : p == 36 ? ( pt(4, 25, 0, 70, 0, 0, 1); pe(0, 500, 10, 150); pm(1, 1, 0, 250, 6, 0, 0, 150); ) : p == 37 ? ( pt(4, 25, 0, 70, 0, 0, 1); pe(0, 500, 12, 150); pm(0, 1, 30, 300, 6, 0, 0, 150); ) : p == 38 ? ( pt(4, 50, 0, 70, 0, 0, 1); pe(0, 500, 12, 150); pm(0, 1, 10, 300, 6, 80, 0, 150); ) : p == 39 ? ( pt(4, 50, 0, 70, 0, 0, 1); pe(0, 300, 0, 80); pm(0, 1, 0, 250, 6, 0, 0, 150); slider33 = 1; ) : ( pt(0, 50, 0, 70, 0, 0, 1); pe(0, 400, 12, 120); pm(0, 1, 0, 250, 6, 0, 0, 150); ); p >= 24 && p <= 31 ? fm_patch = p - 24; slider2 = p; last_patch = p; slider_automate(8589934591); // sliders 1-33 panic_req = 1; update(); ); function chip_first(c) ( c == 0 ? 0 : c == 1 ? 8 : c == 2 ? 16 : c == 3 ? 24 : 32; ); v0.vinit(-0.6); v1.vinit(0); v2.vinit(0.6); v3.vinit(-0.3); v4.vinit(0.3); v5.vinit(0); ins_drift_init(); ost_init(20000); ostL.ost_ch_init(); ostR.ost_ch_init(); dcL.am_bq(1, 15, 0.7, 0, srate); dcR.am_bq(1, 15, 0.7, 0, srate); sm = 1 - exp(-1 / (0.03 * srate)); lv_slew = 1 - exp(-1 / (0.0015 * srate)); fm_slew = 1 - exp(-1 / (0.004 * srate)); bend = 1; modw = 0; pedal = 0; hcount = 0; vage = 0; qcount = 0; last_patch = -1; last_chip = -1; fm_patch = 0; fcnt = 0; // per-patch level trim (set by ear and meter: each patch plays a line at about the same loudness) function patch_trim(p) ( p == 0 ? 0.305 : p == 1 ? 0.417 : p == 2 ? 0.282 : p == 3 ? 0.351 : p == 4 ? 0.313 : p == 5 ? 0.351 : p == 6 ? 0.676 : p == 7 ? 0.339 : p == 8 ? 0.422 : p == 9 ? 0.398 : p == 10 ? 0.422 : p == 11 ? 0.432 : p == 12 ? 0.389 : p == 13 ? 0.556 : p == 14 ? 0.490 : p == 15 ? 0.989 : p == 16 ? 0.389 : p == 17 ? 0.668 : p == 18 ? 0.569 : p == 19 ? 0.422 : p == 20 ? 0.427 : p == 21 ? 0.733 : p == 22 ? 0.389 : p == 23 ? 1.047 : p == 24 ? 0.519 : p == 25 ? 0.759 : p == 26 ? 0.525 : p == 27 ? 0.767 : p == 28 ? 0.646 : p == 29 ? 0.661 : p == 30 ? 0.902 : p == 31 ? 1.059 : p == 32 ? 0.417 : p == 33 ? 0.339 : p == 34 ? 0.299 : p == 35 ? 0.507 : p == 36 ? 0.339 : p == 37 ? 0.327 : p == 38 ? 0.288 : p == 39 ? 0.700 : 0.305; ); !ui_dflt ? ui_thm = 5; !ui_dflt ? ui_hw = 0; !ui_dflt ? ui_kstyle = 0; update(); vol_s = vol_t; ui_dflt = 1; // theme defaults above apply once: @init re-runs (playback start, device reset) must not undo the saved theme @slider update(); @block update(); last_patch < 0 ? last_patch = slider2 : slider2 != last_patch ? ( load_patch(slider2); last_patch = slider2; ); // switching between held-arp and normal play while notes sound: clear them (their note-offs would go astray) chm_ = arp == 7 && !drums; chm_ != last_chm ? ( all_off(); last_chm = chm_; ); qcount = 0; while (midirecv(mofs, m1, m2, m3)) ( ins_chan_ok(m1, chan) ? ( st = m1 & 0xF0; st == 0x90 || st == 0x80 ? q_push(mofs, st == 0x90 && m3 > 0, m2, m3) : st == 0xE0 ? bend = ins_bend(m2, m3, 2) : st == 0xB0 ? ( m2 == 1 ? modw = m3 / 127 : m2 == 64 ? ( pedal = m3 >= 64; !pedal ? q_push(mofs, 2, 0, 0); ) : m2 == 123 || m2 == 120 ? q_push(mofs, 3, 0, 0); ); ); ); panic_req ? ( all_off(); panic_req = 0; ); eqi = 0; spos = 0; @serialize file_var(0, ui_sz); file_var(0, fm_patch); @sample while (eqi < qcount && EQO[eqi] <= spos) ( et = EQT[eqi]; et == 1 ? note_on(EQN[eqi], EQV[eqi]) : et == 0 ? note_off(EQN[eqi]) : et == 2 ? pedal_up() : all_off(); eqi += 1; ); spos += 1; // the frame tick: 50 or 60 times a second fcnt -= 1; fcnt <= 0 ? ( fcnt += fsamp; // HELD arp: step through the held notes arp == 7 && !drums && hcount > 0 ? ( hfr += 1; hfr >= arpspd ? ( hfr = 0; hstep = (hstep + 1) % hcount; v0.vglide(HN[hstep]); v0.cur = HN[hstep]; ); ); vib_mw = modw * 0.5 * sin(2 * $pi * mwph); mwph += 6 * frame_s; v0.vframe(); v1.vframe(); v2.vframe(); v3.vframe(); v4.vframe(); v5.vframe(); ); y0 = v0.vrun(); y1 = v1.vrun(); y2 = v2.vrun(); y3 = v3.vrun(); y4 = v4.vrun(); y5 = v5.vrun(); oL = y0 * (1 - max(v0.pan * stereo, 0)) + y1 * (1 - max(v1.pan * stereo, 0)) + y2 * (1 - max(v2.pan * stereo, 0)) + y3 * (1 - max(v3.pan * stereo, 0)) + y4 * (1 - max(v4.pan * stereo, 0)) + y5 * (1 - max(v5.pan * stereo, 0)); oR = y0 * (1 + min(v0.pan * stereo, 0)) + y1 * (1 + min(v1.pan * stereo, 0)) + y2 * (1 + min(v2.pan * stereo, 0)) + y3 * (1 + min(v3.pan * stereo, 0)) + y4 * (1 + min(v4.pan * stereo, 0)) + y5 * (1 + min(v5.pan * stereo, 0)); // the machine's output stage, blended in by LO-FI fL = mlL.am_run(mhL.am_run(oL)); fR = mlR.am_run(mhR.am_run(oR)); oL += (fL - oL) * lofi; oR += (fR - oR) * lofi; oL = dcL.am_run(oL); oR = dcR.am_run(oR); vol_s += (vol_t - vol_s) * sm; ptr_s = patch_trim(slider2); oL = ostL.ost_band(oL * 0.3 * ptr_s) * vol_s; oR = ostR.ost_band(oR * 0.3 * ptr_s) * vol_s; ins_stereo(oL, oR); ost_out(ins_sl, ins_sr); spl0 = ost_l; spl1 = ost_r; @gfx 1140 414 ui_begin(1140, 414); // ---------- the rack module: black faceplate, rack ears, a ribbed top strip, the rainbow ---------- ZX_BLK = 0x161618; ZX_KEY = 0x9CA1A9; ZX_RED = 0xFF4A4A; ZX_WHT = 0xE8E8E8; fh = 406; ui_col(0x0A0A0B); gfx_rect(0, 0, gfx_w, gfx_h); function zx_ear(x) ( ui_col(0x2A2B2E); gfx_rect(x * sc, 4 * sc, 34 * sc, fh * sc); gfx_gradrect(x * sc, 4 * sc, 34 * sc, fh * sc, 1, 1, 1, 0.05, 0, 0, 0, 0, 0, 0, 0, 0); ui_col(0x0A0A0B); ui_rrect((x + 9) * sc, 26 * sc, 16 * sc, 26 * sc, 7 * sc); ui_rrect((x + 9) * sc, (fh - 44) * sc, 16 * sc, 26 * sc, 7 * sc); pk_screw((x + 17) * sc, 39 * sc); pk_screw((x + 17) * sc, (fh - 31) * sc); ); zx_ear(2); zx_ear(1104); ui_col(ZX_BLK); gfx_rect(36 * sc, 4 * sc, 1068 * sc, fh * sc); gi = 0; loop(9, ui_cola(gi % 2 ? 0xFFFFFF : 0x000000, gi % 2 ? 0.03 : 0.3); gfx_rect(36 * sc, (6 + gi * 3) * sc, 1068 * sc, max(1, 1.5 * sc)); gi += 1; ); ui_cola(0x000000, 0.5); gfx_rect(36 * sc, 150 * sc, 1068 * sc, 1 * sc); ui_cola(0xFFFFFF, 0.06); gfx_rect(36 * sc, 151 * sc, 1068 * sc, 1 * sc); // a rubber key: grey face, white legend, the key's word in small type; on = held down function zx_face(x, y, w, h, down, ch, val, vcol) local(d, tw, th) ( d = down ? 2 : 0; ui_cola(0x000000, 0.55); ui_rrect((x + 1) * sc, (y + 3) * sc, w * sc, h * sc, 5 * sc); ui_col(0x5C6067); ui_rrect(x * sc, (y + d) * sc, w * sc, (h - d) * sc, 5 * sc); ui_col(down ? 0x8B9097 : ZX_KEY); ui_rrect(x * sc, (y + d) * sc, w * sc, (h - 4) * sc, 5 * sc); gfx_gradrect((x + 2) * sc, (y + d + 1) * sc, (w - 4) * sc, 10 * sc, 1, 1, 1, 0.16, 0, 0, 0, 0, 0, 0, 0, -0.16 / (10 * sc)); gfx_setfont(7, "Arial", 15 * sc, 'b'); ui_col(0xFFFFFF); gfx_x = (x + 6) * sc; gfx_y = (y + d + 3) * sc; gfx_drawstr(ch); gfx_setfont(8, "Arial", 10 * sc, 'b'); ui_col(vcol); gfx_measurestr(val, tw, th); gfx_x = (x + w - 6) * sc - tw; gfx_y = (y + d + h - 19) * sc; gfx_drawstr(val); ); function zx_key(x, y, id, ch, val, above, acol, on) local(hov, w, h) ( w = 66; h = 40; hov = mouse_x >= x * sc && mouse_x < (x + w) * sc && mouse_y >= y * sc && mouse_y < (y + h) * sc; pressed && hov && drag_id == 0 ? drag_id = 900 + id; gfx_setfont(8, "Arial", 10 * sc, 'b'); ui_col(acol); ui_text((x + w * 0.5) * sc, (y - 13) * sc, above); zx_face(x, y, w, h, on || drag_id == 900 + id, ch, val, on ? 0xFFF27A : ZX_WHT); released_id == 900 + id && hov; ); // a section: box with its title in an 8-bit micro colour function zx_sec(x, y, w, h, title, col) ( ui_col(0x1D1D20); gfx_rect(x * sc, y * sc, w * sc, h * sc); ui_cola(col, 0.22); gfx_rect(x * sc, y * sc, w * sc, 20 * sc); // the title strip ui_cola(col, 0.9); gfx_rect(x * sc, y * sc, w * sc, max(1, 2 * sc)); ui_cola(0xFFFFFF, 0.08); gfx_rect(x * sc, (y + h - 1) * sc, w * sc, max(1, sc)); gfx_setfont(8, "Arial", 11 * sc, 'b'); ui_col(col); gfx_x = (x + 8) * sc; gfx_y = (y + 5) * sc; gfx_drawstr(title); ui_section(2); C_INK = ZX_WHT; ); function zx_pal(k) ( k == 0 ? 0xFF4A4A : k == 1 ? 0xFF6AFF : k == 2 ? 0x4ADF6A : k == 3 ? 0x3AE6E6 : k == 4 ? 0xF2E04A : k == 5 ? 0x6E8EFF : 0xE8E8E8; ); // ---------- top row: name + utilities | the screen | the chip and patch keys ---------- gfx_setfont(6, "Arial Black", 34 * sc, 'i'); ui_cola(0x000000, 0.7); gfx_x = 53 * sc; gfx_y = 23 * sc; gfx_drawstr("g-chip"); ui_col(0xCDD0D6); gfx_x = 52 * sc; gfx_y = 22 * sc; gfx_drawstr("g-chip"); gfx_setfont(6, "Arial", 13 * sc, 'b'); ui_col(ZX_WHT); gfx_x = 54 * sc; gfx_y = 68 * sc; gfx_drawstr("48 CHIPTUNE SOUND MODULE"); gi = 0; // the rainbow loop(4, ui_col(gi == 0 ? 0xD7272B : gi == 1 ? 0xF2C50F : gi == 2 ? 0x2DA84A : 0x1E9BD7); zx_rx = 232 + gi * 11; gfx_triangle(zx_rx * sc, 64 * sc, (zx_rx + 11) * sc, 64 * sc, (zx_rx + 11 + 26) * sc, 38 * sc, (zx_rx + 26) * sc, 38 * sc); gi += 1; ); sprintf(#zv, "%.0f", slider31); zx_key(52, 102, 31, "CH", slider31 == 0 ? "OMNI" : #zv, "MIDI", ZX_WHT, 0) ? ( slider31 = (slider31 + ((mouse_cap & 8) ? 16 : 1)) % 17; slider_automate(2 ^ 30); ); zx_key(126, 102, 32, "TV", slider32 ? "NTSC" : "PAL", slider32 ? "60 Hz" : "50 Hz", ZX_WHT, 0) ? ( slider32 = 1 - slider32; slider_automate(2 ^ 31); ); pk_led(208 * sc, 108 * sc, 3 * sc, ins_midi_lit(), 0x3ADF6A); gfx_setfont(4); ui_col(ZX_WHT); gfx_x = 216 * sc; gfx_y = 103 * sc; gfx_drawstr("MIDI IN"); ui_size_picker(204 * sc, 118 * sc); // the screen: the border takes the chip's colour and runs loading stripes while notes play scx = 350; scy = 18; scw = 350; sch = 122; bcol = chip == 0 ? 0x0000D7 : chip == 1 ? 0xD70000 : chip == 2 ? 0xD700D7 : chip == 3 ? 0x00B000 : 0x00C0C0; gn = 0; gi = 0; loop(NV, !v_idle(gi) ? gn += 1; gi += 1; ); ui_col(0x000000); gfx_rect((scx - 3) * sc, (scy - 3) * sc, (scw + 6) * sc, (sch + 6) * sc); ui_col(bcol); gfx_rect(scx * sc, scy * sc, scw * sc, sch * sc); gn > 0 ? ( zx_ld += 1; gi = 0; loop(floor(sch / 3), ((gi * 7 + zx_ld * 5) % 11) < 5 ? ( ui_col(chip == 0 ? 0xD7D700 : 0x00D7D7); gfx_rect(scx * sc, (scy + gi * 3) * sc, scw * sc, 3 * sc); ); gi += 1; ); ); ui_col(0xD7D7D7); gfx_rect((scx + 14) * sc, (scy + 12) * sc, (scw - 28) * sc, (sch - 24) * sc); gfx_setfont(6, "Courier New", 17 * sc, 'b'); ui_col(0x000000); sprintf(#l1, "P%02d %s", slider2 + 1, patch_name(slider2)); gfx_x = (scx + 24) * sc; gfx_y = (scy + 20) * sc; gfx_drawstr(#l1); gfx_setfont(8, "Courier New", 13 * sc, 'b'); sprintf(#l2, "%s %d/%d VOICES", chip_name(chip), gn, chans()); gfx_x = (scx + 24) * sc; gfx_y = (scy + 50) * sc; gfx_drawstr(#l2); sprintf(#l3, "%s %s%s", slider32 ? "NTSC" : "PAL", arp == 7 ? "HELD ARP" : arp > 0 ? "ARP" : "", drums ? " DRUMS" : ""); gfx_x = (scx + 24) * sc; gfx_y = (scy + 72) * sc; gfx_drawstr(#l3); gfx_setfont(4); ui_col(0x404040); gfx_x = (scx + scw - 116) * sc; gfx_y = (scy + 92) * sc; gfx_drawstr("CLICK FOR LIST"); pressed && drag_id == 0 && mouse_x >= scx * sc && mouse_x < (scx + scw) * sc && mouse_y >= scy * sc && mouse_y < (scy + sch) * sc ? ( drag_id = -1; gfx_x = scx * sc; gfx_y = (scy + sch) * sc; gsel = gfx_showmenu(">BEEPER (48K)|48K Lead|Thin Pulse|Phaser Lead|Beeper Bass|Chord Arp|Laser Zap|Beeper Drums|AY (128K)|AY Lead|Buzzer Bass|Buzz Tri Bass|Sync Lead|Arp Chords|Soft Bell|Steam Lead|PSG|PSG Lead|PSG Bass|Periodic Bass|Major Arp|Minor Arp|PSG Bell|Vib Square|FM|16-Bit Bass|FM Brass|FM E.Piano|FM Lead|FM Strings|FM Marimba|FM Organ|PULSE|Pulse 12.5|Pulse 25 Lead|Square 50|Triangle Bass|Echo Arp|Vibrato Lead|Slide Lead|<8-Bit Drums|Init"); gsel > 0 ? load_patch(gsel - 1); ); // the number-row keys: chips 1-5, then patch down / up, drums, voices, panic gi = 0; loop(5, sprintf(#zc, "%d", gi + 1); zx_key(730 + gi * 74, 32, gi + 1, #zc, chip_name(gi), gi == 0 ? "48K" : gi == 1 ? "128K" : gi == 2 ? "3 SQUARE" : gi == 3 ? "4-OP" : "2 PULSE", gi == 0 ? 0x6E8EFF : gi == 1 ? 0xFF4A4A : gi == 2 ? 0xFF6AFF : gi == 3 ? 0x4ADF6A : 0x3AE6E6, chip == gi) ? load_patch(chip_first(gi)); gi += 1; ); zx_key(730, 100, 6, "6", "PATCH", "< PREV", ZX_WHT, 0) ? load_patch(slider2 <= 0 ? NP - 1 : slider2 - 1); zx_key(804, 100, 7, "7", "PATCH", "NEXT >", ZX_WHT, 0) ? load_patch(slider2 >= NP - 1 ? 0 : slider2 + 1); zx_key(878, 100, 8, "8", drums ? "ON" : "OFF", "DRUMS", 0x4ADF6A, drums) ? ( slider33 = 1 - slider33; slider_automate(2 ^ 32); panic_req = 1; ); zx_key(952, 100, 9, "9", slider34 ? "6" : "CHIP", "VOICES", 0x4ADF6A, slider34) ? ( slider34 = 1 - slider34; slider_automate(2 ^ 33); panic_req = 1; ); zx_key(1026, 100, 10, "0", "PANIC", "DELETE", ZX_WHT, 0) ? panic_req = 1; // ---------- the knobs: two rows of sections, OUTPUT on the right ---------- ui_knonum = 2; R1 = 226; R2 = 350; zx_sec(52, 160, 280, 114, "TONE / NOISE", zx_pal(0)); ui_knob(88 * sc, R1 * sc, 3, 5, 50, 50, 0, "DUTY", "%.0f%%"); knob_step = 1; ui_knob(152 * sc, R1 * sc, 4, 0, 3, 0, 0, "MIXER", mix_name(slider4)); ui_knob(216 * sc, R1 * sc, 5, 0, 100, 70, 0, "NOISE", "%.0f"); knob_step = 1; ui_knob(280 * sc, R1 * sc, 6, 0, 1, 0, 0, "TYPE", slider6 ? "PERIODIC" : "WHITE"); zx_sec(342, 160, 152, 114, "BUZZ", zx_pal(1)); knob_step = 1; ui_knob(386 * sc, R1 * sc, 11, 0, 3, 0, 0, "SHAPE", buzz_name(slider11)); knob_step = 1; ui_knob(450 * sc, R1 * sc, 12, 0, 2, 1, 0, "TUNE", slider12 == 0 ? "-1 OCT" : slider12 == 1 ? "UNISON" : "+1 OCT"); zx_sec(504, 160, 280, 114, "ENVELOPE", zx_pal(2)); ui_knob(540 * sc, R1 * sc, 7, 0, 2000, 0, 0, "ATTACK", "%.0f ms"); ui_knob(604 * sc, R1 * sc, 8, 0, 5000, 400, 0, "DECAY", "%.0f ms"); knob_step = 1; ui_knob(668 * sc, R1 * sc, 9, 0, 15, 12, 0, "SUSTAIN", "%.0f"); ui_knob(732 * sc, R1 * sc, 10, 0, 5000, 120, 0, "RELEASE", "%.0f ms"); zx_sec(794, 160, 152, 114, "ARP", zx_pal(3)); knob_step = 1; ui_knob(838 * sc, R1 * sc, 13, 0, 7, 0, 0, "CHORD", arp_name(slider13)); knob_step = 1; ui_knob(902 * sc, R1 * sc, 14, 1, 8, 1, 0, "SPEED", "%.0f fr"); zx_sec(52, 284, 216, 114, "VIBRATO", zx_pal(4)); ui_knob(88 * sc, R2 * sc, 15, 0, 100, 0, 0, "DEPTH", "%.0f c"); ui_knob(152 * sc, R2 * sc, 16, 0, 1000, 250, 0, "DELAY", "%.0f ms"); ui_knob(216 * sc, R2 * sc, 17, 1, 12, 6, 0, "RATE", "%.1f Hz"); zx_sec(278, 284, 216, 114, "PITCH", zx_pal(5)); ui_knob(314 * sc, R2 * sc, 18, 0, 1000, 0, 0, "GLIDE", "%.0f ms"); knob_step = 1; ui_knob(378 * sc, R2 * sc, 19, -48, 48, 0, 0, "SWEEP", "%+.0f st"); ui_knob(442 * sc, R2 * sc, 20, 10, 2000, 150, 0, "TIME", "%.0f ms"); zx_sec(504, 284, 442, 114, "FM", zx_pal(6)); knob_step = 1; ui_knob(540 * sc, R2 * sc, 21, 0, 7, 4, 0, "ALGO", "%.0f"); knob_step = 1; ui_knob(611 * sc, R2 * sc, 22, 0, 7, 4, 0, "FEEDBACK", "%.0f"); ui_knob(682 * sc, R2 * sc, 23, 0, 200, 100, 0, "MOD", "%.0f%%"); ui_knob(753 * sc, R2 * sc, 24, 10, 400, 100, 0, "MOD DECAY", "%.0f%%"); ui_knob(824 * sc, R2 * sc, 25, 0, 100, 0, 0, chip == 0 ? "PHASER" : "DETUNE", "%.0f%%"); ui_knob(895 * sc, R2 * sc, 26, 0, 100, 50, 0, "LADDER", "%.0f%%"); zx_sec(956, 160, 136, 238, "OUTPUT", zx_pal(0)); ui_knob(992 * sc, R1 * sc, 29, 0, 100, 30, 0, "VELO", "%.0f%%"); ui_knob(1056 * sc, R1 * sc, 27, 0, 100, 60, 0, "LO-FI", "%.0f%%"); ui_knob(992 * sc, R2 * sc, 28, 0, 100, 40, 0, "STEREO", "%.0f%%"); ui_knob(1056 * sc, R2 * sc, 30, -24, 6, 0, 0, "VOLUME", "%.1f dB"); ui_knonum = 0; // ---------- IN / OUT strip: SAT, OUT, STEREO ---------- // ins_strip(52, 412, 1036, 35, 41, ZX_WHT, 0x1D1D20); pk_end(); ui_end();