desc:GFX Chopper //tags: stutter repeat gate chopper sequencer glitch trance //author: JGWizrad / Garden FX (GFX) // // Stutter-house vocal chops and trance gating. A circular buffer records the input all the time; each step // of the pattern either lets the live sound through, repeats the slice that has just played, plays it // backwards, or mutes it. // // PATTERN up to 16 steps (click a step number to set the length) at one DIVISION: // 1/2, 1/3, 1/4, 1/6, 1/8, 1/12, 1/16, 1/24, 1/32 of a bar (1/3, 1/6, 1/12, 1/24 are triplets). // Per step: MODE THRU (live) / REPEAT / REVERSE / MUTE // RATCHET x1 - x4: the slice is shortened and restarted 2, 3 or 4 times in the step // PITCH 0 / UP (the SHIFT knob, in semitones) / OCT (+12): the slice plays faster // FILTER off / LP / HP (the LP and HP knobs set the frequencies) // Click a cell to step it forward, right-click to step it back. // REPEAT loops the audio of the step that has just passed (beat-repeat), restarting on the grid, with a // FADE window (1 - 10 ms) at every edge so nothing clicks. FREEZE keeps the first slice it caught. // HOLD momentary: while it is down, the slice playing now is locked and repeated at the current division. // MIDI one octave triggers HOLD at each division, rising: C 1/2, C# 1/3, D 1/4, D# 1/6, E 1/8, F 1/12, // F# 1/16, G 1/24, G# 1/32 (octave picked on the panel; middle C = C4). MIDI passes through. // SYNC follows the transport; when it is stopped the pattern runs on at the project tempo. // DRIFT (pitch only): the repeated slices wander slowly in pitch, with a small offset per slice, like tape. // The live sound and the timing are never touched. slider1:0<0,18,1{Init,Lavern Gate,Breathless 16ths,Triplet Flutter,Underwater Build,Reverse Swell,Half-Time Freeze,24th Roll,Trance Pulse,Sidechain Pump,Offbeat Gate,Dotted Gate,Pitch-Up Chops,Triplet Stutter,Snare Roll Build,Rewind Bar,Filter Ladder,IDM Glitch,Half-Time Chops}>-Preset slider2:25<0,100,1>-Drift (%) slider3:0<-24,12,0.1>-Output (dB) slider4:0<-24,24,0.1>-Input (dB) slider5:100<0,100,1>-Mix (%) slider6:6<0,8,1{1/2,1/3,1/4,1/6,1/8,1/12,1/16,1/24,1/32}>-Division slider7:16<1,16,1>-Steps slider8:0<0,50,1>-Swing (%) slider9:5<0,12,1>-Shift (semitones) slider10:1200<100,20000,1>-LP frequency (Hz) slider11:600<20,8000,1>-HP frequency (Hz) slider12:3<1,10,0.1>-Fade (ms) slider13:0<0,1,1{Off,On}>-Freeze slider14:0<0,1,1{Off,On}>-Hold slider15:2<0,4,1{C1,C2,C3,C4,C5}>-MIDI trigger octave // steps: mode + 4 x (ratchet - 1) + 16 x pitch + 48 x filter slider20:0<0,143,1>-Step 1 slider21:0<0,143,1>-Step 2 slider22:0<0,143,1>-Step 3 slider23:0<0,143,1>-Step 4 slider24:0<0,143,1>-Step 5 slider25:0<0,143,1>-Step 6 slider26:0<0,143,1>-Step 7 slider27:0<0,143,1>-Step 8 slider28:0<0,143,1>-Step 9 slider29:0<0,143,1>-Step 10 slider30:0<0,143,1>-Step 11 slider31:0<0,143,1>-Step 12 slider32:0<0,143,1>-Step 13 slider33:0<0,143,1>-Step 14 slider34:0<0,143,1>-Step 15 slider35:0<0,143,1>-Step 16 in_pin:left input in_pin:right input out_pin:left output out_pin:right output @init // ===================================================================== // Memory // ===================================================================== nextmem = 0; function alloc(n) local(ptr) ( ptr = nextmem; nextmem += n; ptr; ); BL = ceil(srate*5) + 8; // 5 s of history: a 1/2 bar down to 48 bpm bufL = alloc(BL); bufR = alloc(BL); // the circular input buffer sbL0 = alloc(BL); sbR0 = alloc(BL); // two slice buffers, so a new slice never cuts the last one off sbL1 = alloc(BL); sbR1 = alloc(BL); evq = alloc(1536); // MIDI events: offset, key, on nh = alloc(16); // trigger keys held memset(0, 0, nextmem); // ===================================================================== // Helpers // ===================================================================== // division in beats: 1/2 1/3 1/4 1/6 1/8 1/12 1/16 1/24 1/32 of a 4/4 bar function divb(i) ( i = floor(i + 0.5); i == 0 ? 2 : i == 1 ? 4/3 : i == 2 ? 1 : i == 3 ? 2/3 : i == 4 ? 0.5 : i == 5 ? 1/3 : i == 6 ? 0.25 : i == 7 ? 1/6 : 0.125; ); // a packed step function st_mode(v) ( v = floor(v + 0.5); v - 4*floor(v/4); ); function st_rat(v) ( v = floor(floor(v + 0.5)/4); v - 4*floor(v/4) + 1; ); function st_pit(v) ( v = floor(floor(v + 0.5)/16); v - 3*floor(v/3); ); function st_flt(v) ( floor(floor(v + 0.5)/48); ); function st_pack(m, r, p, f) ( m + 4*(r - 1) + 16*p + 48*f; ); // copy the last n samples (ending just before the write point) into the slice buffer function sbuf(k, ch) ( k ? (ch ? sbR1 : sbL1) : (ch ? sbR0 : sbL0); ); // a pass ends or restarts: the playing pass carries on as the 'old' voice and fades out over FADE function vrestart() ( xo = gR > 0.0001 ? 1 : 0; ob = vb; oL = vL; orp = rp; odir = vdir; orate = vrate; rp = 0; slrand = rand(2) - 1; ); // copy the last n samples (ending just before the write point) into the other slice buffer function capture(n) local(s, n1, a, b, i) ( vrestart(); vb = 1 - vb; vL = n; a = sbuf(vb, 0); b = sbuf(vb, 1); s = wp - n; s < 0 ? s += BL; n1 = min(n, BL - s); memcpy(a, bufL + s, n1); memcpy(b, bufR + s, n1); n1 < n ? ( memcpy(a + n1, bufL, n - n1); memcpy(b + n1, bufR, n - n1); ); vpk = 0; i = 0; loop(n, vpk = max(vpk, abs(a[i])); i += 1; ); // a silent slice is never kept by FREEZE restarted = 1; ); // the FADE window: 0 at both edges of a pass, 1 inside function win(p, len, rt) local(fs) ( fs = min(len*0.5, max(8, fadeS*rt)); max(0, min(1, min(p, len - 1 - p)/fs)); ); // 4-point Hermite read inside the slice (0 .. len-1) function hread(buf, len, pos) local(i, f, xm1, x0, x1, x2, c1, c2, c3) ( i = floor(pos); f = pos - i; xm1 = buf[max(i - 1, 0)]; x0 = buf[i]; x1 = buf[min(i + 1, len - 1)]; x2 = buf[min(i + 2, len - 1)]; c1 = 0.5*(x1 - xm1); c2 = xm1 - 2.5*x0 + 2*x1 - 0.5*x2; c3 = 0.5*(x2 - xm1) + 1.5*(x0 - x1); ((c3*f + c2)*f + c1)*f + x0; ); // state-variable filter (TPT): low-pass and high-pass outputs function svf_set(f) instance(g, k, a1, a2, a3) ( g = tan($pi*min(f, srate*0.45)/srate); k = 1.2; a1 = 1/(1 + g*(g + k)); a2 = g*a1; a3 = g*a2; ); function svf(v0) instance(ic1, ic2, a1, a2, a3, k, lp, hp) local(v1, v2, v3) ( v3 = v0 - ic2; v1 = a1*ic1 + a2*v3; v2 = ic2 + a2*ic1 + a3*v3; ic1 = 2*v1 - ic1; ic2 = 2*v2 - ic2; lp = v2; hp = v0 - k*v1 - v2; ); // ===================================================================== // Presets - patterns as digit strings, one character per step: // mode 0 thru 1 repeat 2 reverse 3 mute | ratchet 1-4 | pitch 0 / 1 up / 2 oct | filter 0 / 1 LP / 2 HP // ===================================================================== function pat(m, r, p, f) local(i) ( i = 0; loop(16, slider(20 + i) = st_pack(str_getchar(m, i) - 48, str_getchar(r, i) - 48, str_getchar(p, i) - 48, str_getchar(f, i) - 48); i += 1; ); ); function apply_preset(n) ( slider2 = 25; slider3 = 0; slider4 = 0; slider5 = 100; slider6 = 6; slider7 = 16; slider8 = 0; slider9 = 5; slider10 = 1200; slider11 = 600; slider12 = 3; slider13 = 0; pat("0000000000000000", "1111111111111111", "0000000000000000", "0000000000000000"); n == 1 ? ( // Lavern Gate: chopped repeats with gaps, a roll into the next bar pat("1131131311311310", "1111111111112141", "0000000000000000", "0000000000000000"); ) : n == 2 ? ( // Breathless 16ths: every step repeats, the off-beats lifted slider9 = 7; pat("1111111111111111", "1111111111111112", "0101010101010112", "0000000000000000"); ) : n == 3 ? ( // Triplet Flutter: 1/24 steps over two beats slider6 = 7; slider7 = 12; pat("1131131131130000", "1111111121131111", "0001001000120000", "0000000000000000"); ) : n == 4 ? ( // Underwater Build: low-passed repeats that roll faster and rise at the end slider10 = 700; pat("1111111111111111", "1111111122223344", "0000000000000112", "1111111111111111"); ) : n == 5 ? ( // Reverse Swell: 1/8 steps, backwards on the off-beats slider6 = 4; slider7 = 8; slider5 = 100; pat("0202022200000000", "1111111211111111", "0000000000000000", "0000000000000000"); ) : n == 6 ? ( // Half-Time Freeze: one caught slice, quarter by quarter slider6 = 2; slider7 = 4; slider13 = 1; pat("1111000000000000", "1112111111111111", "0001000000000000", "0000000000000000"); ) : n == 7 ? ( // 24th Roll: live, then a rising roll slider6 = 7; slider7 = 12; pat("0000001111110000", "1111111112341111", "0000000001120000", "0000000000220000"); ) : n == 8 ? ( // Trance Pulse: the classic gate on the live sound slider8 = 10; pat("0300300303003003", "1111111111111111", "0000000000000000", "0000000002000020"); ) : n == 9 ? ( // Sidechain Pump: ducked on every beat, the 16th after it thinned by the HP slider11 = 300; pat("3000300030003000", "1111111111111111", "0000000000000000", "0200020002000200"); ) : n == 10 ? ( // Offbeat Gate: the straight 16th on-off trance gate pat("0303030303030303", "1111111111111111", "0000000000000000", "0000000000000000"); ) : n == 11 ? ( // Dotted Gate: 3 + 3 + 3 + 3 + 4 syncopation across the bar pat("0330330330330333", "1111111111111111", "0000000000000000", "0000000000000000"); ) : n == 12 ? ( // Pitch-Up Chops: stutter-house vocal repeats lifted a fifth, an octave at the end slider9 = 7; pat("0011001100111111", "1111111111112233", "0011001100110122", "0000000000000000"); ) : n == 13 ? ( // Triplet Stutter: one bar of 1/12 steps, a backwards kick on each beat slider6 = 5; slider7 = 12; pat("0120120120110000", "1111111111231111", "0000000000120000", "0000000000000000"); ) : n == 14 ? ( // Snare Roll Build: two bars of eighths that ratchet faster, rise and thin out slider6 = 4; slider11 = 400; pat("1111111111111111", "1111222233334444", "0000000011112222", "0000000022222222"); ) : n == 15 ? ( // Rewind Bar: live for half a bar, then backwards and darker slider6 = 4; slider7 = 8; slider10 = 2500; pat("0000222200000000", "1111111211111111", "0000000200000000", "0000111100000000"); ) : n == 16 ? ( // Filter Ladder: a gate whose open steps go LP, then HP, then wide open slider10 = 900; slider11 = 1500; pat("0030003000300033", "1111111111111111", "0000000000000000", "1111222200000000"); ) : n == 17 ? ( // IDM Glitch: 1/32 scatter of repeats, reverses, ratchets and filters slider6 = 8; slider2 = 60; slider9 = 3; pat("1213012310213102", "1311241121314121", "0201020010020120", "0120002100120010"); ) : n == 18 ? ( // Half-Time Chops: quarter-note repeats that drag the groove to half time slider6 = 2; slider7 = 8; slider8 = 15; pat("0101011100000000", "1111111211111111", "0000000200000000", "0000000100000000"); ); ); // ===================================================================== // State // ===================================================================== wp = 0; vL = 0; vb = 0; rp = 0; xo = 0; vpk = 0; vdir = 0; vrate = 1; bcur = 0; restarted = 0; lastg = -999999; lastsub = -1; lasthm = 0; lasthdv = 0; lasthg = 0; curmode = 0; curpitch = 0; curfilt = 0; curstep = 0; gT = 1; gR = 0; wLP = 0; wHP = 0; mnote = -1; needcap = 1; lastfrz = 0; dwv = 0; dwt = 0; dwcnt = 0; dwc = 1 - exp(-1/(0.6*srate)); slrand = 0; fcf = 1 - exp(-1/(0.01*srate)); drag_id = -1; pcap = 0; lastS = 0; gfx_ext_retina = 1; q_init == 0 ? ( // a new instance (the step defaults are the INIT pattern) lastPreset = slider1; ui_sz = 2; q_init = 1; ); @serialize file_var(0, q_init); file_var(0, ui_sz); file_var(0, lastPreset); file_var(0, ch_thm); @slider slider1 != lastPreset ? ( apply_preset(slider1); lastPreset = slider1; ); @block inG = 10^(slider4/20); outG = 10^(slider3/20); mixW = slider5/100; dv = divb(slider6); nst = max(1, floor(slider7 + 0.5)); sw = slider8/100; shift = slider9; dr = slider2/100; lpL.svf_set(slider10); lpR.svf_set(slider10); hpL.svf_set(slider11); hpR.svf_set(slider11); fadeS = max(1, slider12*0.001*srate); fc = 1 - exp(-1/fadeS); spb = srate*60/max(tempo, 1); bpinc = tempo/(60*srate); (play_state & 1) ? bcur = beat_position; // follow the transport; when stopped run on at the tempo holdOn = slider14 > 0.5; frz = slider13 > 0.5; frz && !lastfrz ? needcap = 1; // FREEZE catches a fresh slice when it is switched on lastfrz = frz; // MIDI: pass everything through; the trigger octave holds at each division evn = 0; nb = 24 + 12*floor(slider15 + 0.5); while (midirecv(mo, m1, m2, m3)) ( midisend(mo, m1, m2, m3); ms = m1 & 0xF0; (ms == 0x90 || ms == 0x80) ? ( mk = m2 - nb; mk >= 0 && mk < 9 && evn < 500 ? ( evq[evn*3] = mo; evq[evn*3 + 1] = mk; evq[evn*3 + 2] = ms == 0x90 && m3 > 0; evn += 1; ); ); ); evi = 0; spos = 0; @sample // ---- MIDI triggers at their sample ---- while (evi < evn && evq[evi*3] <= spos) ( mk = evq[evi*3 + 1]; evq[evi*3 + 2] ? ( nh[mk] = 1; mnote = mk; ) : ( nh[mk] = 0; mnote == mk ? ( mnote = -1; i = 8; loop(9, nh[i] && mnote < 0 ? mnote = i; i -= 1; ); ); // fall back to a key still held ); evi += 1; ); spos += 1; xl = spl0*inG; xr = spl1*inG; b = bcur; bcur += bpinc; // ---- which step, and what it does ---- hm = mnote >= 0 ? 2 : holdOn ? 1 : 0; // 2 = a MIDI trigger, 1 = HOLD hm ? ( hdv = hm == 2 ? divb(mnote) : dv; hg = floor(b/hdv); hm != lasthm || hdv != lasthdv ? ( // lock the slice playing now (one division long), with the pitch and filter of the moment capture(min(BL - 8, max(64, floor(hdv*spb + 0.5)))); hpitch = curpitch; hfilt = curfilt; lasthg = hg; ) : hg != lasthg ? ( vrestart(); lasthg = hg; ); lasthm = hm; lasthdv = hdv; tmode = 1; tpitch = hpitch; tfilt = hfilt; ) : ( lasthm ? ( lasthm = 0; lastg = -999999; needcap = 1; ); // released: back into the pattern pf = floor(b/(2*dv)); pp = b - pf*2*dv; a1 = dv*(1 + sw); // swing: the first of each pair is longer pp < a1 ? ( sodd = 0; fr = pp/a1; slen = a1; ) : ( sodd = 1; fr = (pp - a1)/(dv*(1 - sw)); slen = dv*(1 - sw); ); g = pf*2 + sodd; g != lastg ? ( lastg = g; curstep = g - floor(g/nst)*nst; sv = slider(20 + curstep); curmode = st_mode(sv); rat = st_rat(sv); curpitch = st_pit(sv); curfilt = st_flt(sv); lastsub = -1; (curmode == 1 || curmode == 2) && (!frz || needcap || vL < 64 || vpk < 0.0003) ? ( capture(min(BL - 8, max(64, floor(slen*spb/rat + 0.5)))); needcap = 0; ); ); sub = floor(fr*rat); sub != lastsub ? ( lastsub = sub; !restarted ? vrestart(); ); // ratchets restart on the grid tmode = curmode; tpitch = curpitch; tfilt = curfilt; ); restarted = 0; // record (after any capture, so a slice always ends just before now) bufL[wp] = xl; bufR[wp] = xr; wp += 1; wp >= BL ? wp = 0; // ---- drift: the repeats wander in pitch (slow), plus a small offset per slice ---- (dwcnt -= 1) <= 0 ? ( dwt = rand(2) - 1; dwcnt = srate*(0.6 + rand(0.8)); ); dwv += (dwt - dwv)*dwc; // ---- the slice ---- gT += ((tmode == 0) - gT)*fc; gR += ((tmode == 1 || tmode == 2) - gR)*fc; syl = syr = 0; semi = tpitch == 1 ? shift : tpitch == 2 ? 12 : 0; vrate = 2^((semi + dr*(0.25*dwv + 0.1*slrand))/12); vdir = tmode == 2; gR > 0.0001 && vL > 1 ? ( pos = vdir ? vL - 1 - rp : rp; env = win(rp, vL, vrate); // the FADE window at both edges of every pass syl = hread(sbuf(vb, 0), vL, max(0, min(pos, vL - 1)))*env; syr = hread(sbuf(vb, 1), vL, max(0, min(pos, vL - 1)))*env; rp += vrate; rp >= vL - 1 ? rp -= vL - 1; ); // the pass that was cut short by a restart fades out underneath xo > 0 && oL > 1 ? ( pos = odir ? oL - 1 - orp : orp; env = win(orp, oL, orate)*xo; syl += hread(sbuf(ob, 0), oL, max(0, min(pos, oL - 1)))*env; syr += hread(sbuf(ob, 1), oL, max(0, min(pos, oL - 1)))*env; orp += orate; orp >= oL - 1 ? orp -= oL - 1; xo -= 1/fadeS; ); wl = gT*xl + gR*syl; wr = gT*xr + gR*syr; // ---- the step filter (smoothed so a change never zips) ---- wLP += ((tfilt == 1) - wLP)*fcf; wHP += ((tfilt == 2) - wHP)*fcf; lpL.svf(wl); lpR.svf(wr); hpL.svf(wl); hpR.svf(wr); wl = wl*(1 - wLP - wHP) + lpL.lp*wLP + hpL.hp*wHP; wr = wr*(1 - wLP - wHP) + lpR.lp*wLP + hpR.hp*wHP; spl0 = (xl*(1 - mixW) + wl*mixW)*outG; spl1 = (xr*(1 - mixW) + wr*mixW)*outG; @gfx 1600 480 // ===================================================================== // GUI - a 1600 x 480 faceplate in ACID LIME (black print, magenta LEDs) or HOT MAGENTA (white print, cyan LEDs). // Size picker: S 75% / M 90% / L 105% (shrinks further if the window is smaller). // Knobs: drag vertically (Shift = fine), double-click or Ctrl-click = reset; value shown while hovering. // The LCD shows the preset, HOLD, or the control you are touching. // ===================================================================== szf = ui_sz == 1 ? 0.75 : ui_sz == 2 ? 1.05 : 0.9; S = min(szf, min(gfx_w/1600, gfx_h/480)); ox = floor((gfx_w - 1600*S)*0.5); oy = floor((gfx_h - 480*S)*0.5); mx = (mouse_x - ox)/S; my = (mouse_y - oy)/S; hlp ? ( (mouse_cap & 1) && !(pcap & 1) && !hlp_new ? hlp = 0; pcap = mouse_cap; ); hlp_new = 0; S != lastS ? ( gfx_setfont(1, "Arial Black", max(12, 46*S)); // CHOPPER gfx_setfont(2, "Arial Bold", max(10, 18*S)); // section titles gfx_setfont(3, "Arial Bold", max(9, 15*S)); // labels gfx_setfont(4, "Arial Bold", max(9, 14*S)); // values gfx_setfont(5, "Arial Bold", max(8, 13*S)); // small print gfx_setfont(6, "Arial Bold", max(9, 17*S)); // buttons / cells gfx_setfont(7, "Courier New", max(10, 23*S), 'b'); // LCD line 2 gfx_setfont(8, "Courier New", max(9, 15*S), 'b'); // LCD line 1 gfx_setfont(9, "Arial Black", max(12, 40*S)); // HOLD lastS = S; ); // ---- palette ---- !ch_thm ? ( F_R = 0.80; F_G = 0.96; F_B = 0.14; // acid lime K_R = 0.06; K_G = 0.06; K_B = 0.06; // black print L_R = 1.00; L_G = 0.10; L_B = 0.62; // magenta LEDs ) : ( F_R = 0.84; F_G = 0.10; F_B = 0.52; // hot magenta K_R = 0.98; K_G = 0.97; K_B = 0.97; // white print L_R = 0.22; L_G = 0.94; L_B = 1.00; // cyan LEDs ); function col(r, g, b) ( gfx_r = r; gfx_g = g; gfx_b = b; ); function cface() ( col(F_R, F_G, F_B); ); function cdark() ( col(F_R*0.80, F_G*0.80, F_B*0.80); ); function cink() ( col(K_R, K_G, K_B); ); function cled() ( col(L_R, L_G, L_B); ); function cblack() ( col(0.07, 0.07, 0.08); ); function R(x, y, w, h) ( gfx_rect(ox + x*S, oy + y*S, max(1, w*S), max(1, h*S)); ); function RO(x, y, w, h) local(t, k) ( t = max(1, floor(2*S + 0.5)); k = 0; loop(t, gfx_rect(ox + x*S + k, oy + y*S + k, max(1, w*S - 2*k), max(1, h*S - 2*k), 0); k += 1; ); ); function LN(x1, y1, x2, y2) ( gfx_line(ox + x1*S, oy + y1*S, ox + x2*S, oy + y2*S, 1); ); function TX(x, y, txt) ( gfx_x = ox + x*S; gfx_y = oy + y*S; gfx_drawstr(txt); ); function TC(cx, y, txt) local(w, h) ( gfx_measurestr(txt, w, h); gfx_x = ox + cx*S - w*0.5; gfx_y = oy + y*S; gfx_drawstr(txt); ); function TM(cx, cy, txt) local(w, h) ( gfx_measurestr(txt, w, h); gfx_x = ox + cx*S - w*0.5; gfx_y = oy + cy*S - h*0.5; gfx_drawstr(txt); ); function CIRC(x, y, rr, fill) ( gfx_circle(ox + x*S, oy + y*S, rr*S, fill, 1); ); function inside(x, y, w, h) ( mx >= x && mx <= x + w && my >= y && my <= y + h; ); function clicked(x, y, w, h) ( (mouse_cap & 1) && !(pcap & 1) && drag_id == -1 && inside(x, y, w, h); ); function rclicked(x, y, w, h) ( (mouse_cap & 2) && !(pcap & 2) && inside(x, y, w, h); ); function setk(idx, v) ( slider(idx) != v ? ( slider(idx) = v; slider_automate(2^(idx - 1)); ); ); function setmsg(n, v) ( strcpy(#lcdn, n); strcpy(#lcdv, v); lcd_t = time_precise(); ); function screw(x, y) ( col(0.55, 0.55, 0.57); CIRC(x, y, 7, 1); col(0.25, 0.25, 0.27); CIRC(x, y, 7, 0); LN(x - 4, y + 4, x + 4, y - 4); ); // button: on = LED fill with black print; off = face with an outline function btn(x, y, w, h, lbl, on, fnt) ( on ? cled() : cface(); R(x, y, w, h); cink(); RO(x, y, w, h); on ? cblack() : cink(); gfx_setfont(fnt); TM(x + w*0.5, y + h*0.5, lbl); clicked(x, y, w, h); ); function frame(x, y, w, h, title) local(fw, fh) ( cink(); RO(x, y, w, h); gfx_setfont(5); gfx_measurestr(title, fw, fh); R(x + 10, y - 8, fw/S + 14, 16); cface(); TX(x + 17, y - 7, title); ); // black knob with a printed scale and an LED-coloured value arc; the value shows only while hovering function knob(id, idx, cx, cy, rad, lo, hi, def, lg, bip, cap, label, val) local(v, dx, dy, fr, nf, ang, sx, sy, sr, k, t, ta, sa, ca, hov) ( v = slider(idx); fr = lg ? log(v/lo)/log(hi/lo) : (v - lo)/(hi - lo); dx = mx - cx; dy = my - cy; hov = dx*dx + dy*dy < rad*rad*1.6; (mouse_cap & 1) && !(pcap & 1) && drag_id == -1 && hov ? ( ((mouse_cap & 4) || (kc_id == id && time_precise() - kc_t < 0.35)) ? ( v = def; kc_t = 0; setmsg(label, val); ) : ( drag_id = id; drag_sy = my; drag_sf = fr; kc_id = id; kc_t = time_precise(); ); ); drag_id == id ? ( (mouse_cap & 1) ? ( nf = max(0, min(1, drag_sf + (drag_sy - my)/((mouse_cap & 8) ? 720 : 180))); v = lg ? lo*(hi/lo)^nf : lo + nf*(hi - lo); ) : drag_id = -1; ); hov && mouse_wheel != 0 && drag_id == -1 ? ( nf = max(0, min(1, fr + mouse_wheel/120*((mouse_cap & 8) ? 0.005 : 0.025))); v = lg ? lo*(hi/lo)^nf : lo + nf*(hi - lo); mouse_wheel = 0; ); setk(idx, v); fr = lg ? log(v/lo)/log(hi/lo) : (v - lo)/(hi - lo); ang = (-0.75 + 1.5*fr)*$pi; sx = ox + cx*S; sy = oy + cy*S; sr = rad*S; cink(); k = 0; loop(11, ta = (-0.75 + 1.5*k/10)*$pi; t = (k == 0 || k == 10 || (bip && k == 5)) ? 8 : 4; gfx_line(sx + sin(ta)*(sr + 4*S), sy - cos(ta)*(sr + 4*S), sx + sin(ta)*(sr + (4 + t)*S), sy - cos(ta)*(sr + (4 + t)*S), 1); k += 1; ); cled(); k = 0; loop(max(2, floor(3*S + 0.5)), gfx_arc(sx, sy, sr + 2*S + k, bip ? min(0, ang) : -0.75*$pi, bip ? max(0, ang) : ang, 1); k += 1; ); col(0.03, 0.03, 0.03); gfx_circle(sx, sy, sr, 1, 1); col(0.20, 0.20, 0.21); k = 0; loop(28, ta = k*2*$pi/28; gfx_line(sx + sin(ta)*sr*0.82, sy - cos(ta)*sr*0.82, sx + sin(ta)*(sr - 1), sy - cos(ta)*(sr - 1), 1); k += 1; ); cap >= 0 ? ( col(((cap >> 16) & 255)/255*0.7, ((cap >> 8) & 255)/255*0.7, (cap & 255)/255*0.7); gfx_circle(sx, sy, sr*0.74, 1, 1); col(((cap >> 16) & 255)/255, ((cap >> 8) & 255)/255, (cap & 255)/255); gfx_circle(sx, sy, sr*0.70, 1, 1); ) : ( col(0.11, 0.11, 0.12); gfx_circle(sx, sy, sr*0.74, 1, 1); col(0.22, 0.22, 0.24); gfx_circle(sx - sr*0.2, sy - sr*0.24, sr*0.2, 1, 1); ); col(0.97, 0.97, 0.95); sa = sin(ang); ca = cos(ang); t = max(2, floor(4*S + 0.5)); k = 0; loop(t, gfx_line(sx + sa*sr*0.2 + ca*(k - t*0.5), sy - ca*sr*0.2 + sa*(k - t*0.5), sx + sa*sr*0.95 + ca*(k - t*0.5), sy - ca*sr*0.95 + sa*(k - t*0.5), 1); k += 1; ); cink(); gfx_setfont(3); TC(cx, cy + rad + 12, label); (hov || drag_id == id) ? ( gfx_setfont(4); TC(cx, cy + rad + 32, val); ); drag_id == id ? setmsg(label, val); ); function fpct(v) ( sprintf(#vt, "%d%%", floor(v + 0.5)); #vt; ); function fdb(v) local(q) ( q = floor(v*10 + 0.5)/10; q > 0 ? sprintf(#vt, "+%.1f dB", q) : sprintf(#vt, "%.1f dB", q); #vt; ); function fhz(v) ( v >= 1000 ? sprintf(#vt, "%.1f kHz", v/1000) : sprintf(#vt, "%d Hz", floor(v + 0.5)); #vt; ); function dname(i) ( i = floor(i + 0.5); i == 0 ? "1/2" : i == 1 ? "1/3" : i == 2 ? "1/4" : i == 3 ? "1/6" : i == 4 ? "1/8" : i == 5 ? "1/12" : i == 6 ? "1/16" : i == 7 ? "1/24" : "1/32"; ); function pname(n) ( n = floor(n + 0.5); n == 0 ? "INIT" : n == 1 ? "LAVERN GATE" : n == 2 ? "BREATHLESS 16S" : n == 3 ? "TRIPLET FLUTTER" : n == 4 ? "UNDERWATER BUILD" : n == 5 ? "REVERSE SWELL" : n == 6 ? "HALF-TIME FREEZE" : n == 7 ? "24TH ROLL" : n == 8 ? "TRANCE PULSE" : n == 9 ? "SIDECHAIN PUMP" : n == 10 ? "OFFBEAT GATE" : n == 11 ? "DOTTED GATE" : n == 12 ? "PITCH-UP CHOPS" : n == 13 ? "TRIPLET STUTTER" : n == 14 ? "SNARE ROLL BUILD" : n == 15 ? "REWIND BAR" : n == 16 ? "FILTER LADDER" : n == 17 ? "IDM GLITCH" : "HALF-TIME CHOPS"; ); function step_preset(dir) ( slider1 = (slider1 + dir + 19) % 19; apply_preset(slider1); lastPreset = slider1; lcd_t = 0; slider_automate(2^35 - 1); ); function oname(i) ( i = floor(i + 0.5); i == 0 ? "C1" : i == 1 ? "C2" : i == 2 ? "C3" : i == 3 ? "C4" : "C5"; ); // ===================================================================== // Draw // ===================================================================== col(0.10, 0.10, 0.10); gfx_rect(0, 0, gfx_w, gfx_h); cface(); R(0, 0, 1600, 480); // rack ears ex = 0; loop(2, exx = ex == 0 ? 0 : 1556; cink(); RO(exx, 0, 44, 480); ey = 0; loop(2, eyy = ey == 0 ? 60 : 420; cink(); CIRC(exx + 16, eyy, 7, 1); CIRC(exx + 28, eyy, 7, 1); R(exx + 16, eyy - 7, 12, 14); ey += 1; ); ex += 1; ); cink(); R(44, 0, 1512, 3); R(44, 477, 1512, 3); // ---- left: brand, LCD, presets, size, theme, MIDI ---- cink(); gfx_setfont(5); TX(64, 16, "GFX"); gfx_setfont(1); TX(60, 28, "CHOPPER"); gfx_setfont(5); TX(64, 84, "STUTTER / GATE SEQUENCER"); cblack(); R(60, 108, 266, 92); col(0.05, 0.04, 0.05); R(64, 112, 258, 84); cled(); hm ? ( gfx_setfont(8); TX(76, 122, hm == 2 ? "MIDI HOLD" : "HOLD"); gfx_setfont(7); TX(76, 150, dname(hm == 2 ? mnote : slider6)); ) : time_precise() - lcd_t < 1.5 ? ( gfx_setfont(8); TX(76, 122, #lcdn); gfx_setfont(7); TX(76, 150, #lcdv); ) : ( gfx_setfont(8); sprintf(#lcd1, "PRESET %02d/19", slider1 + 1); TX(76, 122, #lcd1); gfx_setfont(7); TX(76, 150, pname(slider1)); ); // PRESETS key or a click on the LCD: every preset in a drop-down pm_ = btn(60, 212, 130, 36, "PRESETS", 0, 6); clicked(60, 108, 266, 92) || pm_ ? ( strcpy(#pmenu, ""); k = 0; loop(19, k == slider1 ? strcat(#pmenu, "!"); strcat(#pmenu, pname(k)); k < 18 ? strcat(#pmenu, "|"); k += 1; ); gfx_x = mouse_x; gfx_y = mouse_y; psel = gfx_showmenu(#pmenu); psel > 0 ? ( slider1 = psel - 1; apply_preset(slider1); lastPreset = slider1; lcd_t = 0; slider_automate(2^35 - 1); ); ); btn(198, 212, 60, 36, "◄", 0, 6) ? step_preset(-1); btn(266, 212, 60, 36, "►", 0, 6) ? step_preset(1); cink(); gfx_setfont(5); TX(60, 268, "SIZE"); btn(60, 284, 48, 30, "S", ui_sz == 1, 6) ? ui_sz = 1; btn(114, 284, 48, 30, "M", ui_sz == 0, 6) ? ui_sz = 0; btn(168, 284, 48, 30, "L", ui_sz == 2, 6) ? ui_sz = 2; btn(222, 284, 30, 30, "?", hlp, 6) ? ( hlp = 1; hlp_new = 1; ); btn(60, 330, 126, 30, ch_thm ? "MAGENTA" : "LIME", 0, 5) ? ch_thm = !ch_thm; btn(200, 330, 126, 30, oname(slider15), 0, 5) ? ( setk(15, (floor(slider15 + 0.5) + 1) % 5); sprintf(#mo1, "%s - %s", oname(slider15), slider15 < 0.5 ? "G#1" : slider15 < 1.5 ? "G#2" : slider15 < 2.5 ? "G#3" : slider15 < 3.5 ? "G#4" : "G#5"); setmsg("MIDI TRIGGERS", #mo1); ); cink(); gfx_setfont(5); TX(204, 366, "MIDI OCTAVE"); TX(60, 392, "MIDI: C = 1/2 ... G# = 1/32"); TX(60, 410, "held key = HOLD at that division"); mnote >= 0 ? cled() : cdark(); CIRC(70, 444, 7, 1); cink(); CIRC(70, 444, 7, 0); gfx_setfont(5); TX(84, 437, "MIDI"); screw(312, 452); // ---- centre: the pattern ---- frame(346, 18, 852, 442, "PATTERN"); cink(); gfx_setfont(3); TX(360, 40, "DIVISION"); k = 0; loop(9, btn(470 + k*79, 32, 73, 34, dname(k), floor(slider6 + 0.5) == k, 6) ? ( setk(6, k); setmsg("DIVISION", dname(k)); ); k += 1; ); cink(); gfx_setfont(5); TX(470, 70, "STRAIGHT 1/2 1/4 1/8 1/16 1/32 - TRIPLET 1/3 1/6 1/12 1/24"); CX0 = 456; CW = 45.5; WC = 41; lane_y1 = 122; lane_h1 = 62; // MODE lane_y2 = 192; lane_h2 = 44; // RATCHET lane_y3 = 244; lane_h3 = 44; // PITCH lane_y4 = 296; lane_h4 = 44; // FILTER cink(); gfx_setfont(3); TX(360, 92, "STEP"); TX(360, lane_y1 + 12, "MODE"); gfx_setfont(5); TX(360, lane_y1 + 34, "◄ = reverse"); gfx_setfont(3); TX(360, lane_y2 + 14, "RATCHET"); TX(360, lane_y3 + 14, "PITCH"); TX(360, lane_y4 + 14, "FILTER"); si = 0; loop(16, x = CX0 + si*CW; sv = slider(20 + si); m_ = st_mode(sv); r_ = st_rat(sv); p_ = st_pit(sv); f_ = st_flt(sv); on_ = si < nst; // step number: click to set the pattern length si == curstep && !hm && on_ ? ( cled(); R(x, 88, WC, 26); cblack(); ) : on_ ? cink() : cdark(); gfx_setfont(6); sprintf(#sn, "%d", si + 1); TM(x + WC*0.5, 101, #sn); clicked(x, 88, WC, 26) ? ( setk(7, si + 1); sprintf(#sm, "%d STEPS", si + 1); setmsg("LENGTH", #sm); ); // MODE m_ == 0 ? cdark() : m_ == 3 ? cblack() : cled(); R(x, lane_y1, WC, lane_h1); cink(); RO(x, lane_y1, WC, lane_h1); m_ == 2 ? ( cblack(); gfx_setfont(6); TM(x + WC*0.5, lane_y1 + lane_h1*0.5, "◄"); ); m_ == 3 ? ( col(0.45, 0.45, 0.47); gfx_setfont(6); TM(x + WC*0.5, lane_y1 + lane_h1*0.5, "×"); ); // RATCHET / PITCH / FILTER r_ > 1 ? cled() : cdark(); R(x, lane_y2, WC, lane_h2); cink(); RO(x, lane_y2, WC, lane_h2); r_ > 1 ? ( cblack(); gfx_setfont(6); sprintf(#rt, "x%d", r_); TM(x + WC*0.5, lane_y2 + lane_h2*0.5, #rt); ); p_ > 0 ? cled() : cdark(); R(x, lane_y3, WC, lane_h3); cink(); RO(x, lane_y3, WC, lane_h3); p_ > 0 ? ( cblack(); gfx_setfont(5); p_ == 1 ? sprintf(#pt, "+%d", floor(slider9 + 0.5)) : strcpy(#pt, "+12"); TM(x + WC*0.5, lane_y3 + lane_h3*0.5, #pt); ); f_ == 1 ? col(0.35, 0.60, 0.98) : f_ == 2 ? col(0.98, 0.80, 0.10) : cdark(); R(x, lane_y4, WC, lane_h4); cink(); RO(x, lane_y4, WC, lane_h4); f_ > 0 ? ( cblack(); gfx_setfont(5); TM(x + WC*0.5, lane_y4 + lane_h4*0.5, f_ == 1 ? "LP" : "HP"); ); // steps past the length are dimmed !on_ ? ( gfx_a = 0.55; cface(); R(x - 1, lane_y1 - 1, WC + 2, lane_y4 + lane_h4 - lane_y1 + 2); gfx_a = 1; ); // the playhead si == curstep && !hm && on_ ? ( cled(); k = 0; loop(max(2, floor(3*S + 0.5)), gfx_rect(ox + (x - 3)*S - k, oy + (lane_y1 - 3)*S - k, (WC + 6)*S + 2*k, (lane_y4 + lane_h4 - lane_y1 + 6)*S + 2*k, 0); k += 1; ); ); // clicks: left steps forward, right steps back dir = clicked(x, lane_y1, WC, lane_h1) || clicked(x, lane_y2, WC, lane_h2) || clicked(x, lane_y3, WC, lane_h3) || clicked(x, lane_y4, WC, lane_h4) ? 1 : rclicked(x, lane_y1, WC, lane_h1) || rclicked(x, lane_y2, WC, lane_h2) || rclicked(x, lane_y3, WC, lane_h3) || rclicked(x, lane_y4, WC, lane_h4) ? -1 : 0; dir != 0 ? ( inside(x, lane_y1, WC, lane_h1) ? ( m_ = (m_ + dir + 4) % 4; setmsg(#sn, m_ == 0 ? "THRU" : m_ == 1 ? "REPEAT" : m_ == 2 ? "REVERSE" : "MUTE"); ) : inside(x, lane_y2, WC, lane_h2) ? ( r_ = (r_ - 1 + dir + 4) % 4 + 1; sprintf(#rt, "RATCHET x%d", r_); setmsg(#sn, #rt); ) : inside(x, lane_y3, WC, lane_h3) ? ( p_ = (p_ + dir + 3) % 3; setmsg(#sn, p_ == 0 ? "PITCH 0" : p_ == 1 ? "PITCH UP" : "PITCH +12"); ) : inside(x, lane_y4, WC, lane_h4) ? ( f_ = (f_ + dir + 3) % 3; setmsg(#sn, f_ == 0 ? "FILTER OFF" : f_ == 1 ? "LOW-PASS" : "HIGH-PASS"); ); sprintf(#sn, "STEP %d", si + 1); strcpy(#lcdn, #sn); setk(20 + si, st_pack(m_, r_, p_, f_)); ); si += 1; ); // HOLD (momentary) and FREEZE hold_m == 0 && clicked(360, 360, 230, 88) ? ( hold_m = 1; setk(14, 1); ); hold_m && !(mouse_cap & 1) ? ( hold_m = 0; setk(14, 0); ); hon = slider14 > 0.5 || mnote >= 0; hon ? cled() : cdark(); R(360, 360, 230, 88); cink(); RO(360, 360, 230, 88); hon ? cblack() : cink(); gfx_setfont(9); TM(475, 404, "HOLD"); btn(606, 360, 150, 40, "FREEZE", slider13 > 0.5, 6) ? ( setk(13, slider13 < 0.5); setmsg("FREEZE", slider13 > 0.5 ? "ON" : "OFF"); ); cink(); gfx_setfont(5); TX(606, 410, "keeps the first slice"); TX(780, 366, "Click a cell to step it on, right-click to step back."); TX(780, 386, "Click a step number to set the pattern length."); TX(780, 406, "REPEAT loops the step that has just played."); TX(780, 426, "HOLD locks the slice playing now while held."); // ---- right: knobs ---- frame(1212, 18, 330, 140, "PITCH & TIME"); sprintf(#q9, "+%d st", floor(slider9 + 0.5)); knob(9, 9, 1268, 74, 26, 0, 12, 5, 0, 0, -1, "SHIFT", #q9); slider9 != floor(slider9 + 0.5) ? slider9 = floor(slider9 + 0.5); knob(2, 2, 1377, 74, 26, 0, 100, 25, 0, 0, -1, "DRIFT", fpct(slider2)); knob(8, 8, 1486, 74, 26, 0, 50, 0, 0, 0, -1, "SWING", fpct(slider8)); frame(1212, 172, 330, 140, "FILTER & FADE"); knob(10, 10, 1268, 228, 26, 100, 20000, 1200, 1, 0, 0x5A99FA, "LP", fhz(slider10)); knob(11, 11, 1377, 228, 26, 20, 8000, 600, 1, 0, 0xFACC1A, "HP", fhz(slider11)); sprintf(#q12, "%.1f ms", slider12); knob(12, 12, 1486, 228, 26, 1, 10, 3, 0, 0, -1, "FADE", #q12); frame(1212, 326, 330, 134, "MASTER"); knob(4, 4, 1268, 380, 26, -24, 24, 0, 0, 1, -1, "INPUT", fdb(slider4)); knob(5, 5, 1377, 380, 26, 0, 100, 100, 0, 0, -1, "MIX", fpct(slider5)); knob(3, 3, 1486, 380, 26, -24, 12, 0, 0, 1, -1, "OUTPUT", fdb(slider3)); // ? pop-up: credit and a quick guide hlp ? ( gfx_a = 0.62; col(0, 0, 0); gfx_rect(0, 0, gfx_w, gfx_h); gfx_a = 1; col(0.11, 0.11, 0.13); R(480, 120, 640, 240); cled(); R(500, 164, 600, 1); gfx_setfont(2); col(0.95, 0.95, 0.93); TX(500, 136, "Design by JGWizrad. © John Garden 2026"); gfx_setfont(3); col(0.85, 0.84, 0.82); TX(500, 180, "MODE: thru / repeat / reverse / mute. RATCHET: x1 - x4."); TX(500, 204, "PITCH: 0 / UP (SHIFT) / +12. FILTER: off / LP / HP."); TX(500, 228, "MIDI: one octave from the chosen C holds at 1/2 ... 1/32."); TX(500, 252, "DRIFT moves the pitch of the repeats only."); TX(500, 276, "https://www.gardenfx.uk/"); col(0.52, 0.51, 0.49); TX(500, 322, "Click anywhere to close."); ); pcap = mouse_cap;