desc:Motra [analogue poly synth] (GFX Lab)
//tags: instrument synth poly mono analogue sequencer unison bass pad
//author: JGWizrad / Garden FX (GFX)
//
// Requires gfx-lab-drift, gfx-lab-voice, gfx-lab-output, gfx-lab-ui, gfx-lab-panel, gfx-lab-instrument, gfx-lab-dyn, gfx-lab-faceplate
// (.jsfx-inc) in the same folder.
//
// MOTRA. A straightforward analogue-style synth built for three things: big fat detuned basses and pads,
// a filter that bites, and motion from a four-track step sequencer.
//
// VOICE      two DCOs (SAW, TRI, PULSE with PW; DCO 2 can be off) with their MIX, a SUB square an octave or two
//            below DCO 1, and NOISE.
// STACK      every note plays 1 - 4 voices, DETUNEd apart and SPREAD across the stereo field (MONO or POLY).
// FILTER     a four-stage ladder with resonance up to self-oscillation, read five ways from its stages:
//            2-POLE LP, 4-POLE LP, BAND, HIGH and NOTCH (mixes of the stage outputs, in the spirit of the
//            mid-80s modular polys that tapped their ladders); DRIVE into it, ENV amount, KEY tracking and VELocity.
// ENVELOPES  FILTER and AMP, attack - decay - sustain - release; AMP velocity.
// MOTION     two LFOs (free or tempo-synced; to pitch, cutoff, PW or pan) and the SEQUENCER: four tracks of
//            sixteen steps - PITCH (semitones), CUTOFF, LEVEL and one assignable (PW, resonance, drive, pan or
//            noise), each with its depth. It runs while a key is held (in time with the project while it plays),
//            RETRIG restarts the envelopes on every step.
// STEP LANES (new in the Lab copy) two more rows under the four tracks: FILTER sets the filter type step by
//            step and STACK the number of stacked voices step by step (each step can also follow the panel).
//            A type change glides the stage mix over 10 ms (the ladder keeps running: nothing restarts, so it
//            never clicks); a stack change fades the stacked voices in or out over 6 ms and re-spreads the
//            others across the stereo field. A note takes as many voices as the STACK lane's biggest step.
//            While a note rings out after the key is let go, the last step's type and stack stay.
// MODES      MONO (legato, last note, GLIDE) or POLY (up to eight voices: four notes at STACK 2, two at STACK 4).
//            A voice taken for a new note fades out first (4 ms): no clicks, nothing bumps into anything.
// ASSIGN     four knobs on the top row, each set to any parameter (click its name).
// DRIFT      every voice's DCOs wander on their own and every voice sits at its own tuning and cutoff, on top of
//            the GFX instrument drift.
// MIDI       note on / off (sample accurate), pitch bend, CC1 mod wheel (adds LFO 1), CC64 sustain, CC123.
// OUTPUT     a 5 Hz DC blocker (Lab copy), then the house output stage (band SAT, CLIP always on). Latency: none.

import gfx-lab-drift.jsfx-inc
import gfx-lab-voice.jsfx-inc
import gfx-lab-output.jsfx-inc
import gfx-lab-ui.jsfx-inc
import gfx-lab-panel.jsfx-inc
import gfx-lab-instrument.jsfx-inc
import gfx-lab-dyn.jsfx-inc
import gfx-lab-faceplate.jsfx-inc

slider1:0<0,2,1{Saw,Tri,Pulse}>-DCO 1 wave
slider2:0<-24,24,1>-DCO 1 freq (st)
slider3:50<2,98,1>-DCO 1 pulse width (%)
slider4:1<0,3,1{Off,Saw,Tri,Pulse}>-DCO 2 wave
slider5:0<-24,24,1>-DCO 2 freq (st)
slider6:7<-50,50,0.1>-DCO 2 fine (cents)
slider7:50<2,98,1>-DCO 2 pulse width (%)
slider8:50<0,100,1>-DCO mix (1 - 2, %)
slider9:0<0,100,1>-Sub level (%)
slider10:0<0,1,1{-1 oct,-2 oct}>-Sub octave
slider11:0<0,100,1>-Noise (%)
slider12:0<0,3,1{1,2,3,4}>-Stack (voices per note)
slider13:12<0,50,0.1>-Stack detune (cents)
slider14:60<0,100,1>-Stack spread (%)
slider15:2500<20,16000,1>-Cutoff (Hz)
slider16:20<0,100,0.1>-Resonance (%)
slider17:1<0,4,1{2-pole low-pass,4-pole low-pass,Band-pass,High-pass,Notch}>-Filter type
slider18:25<0,100,1>-Drive (%)
slider19:45<-100,100,1>-Filter env amount (%)
slider20:40<0,100,1>-Key track (%)
slider21:30<0,100,1>-Filter velocity (%)
slider22:2<0.5,10000,0.1>-Filter env attack (ms)
slider23:400<2,10000,1>-Filter env decay (ms)
slider24:30<0,100,1>-Filter env sustain (%)
slider25:300<2,10000,1>-Filter env release (ms)
slider26:2<0.5,10000,0.1>-Amp attack (ms)
slider27:300<2,10000,1>-Amp decay (ms)
slider28:90<0,100,1>-Amp sustain (%)
slider29:300<2,10000,1>-Amp release (ms)
slider30:50<0,100,1>-Amp velocity (%)
slider31:0<0,2000,1>-Glide (ms)
slider32:1<0,1,1{Mono,Poly}>-Mode
slider33:5<0.05,30,0.01>-LFO 1 rate (Hz)
slider34:0<0,3,1{Triangle,Saw,Square,Random}>-LFO 1 shape
slider35:0<0,3,1{Pitch,Cutoff,PW,Pan}>-LFO 1 destination
slider36:0<0,100,1>-LFO 1 amount (%)
slider37:0<0,6,1{Free,1 bar,1/2,1/4,1/8,1/16,1/8 triplet}>-LFO 1 sync
slider38:0.5<0.05,30,0.01>-LFO 2 rate (Hz)
slider39:0<0,3,1{Triangle,Saw,Square,Random}>-LFO 2 shape
slider40:1<0,3,1{Pitch,Cutoff,PW,Pan}>-LFO 2 destination
slider41:0<0,100,1>-LFO 2 amount (%)
slider42:0<0,6,1{Free,1 bar,1/2,1/4,1/8,1/16,1/8 triplet}>-LFO 2 sync
slider43:0<0,1,1{Off,On}>-Sequencer
slider44:1<0,5,1{1/32,1/16,1/8,1/4,1/16 triplet,1/8 triplet}>-Sequencer rate
slider45:1<0,1,1{Off,On}>-Sequencer retrig
slider46:0<0,4,1{PW,Resonance,Drive,Pan,Noise}>-Seq 4 destination
slider47:60<0,100,1>-Seq cutoff depth (%)
slider48:100<0,100,1>-Seq level depth (%)
slider49:50<0,100,1>-Seq 4 depth (%)
slider50:2<0,12,1>-Bend range (st)
slider51:0<-24,6,0.1>-Volume (dB)
slider52:30<0,100,1>-Drift (%)
slider53:0<0,16,1>-MIDI channel (0 = omni)
slider54:9<1,49,1>-Assign 1 (parameter)
slider55:18<1,49,1>-Assign 2 (parameter)
slider56:13<1,49,1>-Assign 3 (parameter)
slider57:19<1,49,1>-Assign 4 (parameter)
slider58:0<0,100,1>-Band sat (%)
slider59:30<0,100,1>-Bias (%)
slider60:120<40,400,1>-Sat low band (Hz)
slider61:6000<2000,16000,10>-Sat high band (Hz)
slider62:-0.3<-24,0,0.1>-Ceiling (dB)
slider63:0<0,1000000,1>-(edit counter)
slider64:prs_sel=0<0,19,1{No preset,Init,Fat Bass,Sub Bass,Stack Pad,Wide Strings,Seq Bass,Seq Motion,Acid Line,Pluck,Brass Stack,Drone,Wobble,Glide Lead,Dark Pad,Saw Stab,Bell Steps,Type Steps,Stack Steps,Shape Shifter}>-Preset

in_pin:none
out_pin:left output
out_pin:right output

@init
#ui_man = "gfx-lab-g-motra";
ext_noinit = 1;

// ---------- memory ----------
NPP = 49;                                           // sound parameters (sliders 1 - 49), what ASSIGN can pick
SEQ = 100000;                                       // 4 tracks x 16 steps (0..1)
FTL = SEQ + 64; STL = SEQ + 80;                     // the step lanes: filter type (0 = panel, 1..5), stack (0 = panel, 1..4)
PLO = 101000; PHI = 101100; PDEF = 101200; PLG = 101300; PST = 101400;   // range, default, log, stepped
VB = 110000; VS = 128; NVX = 8;                     // voices: 8 slots x 128
QO = 112000; QT = 112400; QN = 112800; QV = 113200; // MIDI event queue (400)
HN = 113600;                                        // mono: held-note stack (64)
HBL = 113700; HBR = 113800;                         // half-band decimator rings (64 each)
PTMP = 114000;                                      // preset parser scratch
memset(VB, 0, VS * NVX); memset(HBL, 0, 64); memset(HBR, 0, 64);

// ---------- the parameter table (range, default, log, stepped) and names (strings 1000 + slider) ----------
function prm(i, lo, hi, def, lg, st, nm) ( PLO[i] = lo; PHI[i] = hi; PDEF[i] = def; PLG[i] = lg; PST[i] = st; strcpy(1000 + i, nm); );
prm(1, 0, 2, 0, 0, 1, "DCO 1 WAVE"); prm(2, -24, 24, 0, 0, 1, "DCO 1 FREQ"); prm(3, 2, 98, 50, 0, 0, "DCO 1 PW");
prm(4, 0, 3, 1, 0, 1, "DCO 2 WAVE"); prm(5, -24, 24, 0, 0, 1, "DCO 2 FREQ"); prm(6, -50, 50, 7, 0, 0, "DCO 2 FINE"); prm(7, 2, 98, 50, 0, 0, "DCO 2 PW");
prm(8, 0, 100, 50, 0, 0, "DCO MIX"); prm(9, 0, 100, 0, 0, 0, "SUB"); prm(10, 0, 1, 0, 0, 1, "SUB OCTAVE"); prm(11, 0, 100, 0, 0, 0, "NOISE");
prm(12, 0, 3, 0, 0, 1, "STACK"); prm(13, 0, 50, 12, 0, 0, "DETUNE"); prm(14, 0, 100, 60, 0, 0, "SPREAD");
prm(15, 20, 16000, 2500, 1, 0, "CUTOFF"); prm(16, 0, 100, 20, 0, 0, "RESONANCE"); prm(17, 0, 4, 1, 0, 1, "FILTER TYPE"); prm(18, 0, 100, 25, 0, 0, "DRIVE");
prm(19, -100, 100, 45, 0, 0, "FILTER ENV"); prm(20, 0, 100, 40, 0, 0, "KEY TRACK"); prm(21, 0, 100, 30, 0, 0, "FILTER VEL");
prm(22, 0.5, 10000, 2, 1, 0, "F.ENV ATTACK"); prm(23, 2, 10000, 400, 1, 0, "F.ENV DECAY"); prm(24, 0, 100, 30, 0, 0, "F.ENV SUSTAIN"); prm(25, 2, 10000, 300, 1, 0, "F.ENV RELEASE");
prm(26, 0.5, 10000, 2, 1, 0, "AMP ATTACK"); prm(27, 2, 10000, 300, 1, 0, "AMP DECAY"); prm(28, 0, 100, 90, 0, 0, "AMP SUSTAIN"); prm(29, 2, 10000, 300, 1, 0, "AMP RELEASE");
prm(30, 0, 100, 50, 0, 0, "AMP VELOCITY"); prm(31, 0, 2000, 0, 0, 0, "GLIDE"); prm(32, 0, 1, 1, 0, 1, "MODE");
prm(33, 0.05, 30, 5, 1, 0, "LFO 1 RATE"); prm(34, 0, 3, 0, 0, 1, "LFO 1 SHAPE"); prm(35, 0, 3, 0, 0, 1, "LFO 1 DEST"); prm(36, 0, 100, 0, 0, 0, "LFO 1 AMOUNT"); prm(37, 0, 6, 0, 0, 1, "LFO 1 SYNC");
prm(38, 0.05, 30, 0.5, 1, 0, "LFO 2 RATE"); prm(39, 0, 3, 0, 0, 1, "LFO 2 SHAPE"); prm(40, 0, 3, 1, 0, 1, "LFO 2 DEST"); prm(41, 0, 100, 0, 0, 0, "LFO 2 AMOUNT"); prm(42, 0, 6, 0, 0, 1, "LFO 2 SYNC");
prm(43, 0, 1, 0, 0, 1, "SEQUENCER"); prm(44, 0, 5, 1, 0, 1, "SEQ RATE"); prm(45, 0, 1, 1, 0, 1, "SEQ RETRIG"); prm(46, 0, 4, 0, 0, 1, "SEQ 4 DEST");
prm(47, 0, 100, 60, 0, 0, "SEQ CUTOFF"); prm(48, 0, 100, 100, 0, 0, "SEQ LEVEL"); prm(49, 0, 100, 50, 0, 0, "SEQ 4 DEPTH");
prm(50, 0, 12, 2, 0, 1, "BEND RANGE"); prm(51, -24, 6, 0, 0, 0, "VOLUME"); prm(52, 0, 100, 30, 0, 0, "DRIFT"); prm(53, 0, 16, 0, 0, 1, "MIDI CHANNEL");

function seq_default() local(i) ( i = 0; loop(64, SEQ[i] = i < 16 ? 0 : i < 32 ? (i & 15) / 15 * 0.6 : i < 48 ? 1 : 0.5; i += 1; ); memset(FTL, 0, 32); );
seq_default();

// ---------- presets: "slider:value" changes from Init, and the sequence (pitch in semitones 0..24, the rest 0..99;
// then, if given, the FILTER lane 0..5 and the STACK lane 0..4, 0 = follow the panel) ----------
function mt_nums(s, dst) local(i, n, c, len, acc, neg, fr, scl, inn) (
  n = 0; i = 0; inn = 0; len = strlen(s);
  loop(len + 1,
    c = i < len ? str_getchar(s, i) : 32;
    c >= 48 && c <= 57 ? (
      !inn ? ( inn = 1; acc = 0; fr = 0; scl = 1; neg = 0; );
      fr ? ( scl *= 0.1; acc += (c - 48) * scl; ) : acc = acc * 10 + c - 48;
    ) : c == 46 ? (
      !inn ? ( inn = 1; acc = 0; scl = 1; neg = 0; ); fr = 1;
    ) : c == 45 ? (
      inn = 1; acc = 0; fr = 0; scl = 1; neg = 1;
    ) : (
      inn ? ( dst[n] = neg ? -acc : acc; n += 1; inn = 0; );
    );
    i += 1;
  );
  n;
);
#mt_pm = "Init|Fat Bass|Sub Bass|Stack Pad|Wide Strings|Seq Bass|Seq Motion|Acid Line|Pluck|Brass Stack|Drone|Wobble|Glide Lead|Dark Pad|Saw Stab|Bell Steps|Type Steps|Stack Steps|Shape Shifter";
function mt_pstr(p) (
  p == 2  ? "32:0 12:2 13:14 14:40 1:0 4:3 7:35 9:60 15:380 16:22 18:55 19:55 23:320 24:20 25:150 26:1 27:500 28:85 29:120 20:30" :
  p == 3  ? "32:0 12:1 13:8 14:20 1:2 3:50 4:2 9:85 10:0 15:240 16:10 18:60 19:30 23:450 24:30 28:100 29:140 31:30" :
  p == 4  ? "12:2 13:16 14:90 1:0 4:1 6:-6 15:1300 16:15 17:0 18:15 19:25 22:900 23:2500 24:60 25:2000 26:1200 28:100 29:2500 33:0.35 36:12 35:2 51:-2" :
  p == 5  ? "12:3 13:22 14:100 1:0 4:1 6:9 15:3400 16:8 17:0 18:10 19:10 22:400 23:1000 24:80 25:900 26:420 28:100 29:1200 38:5 40:0 41:4 51:-2" :
  p == 6  ? "32:0 12:1 13:9 1:0 4:3 7:30 9:50 15:300 16:45 18:50 19:40 23:200 24:10 25:120 26:0.5 27:250 28:80 29:100 43:1 44:1 45:1 47:70 48:100" :
  p == 7  ? "12:2 13:14 14:80 1:2 3:40 4:1 15:700 16:35 18:30 19:20 23:700 24:40 25:700 26:5 28:100 29:900 43:1 44:1 45:0 46:0 47:80 48:40 49:60 51:-2" :
  p == 8  ? "32:0 12:0 1:0 4:0 9:0 15:260 16:72 18:65 19:70 21:60 22:0.5 23:180 24:0 25:90 26:0.5 27:200 28:70 29:60 31:40 43:1 44:1 45:1 47:85" :
  p == 9  ? "12:1 13:7 14:70 1:0 4:3 7:40 15:600 16:20 18:20 19:75 21:60 22:0.5 23:320 24:0 25:280 26:0.5 27:600 28:0 29:500" :
  p == 10 ? "12:2 13:10 14:70 1:0 4:1 6:5 15:900 16:15 18:35 19:60 21:40 22:60 23:700 24:45 25:400 26:40 28:90 29:400 33:5.2 35:0 36:4" :
  p == 11 ? "32:0 12:3 13:26 14:100 1:0 4:1 5:-12 6:4 9:50 15:180 16:62 18:60 19:20 22:2000 23:3000 24:80 26:2500 28:100 29:4000 38:0.07 40:1 41:45 33:0.11 35:2 36:30" :
  p == 12 ? "32:0 12:1 13:10 1:2 3:45 4:1 6:-8 9:80 15:220 16:55 18:60 19:15 28:100 29:150 38:2 39:0 40:1 41:85 42:4 51:-3" :
  p == 13 ? "32:0 12:1 13:6 14:50 1:0 4:1 5:12 6:-5 15:2200 16:35 18:40 19:45 23:900 24:40 26:3 28:90 29:250 31:90 33:5.5 35:0 36:5" :
  p == 14 ? "12:3 13:18 14:100 1:2 3:30 4:2 6:-7 15:500 16:30 17:1 18:25 19:30 22:1800 23:3000 24:50 25:3000 26:1600 28:100 29:3000 38:0.2 40:1 41:25 51:-3" :
  p == 15 ? "12:2 13:12 14:70 1:0 4:1 5:7 6:3 15:800 16:30 18:45 19:80 21:50 22:0.5 23:260 24:0 25:200 26:0.5 27:400 28:0 29:300" :
  p == 16 ? "12:1 13:5 14:60 1:1 4:2 5:19 6:3 8:45 15:2600 16:30 20:100 19:30 22:0.5 23:900 24:0 25:900 26:0.5 27:1200 28:0 29:1100 43:1 44:2 45:1 47:30 48:60" :
  p == 17 ? "12:1 13:10 14:70 1:0 4:1 6:6 15:1400 16:50 18:30 19:15 23:600 24:60 25:500 26:4 28:100 29:600 43:1 44:1 45:0 47:25 48:20 51:-2" :
  p == 18 ? "32:0 12:3 13:18 14:80 1:0 4:1 6:5 9:40 15:420 16:30 18:50 19:45 23:260 24:20 25:150 26:0.5 27:300 28:85 29:120 43:1 44:1 45:1 47:50 48:100" :
  p == 19 ? "32:0 12:1 13:12 14:70 1:0 4:3 7:35 9:50 15:600 16:55 18:55 19:50 21:40 22:0.5 23:220 24:15 25:130 26:0.5 27:260 28:75 29:120 43:1 44:1 45:1 46:0 47:60 48:100 49:40" :
  "";
);
function mt_sstr(p) (
  p == 6  ? "0 0 12 0 0 0 10 0 0 0 12 0 0 3 5 7  20 60 35 90 25 70 40 99 20 60 35 90 25 70 40 99  99 60 80 60 99 60 80 60 99 60 80 60 99 70 90 70  50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50" :
  p == 7  ? "0 7 12 7 0 7 15 7 0 7 12 7 0 5 10 12  10 30 50 70 90 70 50 30 10 30 50 70 90 70 50 30  99 70 99 70 99 70 99 70 99 70 99 70 99 70 99 70  0 20 40 60 80 99 80 60 40 20 0 20 40 60 80 99" :
  p == 8  ? "0 0 12 0 3 0 15 0 0 0 12 10 0 7 0 12  10 50 90 20 60 30 99 40 10 70 20 80 30 99 50 20  99 60 99 60 99 99 60 99 60 99 60 99 99 60 99 70  50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50" :
  p == 16 ? "0 12 7 19 12 24 19 12 0 12 7 19 14 24 19 7  40 50 60 70 40 50 60 70 40 50 60 70 40 50 60 70  99 60 80 60 99 60 80 60 99 60 80 60 99 60 80 60  50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50" :
  // (the Lab presets add the FILTER lane, then the STACK lane)
  p == 17 ? "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  40 50 60 70 40 50 60 70 40 50 60 70 40 50 60 70  99 80 90 80 99 80 90 80 99 80 90 80 99 80 90 80  50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50  2 3 4 3 2 5 4 3 2 3 4 3 1 5 4 3  0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0" :
  p == 18 ? "0 0 12 0 0 0 7 0 0 0 12 0 0 10 12 7  20 40 70 30 20 40 80 30 20 40 70 30 20 60 90 50  99 70 90 70 99 70 90 70 99 70 90 70 99 80 99 80  50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50  0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0  1 1 2 1 1 1 3 1 1 1 2 1 1 4 4 2" :
  p == 19 ? "0 0 12 0 3 0 0 7 0 0 12 0 10 0 7 12  30 50 80 40 30 60 90 40 30 50 80 40 60 30 90 50  99 70 99 70 99 70 99 80 99 70 99 70 99 80 99 80  20 40 60 80 20 40 60 80 20 40 60 80 20 40 60 80  0 0 3 0 4 0 3 1 0 0 3 0 5 0 4 3  1 0 0 3 1 0 4 0 1 0 0 3 1 0 4 4" :
  "";
);
function mt_dirty() ( slider63 = (slider63 + 1) % 1000000; ui_auto(63); );
function mt_dirty_au() ( slider63 = (slider63 + 1) % 1000000; ui_auto(63); );
function load_preset(p) local(n, i) (
  i = 1; loop(49, i != 32 || p > 1 ? slider(i) = PDEF[i]; i += 1; );   // (Init leaves MODE as it is)
  n = mt_nums(mt_pstr(p), PTMP);
  i = 0; loop(floor(n / 2), PTMP[i * 2] >= 1 && PTMP[i * 2] <= 49 ? slider(PTMP[i * 2]) = PTMP[i * 2 + 1]; i += 1; );
  slider51 = PDEF[51]; i = 0; loop(floor(n / 2), PTMP[i * 2] == 51 ? slider51 = PTMP[i * 2 + 1]; i += 1; );
  n = mt_nums(mt_sstr(p), PTMP);
  n > 0 ? ( i = 0; loop(64, SEQ[i] = i < n ? PTMP[i] / (i < 16 ? 24 : 99) : 0; i += 1; );
            i = 0; loop(32, FTL[i] = 64 + i < n ? PTMP[64 + i] : 0; i += 1; ); ) : seq_default();
  i = 1; loop(51, ui_auto(i); i += 1; );
  pr_cur = p; mt_dirty_au();
);

// ---------- the filter: four TPT one-poles with global resonance, the input stage saturating (one voice) ----------
// p[40..43] stage states; g = tan(pi fc / fs2); k = 0..~4.3. The output is a mix of the input stage and the four
// stage outputs (fm_a .. fm_e, gliding, shared by the voices), with a resonance make-up (1 + fm_k k): the 2-pole
// low-pass taps stage 2 inside the same loop, so every type still sings.
function mt_flt(p, x, g, k) local(ga, ga2, ga4, sa, u, v, y1, y2, y3, y4, h) (
  h = 1 / (1 + g); ga = g * h; ga2 = ga * ga; ga4 = ga2 * ga2;
  sa = ga2 * ga * p[40] * h + ga2 * p[41] * h + ga * p[42] * h + p[43] * h;
  u = (x - k * sa) / (1 + k * ga4);
  u = vo_tanh(u);
  v = (u - p[40]) * ga; y1 = v + p[40]; p[40] = y1 + v;
  v = (y1 - p[41]) * ga; y2 = v + p[41]; p[41] = y2 + v;
  v = (y2 - p[42]) * ga; y3 = v + p[42]; p[42] = y3 + v;
  v = (y3 - p[43]) * ga; y4 = v + p[43]; p[43] = y4 + v;
  (fm_a * u + fm_b * y1 + fm_c * y2 + fm_d * y3 + fm_e * y4) * (1 + fm_k * k);
);
// the stage mix of each type: 2-pole LP, 4-pole LP, band-pass, high-pass, notch
function ft_set(t) (
  t < 0.5 ? ( ft_a = 0; ft_b = 0; ft_c = 1; ft_d = 0; ft_e = 0; ft_k = 0.15; ) :
  t < 1.5 ? ( ft_a = 0; ft_b = 0; ft_c = 0; ft_d = 0; ft_e = 1; ft_k = 0.3; ) :
  t < 2.5 ? ( ft_a = 0; ft_b = 2; ft_c = -2; ft_d = 0; ft_e = 0; ft_k = 0.15; ) :
  t < 3.5 ? ( ft_a = 1; ft_b = -2; ft_c = 1; ft_d = 0; ft_e = 0; ft_k = 0; ) :
            ( ft_a = 1; ft_b = -2; ft_c = 2; ft_d = 0; ft_e = 0; ft_k = 0.08; );
);
function ft_snap() ( fm_a = ft_a; fm_b = ft_b; fm_c = ft_c; fm_d = ft_d; fm_e = ft_e; fm_k = ft_k; );

// half-band decimator (39 taps, equiripple, > 63 dB stop band): ring[(w - j) & 63], newest 2x sample at j = 0
function mt_hb(b, w) (
  0.5003175242 * b[(w - 19) & 63]
  + 0.3165830275 * (b[(w - 18) & 63] + b[(w - 20) & 63])
  - 0.1004920620 * (b[(w - 16) & 63] + b[(w - 22) & 63])
  + 0.0546158895 * (b[(w - 14) & 63] + b[(w - 24) & 63])
  - 0.0335169601 * (b[(w - 12) & 63] + b[(w - 26) & 63])
  + 0.0211574377 * (b[(w - 10) & 63] + b[(w - 28) & 63])
  - 0.0131712778 * (b[(w - 8) & 63] + b[(w - 30) & 63])
  + 0.0078701971 * (b[(w - 6) & 63] + b[(w - 32) & 63])
  - 0.0043811777 * (b[(w - 4) & 63] + b[(w - 34) & 63])
  + 0.0021893628 * (b[(w - 2) & 63] + b[(w - 36) & 63])
  - 0.0010131992 * (b[(w - 0) & 63] + b[(w - 38) & 63]);
);

// ---------- voice slots (p = VB + VS x v) ----------
//   0 on   1 note   2 gate (1 key down, 2 held by the pedal)   3 age   4 velocity 0..1   5 stack position -1..1
//   6 fresh (snap the ramps)   7 fade samples left (taken for a new note)   8 / 9 / 10 / 11 the waiting note, its
//   velocity, stack position, gate   12 pitch now (glide)   13 pitch target   14 per-note detune (cents)
//   16 / 17 DCO phases   18 sub phase   20 / 21 increments   22 / 23 their steps   24 / 25 PW   26 / 27 steps
//   28 g   29 step   30 k   31 step   32 amp   33 step   34 / 35 pan gains L / R
//   37 / 38 filter env (stage, level)   39 / 44 amp env (stage, level)   40..43 filter stages
//   45..47 drift wander (DCO 1, DCO 2, cutoff)   48..50 its targets   51 its timer   52..54 the voice's own offsets
//   15 its copy in the stack (0..3)   55 its stack level (0..1: the STACK lane fades copies in and out)
//   (5, the stack position, now glides to where the copy sits in the stack sounding now; 10 holds the waiting copy)

function v_ptr(i) ( VB + i * VS; );

// ADSR at control rate (n samples per tick): stage 1 attack (linear, from where it stands), 2 decay / sustain,
// 3 release, 0 idle. Returns the level; the new stage is left in mt_es.
function mt_env(st_, lv_, a, d, s, r, n) (
  st_ == 1 ? ( lv_ += n / max(1, a * 0.001 * srate); lv_ >= 1 ? ( lv_ = 1; st_ = 2; ); ) :
  st_ == 2 ? ( lv_ = s + (lv_ - s) * exp(-4.6 * n / (d * 0.001 * srate)); ) :
  st_ == 3 ? ( lv_ *= exp(-4.6 * n / (r * 0.001 * srate)); lv_ < 0.0001 ? ( lv_ = 0; st_ = 0; ); );
  mt_es = st_; lv_;
);

// one voice's control tick (every 16 samples)
function mt_tick(p, n) local(e1, e2, base, p1, p2, f, oct, fc, g, kk, amp, t, pw1, pw2, pan, NR, on, ps) (
  e1 = mt_env(p[37], p[38], slider22, slider23, slider24 * 0.01, slider25, n); p[37] = mt_es; p[38] = e1;
  e2 = mt_env(p[39], p[44], slider26, slider27, slider28 * 0.01, slider29, n); p[39] = mt_es; p[44] = e2;
  p[39] == 0 && p[7] <= 0 ? p[0] = 0;                 // the amp envelope has finished: the voice is free
  p[12] += (p[13] - p[12]) * glide_k;
  // the stack sounding now (the STACK lane): copies past it fade out where they stand, the rest re-spread
  on = p[15] < stk_act;
  ps = stk_act > 1 ? 2 * min(p[15], stk_act - 1) / (stk_act - 1) - 1 : 0;
  p[6] ? ( p[55] = on; p[5] = ps; ) : ( p[55] += (on - p[55]) * stk_k; on ? p[5] += (ps - p[5]) * stk_pk; );
  // drift wander: a new target every 1.5 - 4 s, glided to
  p[51] -= n; p[51] <= 0 ? ( p[51] = (1.5 + 2.5 * rand(1)) * srate / max(dm_s, 0.3); p[48] = rand(2) - 1; p[49] = rand(2) - 1; p[50] = rand(2) - 1; );
  t = n / (1.2 * srate); p[45] += (p[48] * 9 - p[45]) * t; p[46] += (p[49] * 9 - p[46]) * t; p[47] += (p[50] * 0.12 - p[47]) * t;
  // pitch: note, glide, bend, the stack, the sequencer, LFO pitch, drift
  base = p[12] - 69 + bend_st + sq_pitch + lfo_pitch + (p[14] + p[5] * stk_det) / 100;
  p1 = base + slider2 + (p[52] + p[45]) * dr_amt / 100;
  p2 = base + slider5 + slider6 / 100 + (p[53] + p[46]) * dr_amt / 100 + ins_aux;
  NR = 32;                                            // ramp steps: 16 samples at 2x
  f = min(440 * 2 ^ (p1 / 12) * ins_pitch / (2 * srate), 0.24); p[22] = (f - p[20]) / NR; p[6] ? ( p[20] = f; p[22] = 0; );
  f = min(440 * 2 ^ (p2 / 12) * ins_pitch / (2 * srate), 0.24); p[23] = (f - p[21]) / NR; p[6] ? ( p[21] = f; p[23] = 0; );
  pw1 = min(max(slider3 * 0.01 + lfo_pw + sq_pw, 0.03), 0.97); p[26] = (pw1 - p[24]) / NR; p[6] ? ( p[24] = pw1; p[26] = 0; );
  pw2 = min(max(slider7 * 0.01 + lfo_pw + sq_pw, 0.03), 0.97); p[27] = (pw2 - p[25]) / NR; p[6] ? ( p[25] = pw2; p[27] = 0; );
  // filter
  oct = fl_ev * 0.01 * 7 * e1 * (1 - slider21 * 0.01 + slider21 * 0.01 * p[4]) + slider20 * 0.01 * (p[12] - 60) / 12
        + lfo_cut + sq_cut + ins_cut + (p[54] + p[47]) * dr_amt - sag * 0.15;
  fc = min(max(exp(fl_c2 + oct * 0.693147), 15), srate * 0.9);
  g = tan($pi * min(fc, 0.45 * 2 * srate) / (2 * srate)); p[29] = (g - p[28]) / NR; p[6] ? ( p[28] = g; p[29] = 0; );
  kk = min(fl_rs * 0.01 + sq_res, 1) * 4.3; p[31] = (kk - p[30]) / NR; p[6] ? ( p[30] = kk; p[31] = 0; );
  // amp (a voice fading out for a new note is handled per sample)
  amp = e2 * (1 - slider30 * 0.01 + slider30 * 0.01 * p[4]) * sq_lvl * stk_gs * p[55];
  p[7] <= 0 ? p[33] = (amp - p[32]) / NR;
  pan = min(max(p[5] * stk_spr + lfo_pan + sq_pan, -1), 1);
  p[34] = sqrt(0.5 * (1 - pan)); p[35] = sqrt(0.5 * (1 + pan));
  p[6] = 0;
);

// start a voice now (it is silent: fresh, or faded out)
function v_go(p, n, vel, c) (
  p[16] = rand(1); p[17] = rand(1); p[18] = p[16] * 0.5;
  p[40] = p[41] = p[42] = p[43] = 0; p[32] = 0; p[33] = 0;
  p[1] = n; p[13] = n; p[2] = 1; p[4] = vel / 127; p[15] = c; p[6] = 1; p[7] = 0;
  p[12] = mono_mode && glide_k < 1 && last_note >= 0 ? last_note : n;
  p[14] = (rand(2) - 1) * 6 * dr_amt;
  p[37] = 1; p[38] = 0; p[39] = 1; p[44] = 0;
  p[0] = 1; vage += 1; p[3] = vage;
);
// put a note on voice i: straight away when it is silent, else after a 4 ms fade
function v_note(i, n, vel, c) local(p) (
  p = v_ptr(i);
  p[0] && p[32] > 0.0005 ? (
    p[7] = floor(0.004 * srate); p[8] = n; p[9] = vel; p[10] = c; p[11] = 1;
    p[1] = n; p[2] = 1; vage += 1; p[3] = vage;
  ) : v_go(p, n, vel, c);
);
// retrigger the envelopes (RETRIG on a sequencer step), from where they stand
function v_retrig(p) ( p[37] = 1; p[39] = 1; );
function v_release(p) ( p[2] = 0; p[11] = 0; p[37] > 0 ? p[37] = 3; p[39] > 0 ? p[39] = 3; );

// ---------- notes ----------
function mono_play(n, vel, retrig) local(i, p) (
  i = 0;
  loop(stk_al,
    p = v_ptr(i);
    retrig || !p[0] || p[7] > 0 ? v_note(i, n, vel, i) : ( p[13] = n; p[1] = n; p[2] = 1; );
    i += 1;
  );
  last_note = n;
);
function mono_stop() local(i, p) ( i = 0; loop(NVX, p = v_ptr(i); v_release(p); i += 1; ); );
function st_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 note_on(n, vel) local(c, i, b, ba, p, had) (
  mono_mode ? (
    had = hcount > 0; st_del(n); hcount < 64 ? ( HN[hcount] = n; hcount += 1; );
    mono_play(n, vel, !had);
  ) : (
    c = 0;
    loop(stk_al,
      // a free voice first, else the one released longest ago, else the oldest
      b = -1; i = 0; loop(NVX, p = v_ptr(i); b < 0 && !p[0] ? b = i; i += 1; );
      b < 0 ? ( ba = 0; i = 0; loop(NVX, p = v_ptr(i); !p[2] && (b < 0 || p[3] < ba) ? ( b = i; ba = p[3]; ); i += 1; ); );
      b < 0 ? ( ba = 0; i = 0; loop(NVX, p = v_ptr(i); b < 0 || p[3] < ba ? ( b = i; ba = p[3]; ); i += 1; ); );
      v_note(b, n, vel, c);
      c += 1;
    );
  );
);
function note_off(n) local(i, p) (
  mono_mode ? (
    st_del(n);
    hcount > 0 ? mono_play(HN[hcount - 1], 100, 0) : pedal ? mhold = 1 : mono_stop();
  ) : (
    i = 0; loop(NVX, p = v_ptr(i); p[1] == n && p[2] == 1 ? ( pedal ? p[2] = 2 : v_release(p); ); i += 1; );
  );
);
function pedal_up() local(i, p) (
  i = 0; loop(NVX, p = v_ptr(i); p[2] == 2 ? v_release(p); i += 1; );
  mhold && hcount == 0 ? mono_stop(); mhold = 0;
);
function all_off() ( mono_stop(); hcount = 0; mhold = 0; pedal = 0; );
function q_push(ofs, t, n, v) local(i) (
  qcount < 400 ? (
    i = qcount; while (i > 0 && QO[i - 1] > ofs) ( QO[i] = QO[i - 1]; QT[i] = QT[i - 1]; QN[i] = QN[i - 1]; QV[i] = QV[i - 1]; i -= 1; );
    QO[i] = ofs; QT[i] = t; QN[i] = n; QV[i] = v; qcount += 1;
  );
);

// ---------- the unit ----------
function lfo_div(s) ( s == 1 ? 4 : s == 2 ? 2 : s == 3 ? 1 : s == 4 ? 0.5 : s == 5 ? 0.25 : 1 / 3; );   // beats per cycle
function update() (
  mono_mode = slider32 < 0.5;
  stk_n = floor(slider12 + 0.5) + 1;
  stk_det = slider13 * 0.5; stk_spr = slider14 * 0.01;
  // a note takes as many voices as the STACK lane's biggest step (while the sequencer is on)
  stk_al = stk_n;
  slider43 >= 0.5 ? ( stk_al = 0; stl_i = 0; loop(16, stk_al = max(stk_al, STL[stl_i] > 0.5 ? min(floor(STL[stl_i] + 0.5), 4) : stk_n); stl_i += 1; ); );
  stk_m = mono_mode ? 0.62 : 0.34;                           // (POLY: several notes sum)
  glide_k = slider31 > 0 ? 1 - exp(-4.6 * 16 / (slider31 * 0.001 * srate)) : 1;
  chan = slider53;
  vol_t = 10 ^ (slider51 / 20);
  drift_macro(slider52); dr_amt = dm_a;
  ins_output_setup(58);   // output stage: CLIP, always on
);

ins_drift_init();
fl_ok = 0;
ost_init(20000); ostL.ost_ch_init(); ostR.ost_ch_init();
gi = 0; loop(NVX, p_ = v_ptr(gi); p_[52] = (rand(2) - 1) * 5; p_[53] = (rand(2) - 1) * 5; p_[54] = (rand(2) - 1) * 0.08; p_[51] = rand(1) * srate; gi += 1; );
sm = 1 - exp(-1 / (0.03 * srate)); dc_r = 1 - 2 * $pi * 5 / srate; dcxl = dcyl = dcxr = dcyr = 0; sagc = 1 - exp(-1 / (0.15 * srate));
qcount = 0; vage = 0; pedal = 0; hcount = 0; mhold = 0; hw = 0; sag = 0; sage = 0; last_note = -1; ctl = 0;
sq_run = 0; sq_ph = 0; sq_step = -1; mw = 0; bend_st = 0;
sq_pitch = 0; sq_cut = 0; sq_lvl = 1; sq_pw = sq_res = sq_drv = sq_pan = sq_nz = 0;
lfo_pitch = lfo_cut = lfo_pw = lfo_pan = 0; lf1 = lf2 = 0; ls1 = ls2 = 0;
!ui_dflt ? ( ui_sz = 2; pr_cur = 1; mt_page = 0; );   // a new instance opens Large, on Init, on the sequencer page
update(); last_mono = mono_mode; last_stk = stk_al;
// the step lanes: what sounds now (type, stack), the stack gain and the glides (type 10 ms, stack 6 ms, spread 20 ms)
sq_ft = slider17; sq_sn = stk_n; stk_act = stk_n; stk_gs = stk_m / sqrt(stk_n); ft_set(sq_ft); ft_snap();
ft_g = 1 - exp(-1 / (0.010 * srate)); stk_k = 1 - exp(-16 / (0.006 * srate)); stk_pk = 1 - exp(-16 / (0.020 * srate));
vol_s = vol_t;
ui_dflt = 1;

// PRESET parameter (JG, 28 Oct 2026: accessibility - a screen reader reaches the presets through REAPER's parameter list).
// A change of prs_sel (a screen reader, the host, automation) loads that preset (0 = No preset: nothing loaded); the preset bar asks through
// prs_dirty. Both load here, on the audio side only. prs_last is saved, so opening a project or recalling its
// state never reloads a preset over the saved settings.
// the @slider work, also run after a preset swap in @block (PRESET parameter)
function prs_sl() (
update();
);
function prs_chk() local(k) (
  prs_dirty > 0 ? ( prs_pk = prs_dirty; prs_dirty = 0; prs_q = 1; ) : prs_ok ? (
    prs_sel = max(0, min(19, prs_sel | 0));
    prs_sel != prs_last ? ( prs_last = prs_sel; prs_sel > 0 ? ( prs_pk = prs_sel; prs_q = 0; ); );
  ) : ( prs_last = prs_sel | 0; prs_ok = 1; );
  // PRESET parameter: the swap ducks 5 ms, loads at silence (at the top of a block) and comes back over 30 ms
  prs_pk > 0 ? (
    prs_g <= 0 ? (
      k = prs_pk; prs_pk = 0; load_preset(k); prs_sl();
      prs_q ? ( k <= 19 ? ( prs_sel = k; slider_automate(prs_sel); prs_cur_d = k; ); prs_last = prs_sel; ) : prs_cur_d = k;
      prs_gs = 1 / (0.030 * srate);
    ) : prs_gs = -1 / (0.005 * srate);
  );
);
prs_ok = 0; prs_dirty = 0; prs_pk = 0; prs_g = 1; prs_gs = 0;

@slider
prs_chk();
prs_sl();

@block
prs_chk();
update();
// the filter glide: two 15 ms stages on the cutoff, 15 ms on RESO and ENV (starts on its targets)
fl_lc = log(slider15);
fl_k = 1 - exp(-1 / (0.015 * srate));
!fl_ok ? ( fl_c1 = fl_c2 = fl_lc; fl_rs = slider16; fl_ev = slider19; fl_ok = 1; );
mono_mode != last_mono || stk_al != last_stk ? ( all_off(); last_mono = mono_mode; last_stk = stk_al; );

qcount = 0;
while (midirecv(mofs, m1, m2, m3)) (
  ins_chan_ok(m1, chan) ? (
    mst = m1 & 0xF0;
    mst == 0x90 || mst == 0x80 ? ( q_push(mofs, mst == 0x90 && m3 > 0, m2, m3); mst == 0x90 && m3 > 0 ? ins_midi_hit(); ) :
    mst == 0xE0 ? bend_st = (((m3 * 128 + m2) - 8192) / 8192) * slider50 :
    mst == 0xB0 ? (
      m2 == 1 ? mw = m3 / 127 :
      m2 == 64 ? ( pedal = m3 >= 64; !pedal ? q_push(mofs, 2, 0, 0); ) :
      m2 == 123 || m2 == 120 ? q_push(mofs, 3, 0, 0);
    );
  );
  midisend(mofs, m1, m2, m3);
);
// transport stop: release whatever is still held (as an all-notes-off would; a safety net if the stop's note-offs go missing)
gfx_pst = play_state == 1 || play_state == 5;
gfx_pst_prev && !gfx_pst ? ( q_push(0, 3, 0, 0); );
gfx_pst_prev = gfx_pst;
qi = 0; spos = 0;
ins_drift_block(slider52 / 100, samplesblock);
sag = sage * dr_amt;
// the sequencer clock (steps per beat); in time with the project while it plays
sq_spb = slider44 < 0.5 ? 8 : slider44 < 1.5 ? 4 : slider44 < 2.5 ? 2 : slider44 < 3.5 ? 1 : slider44 < 4.5 ? 6 : 3;
sq_inc = sq_spb * max(tempo, 20) / 60 / srate;
(play_state & 1) && sq_run ? sq_ph = beat_position * sq_spb;
// LFO rates (the tempo-synced ones from the project tempo, and in phase with it while it plays)
lf1r = slider37 > 0.5 ? max(tempo, 20) / 60 / lfo_div(floor(slider37 + 0.5)) : slider33;
lf2r = slider42 > 0.5 ? max(tempo, 20) / 60 / lfo_div(floor(slider42 + 0.5)) : slider38;
(play_state & 1) ? (
  slider37 > 0.5 ? ( lf1 = beat_position / lfo_div(floor(slider37 + 0.5)); lf1 -= floor(lf1); );
  slider42 > 0.5 ? ( lf2 = beat_position / lfo_div(floor(slider42 + 0.5)); lf2 -= floor(lf2); );
);

@serialize
file_var(0, ui_sz);
file_var(0, pr_cur);
file_var(0, mt_page);
file_mem(0, SEQ, 64);
file_avail(0) != 0 ? file_var(0, prs_last) : prs_last = prs_sel | 0; prs_ok = 1;   // PRESET parameter: no reload on recall

@sample
while (qi < qcount && (QO[qi] <= spos || spos >= samplesblock - 1)) (   // (an event stamped at / past the block end: handled at its last sample)
  evt = QT[qi];
  evt == 1 ? note_on(QN[qi], QV[qi]) : evt == 0 ? note_off(QN[qi]) : evt == 2 ? pedal_up() : all_off();
  qi += 1;
);
spos += 1;
// CUTOFF, RESO and ENV glide per sample, so a turned knob or automation sweeps smoothly (never in steps)
fl_c1 += (fl_lc - fl_c1) * fl_k; fl_c2 += (fl_c1 - fl_c2) * fl_k; fl_rs += (slider16 - fl_rs) * fl_k; fl_ev += (slider19 - fl_ev) * fl_k;
// the filter type glides per sample (the stage mix), so a step that changes type never clicks
fm_a += (ft_a - fm_a) * ft_g; fm_b += (ft_b - fm_b) * ft_g; fm_c += (ft_c - fm_c) * ft_g;
fm_d += (ft_d - fm_d) * ft_g; fm_e += (ft_e - fm_e) * ft_g; fm_k += (ft_k - fm_k) * ft_g;

// ---------- control rate (every 16 samples): the LFOs and the sequencer, shared by the voices ----------
(ctl -= 1) <= 0 ? (
  ctl = 16;
  lf1 += lf1r * 16 / srate; lf1 >= 1 ? ( lf1 -= floor(lf1); ls1 = rand(2) - 1; );
  lf2 += lf2r * 16 / srate; lf2 >= 1 ? ( lf2 -= floor(lf2); ls2 = rand(2) - 1; );
  sh_ = slider34; lfo1v = sh_ < 0.5 ? 1 - 4 * abs(lf1 - 0.5) : sh_ < 1.5 ? 2 * lf1 - 1 : sh_ < 2.5 ? (lf1 < 0.5 ? 1 : -1) : ls1;
  sh_ = slider39; lfo2v = sh_ < 0.5 ? 1 - 4 * abs(lf2 - 0.5) : sh_ < 1.5 ? 2 * lf2 - 1 : sh_ < 2.5 ? (lf2 < 0.5 ? 1 : -1) : ls2;
  a1_ = min(slider36 * 0.01 + mw, 1) * lfo1v; a2_ = slider41 * 0.01 * lfo2v;
  lfo_pitch = lfo_cut = lfo_pw = lfo_pan = 0;
  // (pitch: fine vibrato at small amounts, up to an octave at full)
  d_ = slider35; d_ < 0.5 ? lfo_pitch += a1_ * a1_ * a1_ * 12 + a1_ * 0.3 : d_ < 1.5 ? lfo_cut += a1_ * 4 : d_ < 2.5 ? lfo_pw += a1_ * 0.45 : lfo_pan += a1_;
  d_ = slider40; d_ < 0.5 ? lfo_pitch += a2_ * a2_ * a2_ * 12 + a2_ * 0.3 : d_ < 1.5 ? lfo_cut += a2_ * 4 : d_ < 2.5 ? lfo_pw += a2_ * 0.45 : lfo_pan += a2_;
  // the sequencer: runs while a key is held
  any_key = 0; snd_ = 0; vi_ = 0; loop(NVX, vp_ = v_ptr(vi_); vp_[2] ? any_key = 1; vp_[0] ? snd_ = max(snd_, vp_[32]); vi_ += 1; );
  run_ = slider43 >= 0.5 && any_key;
  run_ && !sq_run ? ( sq_ph = (play_state & 1) ? beat_position * sq_spb : 0; sq_step = -1; );
  sq_run = run_;
  sq_run ? (
    sq_ph += sq_inc * 16;
    st_ = floor(sq_ph) & 15;
    st_ != sq_step ? (
      sq_step = st_;
      slider45 >= 0.5 ? ( vi_ = 0; loop(NVX, vp_ = v_ptr(vi_); vp_[2] && vp_[7] <= 0 ? v_retrig(vp_); vi_ += 1; ); );
    );
    sq_pitch = floor(SEQ[sq_step] * 24 + 0.5);
    sq_cut = SEQ[16 + sq_step] * slider47 * 0.01 * 4;
    sq_lvl = 1 - slider48 * 0.01 + slider48 * 0.01 * SEQ[32 + sq_step];
    v4_ = SEQ[48 + sq_step] * slider49 * 0.01; d_ = slider46;
    sq_pw = d_ < 0.5 ? (v4_ - 0.5 * slider49 * 0.01) * 0.9 : 0;
    sq_res = d_ >= 0.5 && d_ < 1.5 ? v4_ : 0;
    sq_drv = d_ >= 1.5 && d_ < 2.5 ? v4_ : 0;
    sq_pan = d_ >= 2.5 && d_ < 3.5 ? v4_ * 2 - slider49 * 0.01 : 0;
    sq_nz = d_ >= 3.5 ? v4_ : 0;
    // the step lanes (0 = follow the panel)
    sq_ft = FTL[sq_step] > 0.5 ? floor(FTL[sq_step] + 0.5) - 1 : slider17;
    sq_sn = STL[sq_step] > 0.5 ? floor(STL[sq_step] + 0.5) : stk_n;
  ) : (
    sq_step = -1; sq_pitch = 0; sq_cut = 0; sq_lvl = 1; sq_pw = sq_res = sq_drv = sq_pan = sq_nz = 0;
    // (a note ringing out keeps the last step's filter type and stack; back to the panel once all is quiet)
    snd_ < 0.0005 || slider43 < 0.5 ? ( sq_ft = slider17; sq_sn = stk_n; );
  );
  // the filter type and the stack sounding now; with nothing sounding they jump straight there
  ft_set(sq_ft);
  stk_act = min(max(sq_sn, 1), stk_al);
  stk_gs += (stk_m / sqrt(stk_act) - stk_gs) * stk_k;
  snd_ < 0.0005 ? ( ft_snap(); stk_gs = stk_m / sqrt(stk_act); );
  // the mixer (shared)
  mx2_ = slider8 * 0.01; lv1 = 1 - mx2_; lv2 = slider4 > 0.5 ? mx2_ : 0;
  lvs = slider9 * 0.01; lvn = min(slider11 * 0.01 + sq_nz, 1); w1 = floor(slider1 + 0.5); w2 = floor(slider4 + 0.5);
  sdiv = slider10 < 0.5 ? 0.5 : 0.25;
  drv = (1 + 5 * min(slider18 * 0.01 + sq_drv, 1)) * (1 + sag * 0.35);
  vi_ = 0; loop(NVX, vp_ = v_ptr(vi_); vp_[0] ? mt_tick(vp_, 16); vi_ += 1; );
);

L0 = L1 = R0 = R1 = 0;
p = VB;
loop(NVX,
  p[0] ? (
    // a voice taken for a new note: 4 ms down to silence, then the new note starts clean
    p[7] > 0 ? (
      p[32] *= 1 - 1 / p[7]; p[33] = 0; p[7] -= 1;
      p[7] <= 0 ? ( v_go(p, p[8], p[9], p[10]); p[2] = p[11]; mt_tick(p, 16); );
    );
    // a stack copy the STACK lane has faded out rests (silent, its filter cleared) until it is wanted again
    p[55] < 0.0005 && p[15] >= stk_act && p[7] <= 0 ? ( p[40] = p[41] = p[42] = p[43] = 0; k = 2; ) : k = 0;
    loop(2 - k,
      p[20] += p[22]; p[21] += p[23]; p[24] += p[26]; p[25] += p[27];
      p[28] += p[29]; p[30] += p[31]; p[32] += p[33];
      // DCO 1 and the sub
      i1 = p[20]; ph = p[16] + i1; ph -= floor(ph); p[16] = ph;
      o1 = w1 == 0 ? 2 * ph - 1 - vo_blep(ph, i1) :
           w1 == 1 ? (ph < 0.5 ? 4 * ph - 1 : 3 - 4 * ph) :
           ( p2_ = ph + 1 - p[24]; p2_ -= floor(p2_); 0.8 * ((2 * ph - 1 - vo_blep(ph, i1)) - (2 * p2_ - 1 - vo_blep(p2_, i1))); );
      si = i1 * sdiv; sp = p[18] + si; sp -= floor(sp); p[18] = sp;
      lvs > 0.0001 ? ( p2_ = sp + 0.5; p2_ -= floor(p2_); s1 = (2 * sp - 1 - vo_blep(sp, si)) - (2 * p2_ - 1 - vo_blep(p2_, si)); ) : s1 = 0;
      // DCO 2
      i2 = p[21]; ph = p[17] + i2; ph -= floor(ph); p[17] = ph;
      o2 = w2 == 0 ? 0 : w2 == 1 ? 2 * ph - 1 - vo_blep(ph, i2) :
           w2 == 2 ? (ph < 0.5 ? 4 * ph - 1 : 3 - 4 * ph) :
           ( p2_ = ph + 1 - p[25]; p2_ -= floor(p2_); 0.8 * ((2 * ph - 1 - vo_blep(ph, i2)) - (2 * p2_ - 1 - vo_blep(p2_, i2))); );
      x = o1 * lv1 + o2 * lv2 + 0.5 * s1 * lvs;
      lvn > 0.0001 ? x += (rand(2) - 1) * lvn;
      y = mt_flt(p, x * 0.55 * drv, p[28], p[30]) * p[32];
      k == 0 ? ( L0 += y * p[34]; R0 += y * p[35]; ) : ( L1 += y * p[34]; R1 += y * p[35]; );
      k += 1;
    );
  );
  p += VS;
);
// ---------- decimate the 2x voices ----------
HBL[hw] = L0; HBR[hw] = R0; hw = (hw + 1) & 63;
HBL[hw] = L1; HBR[hw] = R1;
oL = mt_hb(HBL, hw); oR = mt_hb(HBR, hw);
hw = (hw + 1) & 63;
// 5 Hz DC blocker (pulse waves at uneven widths and the driven ladder carry some DC)
dcx = oL; oL = oL - dcxl + dc_r * dcyl; dcxl = dcx; dcyl = oL;
dcx = oR; oR = oR - dcxr + dc_r * dcyr; dcxr = dcx; dcyr = oR;
mt_pre = max(mt_pre, max(abs(oL), abs(oR)));          // (the level before the output stage)
sage += (min(abs(oL) + abs(oR), 2) - sage) * sagc;
vol_s += (vol_t - vol_s) * sm;
oL = ostL.ost_band(oL) * vol_s; oR = ostR.ost_band(oR) * vol_s;
ost_out(oL, oR);
spl0 = ost_l; spl1 = ost_r;
mt_pk_d = max(mt_pk_d, max(abs(spl0), abs(spl1)));
prs_gs != 0 ? ( prs_g += prs_gs; prs_g <= 0 ? ( prs_g = 0; prs_gs = 0; ) : prs_g >= 1 ? ( prs_g = 1; prs_gs = 0; ); );
prs_g < 1 ? ( prs_w = prs_g * prs_g * (3 - 2 * prs_g); spl0 *= prs_w; spl1 *= prs_w; );   // PRESET parameter duck: the swap ducks 5 ms, loads at silence (at the top of a block) and comes back over 30 ms

@gfx 960 680
// =====================================================================
// GUI - design space 1200 x 850 at qs = 0.8 x sc. A starship hull in the spirit of the 70s sci-fi paperback
// paintings: off-white plating with seams and rivets, bold livery blocks (orange, teal, red, hazard yellow),
// hazard stripes and a stencilled hull code; the name rail is an airbrushed deep-space sky.
// Top: the performance controls (CUTOFF, RESONANCE, ATTACK, DECAY / RELEASE, four ASSIGN knobs, the voice
// lights, MODE and STACK). Below: the SEQUENCER page (four tracks and the FILTER / STACK step lanes), or every
// parameter behind EDIT.
// Bottom: the PLAY strip. Knob values show on hover / drag (house style).
// =====================================================================
ui_begin(960, 680);
qs = sc * 0.8;
gfx_setfont(8, "Trebuchet MS", 13 * qs, 'b');        // small print, labels
gfx_setfont(9, "Arial Black", 15 * qs);               // section titles (stencil livery)
gfx_setfont(10, "Arial Black", 46 * qs);              // the name
gfx_setfont(11, "Trebuchet MS", 15 * qs, 'b');        // keys
gfx_setfont(12, "Arial Black", 22 * qs);              // hull code
gfx_setfont(13, "Trebuchet MS", 12 * qs, 'b');        // tiny print
gfx_setfont(14, "Trebuchet MS", 15 * qs, 'b');        // labels and info, the larger print

HULL = 0xE7E1D2; HULL2 = 0xD8D1BF; SEAM = 0xB9B19C; INK = 0x1B1B1C; INK2 = 0x5A564C;
ORG = 0xE8692A; TEAL = 0x1F6F78; RED = 0xC8352B; YEL = 0xF2C230; NAVY = 0x0E1A33;

function RCT(x, y, w, h) ( gfx_rect(x * qs, y * qs, max(1, w * qs), max(1, h * qs)); );
function RR(x, y, w, h, r) ( rd_rr(x * qs, y * qs, w * qs, h * qs, r * qs); );
function TX(x, y, t) ( gfx_x = x * qs; gfx_y = y * qs; gfx_drawstr(t); );
function TC(cx, y, t) local(w, h) ( gfx_measurestr(t, w, h); gfx_x = cx * qs - w * 0.5; gfx_y = y * qs; gfx_drawstr(t); );
function TRt(rx, y, t) local(w, h) ( gfx_measurestr(t, w, h); gfx_x = rx * qs - w; gfx_y = y * qs; gfx_drawstr(t); );
function inbox(x, y, w, h) ( mouse_x >= x * qs && mouse_x < (x + w) * qs && mouse_y >= y * qs && mouse_y < (y + h) * qs; );
function hitbox(x, y, w, h) local(h_) ( h_ = pressed && drag_id == 0 && inbox(x, y, w, h); h_ ? drag_id = -1; h_; );
function setv(idx, v) ( slider(idx) = v; ui_auto(idx); );
function tog(idx) ( slider(idx) = slider(idx) < 0.5; ui_auto(idx); );
function rivet(x, y) ( ui_col(SEAM); gfx_circle(x * qs, y * qs, 2.6 * qs, 1, 1); ui_cola(0xFFFFFF, 0.7); gfx_circle((x - 0.7) * qs, (y - 0.7) * qs, 1.1 * qs, 1, 1); );
function iname(list, v) ( pk_pitem(list, floor(v + 0.5) + 1, #in_); #in_; );
// a value as text, by parameter
function fval(i) local(v) (
  v = slider(i);
  i == 1 ? ( v < 0.5 ? "SAW" : v < 1.5 ? "TRI" : "PULSE"; ) :
  i == 4 ? ( v < 0.5 ? "OFF" : v < 1.5 ? "SAW" : v < 2.5 ? "TRI" : "PULSE"; ) :
  i == 10 ? ( v < 0.5 ? "-1 OCT" : "-2 OCT"; ) :
  i == 12 ? sprintf(#fv, "%d", v + 1) :
  i == 17 ? iname("2-POLE LP|4-POLE LP|BAND-PASS|HIGH-PASS|NOTCH", v) :
  i == 32 ? ( v < 0.5 ? "MONO" : "POLY"; ) :
  i == 34 || i == 39 ? ( v < 0.5 ? "TRIANGLE" : v < 1.5 ? "SAW" : v < 2.5 ? "SQUARE" : "RANDOM"; ) :
  i == 35 || i == 40 ? ( v < 0.5 ? "PITCH" : v < 1.5 ? "CUTOFF" : v < 2.5 ? "PW" : "PAN"; ) :
  i == 37 || i == 42 ? iname("FREE|1 BAR|1/2|1/4|1/8|1/16|1/8 T", v) :
  i == 43 || i == 45 ? ( v < 0.5 ? "OFF" : "ON"; ) :
  i == 44 ? iname("1/32|1/16|1/8|1/4|1/16 T|1/8 T", v) :
  i == 46 ? iname("PW|RESONANCE|DRIVE|PAN|NOISE", v) :
  i == 2 || i == 5 || i == 50 ? sprintf(#fv, "%+d st", v) :
  i == 6 || i == 13 ? sprintf(#fv, "%+.1f c", v) :
  i == 15 ? ( v >= 1000 ? sprintf(#fv, "%.2f kHz", v / 1000) : sprintf(#fv, "%d Hz", floor(v + 0.5)) ) :
  i == 33 || i == 38 ? sprintf(#fv, "%.2f Hz", v) :
  i == 31 || i == 22 || i == 23 || i == 25 || i == 26 || i == 27 || i == 29 ?
    ( v >= 1000 ? sprintf(#fv, "%.2f s", v / 1000) : v >= 10 ? sprintf(#fv, "%d ms", floor(v + 0.5)) : sprintf(#fv, "%.1f ms", v) ) :
  i == 51 ? sprintf(#fv, "%+.1f dB", v) :
  i == 19 ? sprintf(#fv, "%+d%%", v) :
  sprintf(#fv, "%d%%", floor(v + 0.5));
);
// a knob on a parameter (range from the table), coloured cap, value on hover
function knob(i, cx, cy, r, col, label) (
  dy_kst = 36; dy_ink = INK; dy_bg = HULL2;
  PST[i] ? dy_step = 0;
  dy_knob(cx * 0.8, cy * 0.8, r * 0.8, i, PLO[i], PHI[i], PDEF[i], PLG[i], col, label, fval(i));
  PST[i] && slider(i) != floor(slider(i) + 0.5) ? slider(i) = floor(slider(i) + 0.5);
);
// the same on any slider with its own range
function knobx(i, cx, cy, r, lo, hi, def, lg, col, label, val) (
  dy_kst = 36; dy_ink = INK; dy_bg = HULL2;
  dy_knob(cx * 0.8, cy * 0.8, r * 0.8, i, lo, hi, def, lg, col, label, val);
);
// a lit key, caption on it
function key(x, y, w, h, on, col, cap) local(hit) (
  rd_sqbg = 0x2A2C2F; gfx_setfont(4, "Trebuchet MS", (mg_kfs > 0 ? mg_kfs : 12) * qs, 'b');
  hit = rd_sq(x * 0.8, y * 0.8, w * 0.8, h * 0.8, on, col, cap);
  hit;
);
// a menu box: dark window, cream text, the list opens on a click
function mbox(x, y, w, h, t) local(hov) (
  hov = inbox(x, y, w, h);
  ui_col(0x16181B); RR(x, y, w, h, 3);
  ui_cola(0xFFFFFF, hov ? 0.35 : 0.12); gfx_rect(x * qs, y * qs, w * qs, h * qs, 0);
  gfx_setfont(14); ui_col(0xF1E9D2); gfx_x = (x + 6) * qs; gfx_y = (y + h * 0.5 - 7.5) * qs; gfx_drawstr(t);
  ui_cola(0xF1E9D2, 0.6); TRt(x + w - 5, y + h * 0.5 - 7.5, "v");
  hitbox(x, y, w, h);
);
function mlist(idx, list, x, y) local(k, n, s) (
  strcpy(#ml, ""); n = pk_pcount(list); k = 1;
  loop(n, floor(slider(idx) + 0.5) == k - 1 ? strcat(#ml, "!"); strcat(#ml, pk_pitem(list, k, #mi)); k < n ? strcat(#ml, "|"); k += 1; );
  gfx_x = x * qs; gfx_y = y * qs; k = gfx_showmenu(#ml); k > 0 ? setv(idx, k - 1);
);
// a recessed bay on the hull with its livery header
function sect(x, y, w, h, col, title) (
  ui_cola(0x000000, 0.18); RR(x + 1, y + 2, w, h, 6);
  ui_col(SEAM); RR(x, y, w, h, 6);
  ui_col(HULL2); RR(x + 1.5, y + 1.5, w - 3, h - 3, 5);
  ui_col(col); RR(x + 1.5, y + 1.5, w - 3, 24, 5); RCT(x + 1.5, y + 14, w - 3, 11.5);
  gfx_setfont(9); ui_col(col == YEL ? INK : 0xFFFFFF); TX(x + 12, y + 4, title);
  rivet(x + w - 9, y + h - 9); rivet(x + 9, y + h - 9);
);

// ---------------------------------------------------------------------
// the hull and the sky
ui_col(HULL); gfx_rect(0, 0, gfx_w, gfx_h);
// the sky, row by row: deep navy, a band of teal, an amber glow on the horizon
mg_i = 0;
loop(ceil(62 * qs),
  mg_t = mg_i / (62 * qs);
  mg_c = mg_t < 0.6 ? ui_mix(0x0A1230, 0x15405A, mg_t / 0.6) : mg_t < 0.85 ? ui_mix(0x15405A, 0x6A4A4A, (mg_t - 0.6) / 0.25) : ui_mix(0x6A4A4A, 0xE0823C, (mg_t - 0.85) / 0.15);
  ui_col(mg_c); gfx_rect(0, mg_i, 1200 * qs + 1, 1);
  mg_i += 1;
);
mg_i = 0; loop(90, ui_cola(0xFFFFFF, 0.25 + 0.6 * rd_hash(mg_i + 11) ^ 2); gfx_circle(1200 * rd_hash(mg_i) * qs, 52 * rd_hash(mg_i + 77) * qs, (0.5 + rd_hash(mg_i + 5)) * qs, 1, 1); mg_i += 1; );
// a distant ringed planet on the horizon
ui_cola(0xE8A060, 0.9); gfx_circle(1012 * qs, 40 * qs, 20 * qs, 1, 1);
ui_cola(0x6A2E1A, 0.55); gfx_circle(1019 * qs, 46 * qs, 19 * qs, 1, 1);
ui_cola(0xF6D9A8, 0.7); gfx_line(978 * qs, 47 * qs, 1048 * qs, 31 * qs, 1); gfx_line(978 * qs, 48 * qs, 1048 * qs, 32 * qs, 1);
// the livery band, hazard stripes, the hull code
ui_col(ORG); RCT(0, 58, 1200, 16);
mg_i = 0; loop(16, ui_col(mg_i & 1 ? INK : YEL); gfx_triangle((mg_i * 16) * qs, 74 * qs, (mg_i * 16 + 16) * qs, 74 * qs, (mg_i * 16 + 32) * qs, 58 * qs, (mg_i * 16 + 16) * qs, 58 * qs); mg_i += 1; );
ui_col(TEAL); RCT(1110, 58, 90, 16);
ui_cola(0x000000, 0.25); RCT(0, 74, 1200, 2);
gfx_setfont(13); ui_col(INK); TX(300, 59, "UNIT  MT-04       STACK  1 - 4       SEQUENCER  6 x 16       LADDER  5 TYPES");
gfx_setfont(13); ui_col(0xFFFFFF); TX(1122, 59, "MT-04");
// hull seams
ui_col(SEAM); RCT(0, 241, 1200, 1); RCT(0, 775, 1200, 1);

// ---- the name rail ----
ui_col(0xF1E9D2); gfx_circle(34 * qs, 29 * qs, 17 * qs, 1, 1);
gfx_setfont(13); ui_col(NAVY); ui_keytext(34 * qs, 29 * qs, "GFX");
gfx_setfont(10); ui_col(0xF4EEDD); TX(62, 2, "MOTRA");
gfx_measurestr("MOTRA", mg_tw, mg_th);
gfx_setfont(13); ui_cola(0xF4EEDD, 0.85); TX(66 + mg_tw / qs, 14, "STACKED ANALOGUE SYNTH"); TX(66 + mg_tw / qs, 30, "MONO  -  POLY  -  SEQUENCER");
pk_pextra = "";
prs_cur_d > 0 ? ( pr_cur = prs_cur_d; prs_cur_d = 0; );     // PRESET parameter: show a preset loaded by the host
mg_k = pk_preset(470 * qs, 13 * qs, 380 * qs, 32 * qs, pr_cur, #mt_pm);
mg_k > 0 ? prs_dirty = mg_k;
rd_led(872 * 0.8, 29 * 0.8, 4.5 * 0.8, ins_midi_lit(), 0x3ADF6A);
gfx_setfont(13); ui_col(0xF4EEDD); TX(882, 22, "MIDI");
mg_ka = C_KA; C_KA = mt_page ? ORG : TEAL;                     // EDIT is always lit: teal on the sequencer, orange on its own page
ui_noled = 1; ui_button(1060 * qs, 15 * qs, 62 * qs, 30 * qs, "EDIT", 1, 1) ? mt_page = !mt_page;
C_KA = mg_ka;
ui_size_picker(1130 * qs, 20 * qs);

// ---------------------------------------------------------------------
// performance row
mg_y = 82;
sect(14, mg_y, 430, 150, ORG, "FILTER  /  ENVELOPE");
knob(15, 68, mg_y + 78, 34, ORG, "CUTOFF");               // centres evenly spaced, 107 apart
knob(16, 175, mg_y + 82, 30, ORG, "RESONANCE");
mg_a = slider26; knob(26, 282, mg_y + 88, 24, ORG, "ATTACK");
slider26 != mg_a ? setv(22, slider26);                          // (the filter envelope follows)
mg_a = slider27; knob(27, 389, mg_y + 88, 24, ORG, "DECAY / REL");
slider27 != mg_a ? ( setv(23, slider27); setv(25, slider27); setv(29, slider27); );

sect(454, mg_y, 300, 150, TEAL, "ASSIGN");
mg_i = 0;
loop(4,
  mg_t = floor(slider(54 + mg_i) + 0.5); mg_t = min(max(mg_t, 1), NPP);
  mg_cx = 494 + mg_i * 72;
  knob(mg_t, mg_cx, mg_y + 72, 22, TEAL, "");
  gfx_setfont(14); ui_col(INK); TC(mg_cx, mg_y + 106, 1000 + mg_t);
  hitbox(mg_cx - 34, mg_y + 102, 68, 32) ? (
    strcpy(#am, ""); mg_k = 1; loop(NPP, mg_k == mg_t ? strcat(#am, "!"); strcat(#am, 1000 + mg_k); mg_k < NPP ? strcat(#am, "|"); mg_k += 1; );
    gfx_x = mouse_x; gfx_y = mouse_y; mg_k = gfx_showmenu(#am); mg_k > 0 ? setv(54 + mg_i, mg_k);
  );
  mg_i += 1;
);

// VOICES: a light for each of the eight voices
sect(764, mg_y, 190, 150, RED, "VOICES");
mg_i = 0;
loop(8,
  mg_p = VB + mg_i * VS;
  mg_lx = 802 + (mg_i & 3) * 38; mg_ly = mg_y + (mg_i < 4 ? 62 : 104);
  rd_led(mg_lx * 0.8, mg_ly * 0.8, 6 * 0.8, mg_p[0] && mg_p[32] > 0.002, YEL);
  mg_i += 1;
);

// MODE and STACK
sect(964, mg_y, 222, 150, YEL, "MODE");
mg_kfs = 16;
key(978, mg_y + 34, 92, 28, slider32 < 0.5, YEL, "MONO") ? setv(32, 0);
key(1080, mg_y + 34, 92, 28, slider32 >= 0.5, YEL, "POLY") ? setv(32, 1);
mg_kfs = 0;
gfx_setfont(14); ui_col(INK2); TX(978, mg_y + 70, "STACK  -  VOICES PER NOTE");
mg_kfs = 14;
mg_i = 0; loop(4, sprintf(#pk, "%d", mg_i + 1); key(978 + mg_i * 49, mg_y + 90, 43, 24, floor(slider12 + 0.5) == mg_i, ORG, #pk) ? setv(12, mg_i); mg_i += 1; );
mg_kfs = 0;
gfx_setfont(14); ui_cola(INK, 0.65); TX(978, mg_y + 122, slider32 < 0.5 ? "LEGATO, LAST NOTE" : sprintf(#pv, "%d NOTES AT A TIME", floor(8 / max(stk_al, 1))));

// ---------------------------------------------------------------------
!mt_page ? (
  // ---- SEQUENCER: four tracks of sixteen steps (drag across the bars to draw; the value shows while drawing) ----
  sect(14, 250, 1172, 518, TEAL, "SEQUENCER");
  key(560, 254, 70, 18, slider43 < 0.5, YEL, "OFF") ? setv(43, 0);
  key(636, 254, 70, 18, slider43 >= 0.5, YEL, "ON") ? setv(43, 1);
  key(730, 254, 90, 18, slider45 >= 0.5, ORG, "RETRIG") ? tog(45);
  gfx_setfont(13); ui_col(0xFFFFFF); TX(870, 257, "RATE");
  mbox(910, 253, 120, 20, fval(44)) ? mlist(44, "1/32|1/16|1/8|1/4|1/16 triplet|1/8 triplet", 910, 274);
  gfx_setfont(13); ui_col(0xFFFFFF); TX(1046, 257, "HOLD A KEY");
  mg_st = sq_run ? sq_step : -1;
  mg_bx = 214; mg_bw = 59;
  mg_i = 0; loop(16, rd_led((mg_bx + mg_i * mg_bw + mg_bw * 0.5) * 0.8, 290 * 0.8, 3.5 * 0.8, mg_i == mg_st, ORG); mg_i += 1; );
  mg_tr = 0;
  loop(4,
    mg_ty = 304 + mg_tr * 88; mg_col = mg_tr == 0 ? ORG : mg_tr == 1 ? TEAL : mg_tr == 2 ? RED : 0xC79A10;
    mg_pit = mg_tr == 0;
    ui_col(mg_col); RR(28, mg_ty, 8, 72, 2);
    gfx_setfont(9); ui_col(INK); TX(46, mg_ty + 2, mg_tr == 0 ? "PITCH" : mg_tr == 1 ? "CUTOFF" : mg_tr == 2 ? "LEVEL" : "SEQ 4");
    mg_tr == 0 ? ( gfx_setfont(13); ui_col(INK2); TX(46, mg_ty + 28, "SEMITONES 0 - 24"); TX(46, mg_ty + 44, "ON TOP OF THE KEY"); ) : (
      mg_tr == 3 ? ( mbox(46, mg_ty + 26, 96, 20, fval(46)) ? mlist(46, "PW|Resonance|Drive|Pan|Noise", 46, mg_ty + 46); );
      knob(46 + mg_tr, 172, mg_ty + 42, 15, mg_col, "");
      gfx_setfont(13); ui_col(INK2); TX(46, mg_ty + 54, "DEPTH");
    );
    // the bars
    mg_q = SEQ + mg_tr * 16;
    ui_col(0x16181B); RR(mg_bx - 4, mg_ty - 2, 16 * mg_bw + 6, 76, 4);
    mg_i = 0;
    loop(16,
      mg_x = mg_bx + mg_i * mg_bw;
      ui_cola(0xFFFFFF, (mg_i & 3) == 0 ? 0.05 : 0.025); RCT(mg_x + 2, mg_ty + 2, mg_bw - 6, 68);
      mg_v = mg_q[mg_i]; mg_pit ? mg_v = floor(mg_v * 24 + 0.5) / 24;
      ui_col(mg_i == mg_st ? ui_mix(mg_col, 0xFFFFFF, 0.35) : mg_col); RCT(mg_x + 2, mg_ty + 2 + 68 * (1 - mg_v), mg_bw - 6, max(1.5, 68 * mg_v));
      mg_pit && mg_v > 0 ? ( gfx_setfont(13); ui_col(0xF1E9D2); sprintf(#sn, "%d", floor(mg_v * 24 + 0.5)); TC(mg_x + mg_bw * 0.5 - 2, mg_ty + 56, #sn); );
      mg_i += 1;
    );
    // drawing: press in the bars, drag across
    pressed && drag_id == 0 && inbox(mg_bx, mg_ty, 16 * mg_bw, 72) ? ( drag_id = 3000 + mg_tr; );
    drag_id == 3000 + mg_tr ? (
      mg_i = min(max(floor((mouse_x / qs - mg_bx) / mg_bw), 0), 15);
      mg_v = min(max(1 - (mouse_y / qs - mg_ty - 2) / 68, 0), 1);
      mg_pit ? mg_v = floor(mg_v * 24 + 0.5) / 24;
      mg_q[mg_i] = mg_v; mg_sdirty = 1;
      mg_pit ? sprintf(#sv, "STEP %d   +%d st", mg_i + 1, floor(mg_v * 24 + 0.5)) : sprintf(#sv, "STEP %d   %d", mg_i + 1, floor(mg_v * 100 + 0.5));
      ui_col(0x16181B); RR(mg_bx + 16 * mg_bw - 166, mg_ty - 22, 166, 18, 3);
      gfx_setfont(13); ui_col(0xF1E9D2); TX(mg_bx + 16 * mg_bw - 158, mg_ty - 20, #sv);
    );
    mg_tr += 1;
  );
  // ---- the step lanes: FILTER (type per step) and STACK (voices per step); a dim step follows the panel ----
  mg_ln = 0;
  loop(2,
    mg_ty = 660 + mg_ln * 54; mg_col = mg_ln == 0 ? ORG : TEAL; mg_q = mg_ln == 0 ? FTL : STL;
    ui_col(mg_col); RR(28, mg_ty, 8, 40, 2);
    gfx_setfont(9); ui_col(INK); TX(46, mg_ty, mg_ln == 0 ? "FILTER" : "STACK");
    gfx_setfont(13); ui_col(INK2); TX(46, mg_ty + 24, mg_ln == 0 ? "TYPE PER STEP" : "VOICES PER STEP");
    ui_col(0x16181B); RR(mg_bx - 4, mg_ty - 2, 16 * mg_bw + 6, 44, 4);
    mg_i = 0;
    loop(16,
      mg_x = mg_bx + mg_i * mg_bw; mg_v = floor(mg_q[mg_i] + 0.5);
      mg_hv = inbox(mg_x + 2, mg_ty + 2, mg_bw - 6, 36);
      mg_v > 0 ? ( ui_col(mg_i == mg_st ? ui_mix(mg_col, 0xFFFFFF, 0.35) : mg_col); RCT(mg_x + 2, mg_ty + 2, mg_bw - 6, 36); ) :
                 ( ui_cola(0xFFFFFF, mg_i == mg_st ? 0.16 : (mg_i & 3) == 0 ? 0.06 : 0.035); RCT(mg_x + 2, mg_ty + 2, mg_bw - 6, 36); );
      mg_hv ? ( ui_cola(0xFFFFFF, 0.5); gfx_rect((mg_x + 2) * qs, (mg_ty + 2) * qs, (mg_bw - 6) * qs, 36 * qs, 0); );
      mg_ln == 0 ? ( pk_pitem("LP2|LP4|BAND|HIGH|NOTCH", (mg_v > 0 ? mg_v - 1 : floor(slider17 + 0.5)) + 1, #lt); ) :
                   sprintf(#lt, "%d", mg_v > 0 ? mg_v : floor(slider12 + 0.5) + 1);
      gfx_setfont(14); gfx_measurestr(#lt, mg_tw, mg_th); mg_tw > (mg_bw - 8) * qs ? gfx_setfont(13);
      ui_col(mg_v > 0 ? (mg_col == ORG ? INK : 0xFFFFFF) : 0x8A8A86); TC(mg_x + mg_bw * 0.5 - 1, mg_ty + 12, #lt);
      hitbox(mg_x + 2, mg_ty + 2, mg_bw - 6, 36) ? (
        mg_ln == 0 ? sprintf(#ml, "%sPanel (%s)|%sLP 2-pole|%sLP 4-pole|%sBand-pass|%sHigh-pass|%sNotch||All steps follow the panel",
                       mg_v == 0 ? "!" : "", fval(17), mg_v == 1 ? "!" : "", mg_v == 2 ? "!" : "", mg_v == 3 ? "!" : "", mg_v == 4 ? "!" : "", mg_v == 5 ? "!" : "")
                   : sprintf(#ml, "%sPanel (%d)|%s1 voice|%s2 voices|%s3 voices|%s4 voices||All steps follow the panel",
                       mg_v == 0 ? "!" : "", floor(slider12 + 0.5) + 1, mg_v == 1 ? "!" : "", mg_v == 2 ? "!" : "", mg_v == 3 ? "!" : "", mg_v == 4 ? "!" : "");
        gfx_x = (mg_x + 2) * qs; gfx_y = (mg_ty + 40) * qs; mg_k = gfx_showmenu(#ml);
        mg_k > 0 ? ( mg_k == (mg_ln == 0 ? 7 : 6) ? memset(mg_q, 0, 16) : mg_q[mg_i] = mg_k - 1; mt_dirty(); );
      );
      mg_i += 1;
    );
    mg_ln += 1;
  );
  !mdown && mg_sdirty ? ( mt_dirty(); mg_sdirty = 0; );
) : (
  // ---- EDITOR: every parameter ----
  // row 1: DCO 1 | DCO 2 | MIXER | STACK | FILTER
  mg_y = 250; mg_ky = mg_y + 98;
  sect(14, mg_y, 190, 160, TEAL, "DCO 1");
  mbox(26, mg_y + 32, 90, 20, fval(1)) ? mlist(1, "Saw|Tri|Pulse", 26, mg_y + 52);
  knob(2, 58, mg_ky, 17, TEAL, "FREQ"); knob(3, 150, mg_ky, 17, TEAL, "PW");
  sect(212, mg_y, 250, 160, TEAL, "DCO 2");
  mbox(224, mg_y + 32, 90, 20, fval(4)) ? mlist(4, "Off|Saw|Tri|Pulse", 224, mg_y + 52);
  knob(5, 250, mg_ky, 17, TEAL, "FREQ"); knob(6, 310, mg_ky, 17, TEAL, "FINE"); knob(7, 370, mg_ky, 17, TEAL, "PW");
  knob(8, 430, mg_ky, 15, 0xC79A10, "MIX");
  sect(470, mg_y, 196, 160, YEL, "SUB  /  NOISE");
  key(484, mg_y + 32, 44, 20, slider10 >= 0.5, YEL, slider10 >= 0.5 ? "-2" : "-1") ? tog(10);
  gfx_setfont(14); ui_col(INK2); TX(536, mg_y + 35, "SUB OCTAVE");
  knob(9, 532, mg_ky, 17, 0xC79A10, "SUB"); knob(11, 604, mg_ky, 17, 0xC79A10, "NOISE");
  sect(674, mg_y, 200, 160, ORG, "STACK");
  knob(13, 710, mg_ky - 10, 16, ORG, "DETUNE"); knob(14, 772, mg_ky - 10, 16, ORG, "SPREAD"); knob(31, 834, mg_ky - 10, 16, ORG, "GLIDE");
  gfx_setfont(14); ui_col(INK2); TX(690, mg_y + 33, "VOICES: TOP ROW");
  sect(882, mg_y, 304, 160, ORG, "FILTER");
  mbox(1050, mg_y + 32, 124, 20, fval(17)) ? mlist(17, "2-pole low-pass|4-pole low-pass|Band-pass|High-pass|Notch", 1050, mg_y + 52);
  knob(18, 920, mg_ky, 15, ORG, "DRIVE"); knob(19, 996, mg_ky, 15, ORG, "ENV AMT"); knob(20, 1072, mg_ky, 15, ORG, "KEY"); knob(21, 1148, mg_ky, 15, ORG, "VEL");
  gfx_setfont(14); ui_col(INK2); TX(898, mg_y + 33, "CUTOFF / RES:"); TX(898, mg_y + 50, "TOP ROW");
  // row 2: the envelopes
  mg_y = 420;
  sect(14, mg_y, 580, 156, ORG, "FILTER ENVELOPE");
  knob(22, 86, mg_y + 84, 19, ORG, "ATTACK"); knob(23, 231, mg_y + 84, 19, ORG, "DECAY"); knob(24, 376, mg_y + 84, 19, ORG, "SUSTAIN"); knob(25, 521, mg_y + 84, 19, ORG, "RELEASE");
  sect(602, mg_y, 584, 156, RED, "AMP ENVELOPE");
  knob(26, 660, mg_y + 84, 19, RED, "ATTACK"); knob(27, 777, mg_y + 84, 19, RED, "DECAY"); knob(28, 894, mg_y + 84, 19, RED, "SUSTAIN"); knob(29, 1011, mg_y + 84, 19, RED, "RELEASE");
  knob(30, 1128, mg_y + 86, 17, RED, "VELOCITY");
  // row 3: the LFOs
  mg_y = 586; mg_l = 0;
  loop(2,
    mg_x = 14 + mg_l * 590; mg_b = 33 + mg_l * 5;
    sect(mg_x, mg_y, 582, 182, TEAL, mg_l == 0 ? "LFO 1  -  MOD WHEEL ADDS" : "LFO 2");
    mg_b2 = mg_b;
    knob(mg_b, mg_x + 60, mg_y + 100, 18, TEAL, "RATE");
    slider(mg_b + 4) > 0.5 ? ( ui_cola(HULL2, 0.6); gfx_circle((mg_x + 60) * qs, (mg_y + 100) * qs, 24 * qs, 1, 1); );
    gfx_setfont(14); ui_col(INK2); TX(mg_x + 128, mg_y + 60, "SYNC");
    mbox(mg_x + 128, mg_y + 78, 92, 20, fval(mg_b + 4)) ? mlist(mg_b + 4, "Free|1 bar|1/2|1/4|1/8|1/16|1/8 triplet", mg_x + 128, mg_y + 100);
    gfx_setfont(14); ui_col(INK2); TX(mg_x + 236, mg_y + 60, "SHAPE");
    mbox(mg_x + 236, mg_y + 78, 100, 20, fval(mg_b + 1)) ? mlist(mg_b + 1, "Triangle|Saw|Square|Random", mg_x + 236, mg_y + 100);
    gfx_setfont(14); ui_col(INK2); TX(mg_x + 352, mg_y + 60, "TO");
    mbox(mg_x + 352, mg_y + 78, 100, 20, fval(mg_b + 2)) ? mlist(mg_b + 2, "Pitch|Cutoff|PW|Pan", mg_x + 352, mg_y + 100);
    knob(mg_b + 3, mg_x + 512, mg_y + 100, 18, TEAL, "AMOUNT");
    mg_l += 1;
  );
);

// ---------------------------------------------------------------------
// PLAY strip
mg_y = 780;
ui_col(HULL2); RCT(0, mg_y, 1200, 70); ui_col(SEAM); RCT(0, mg_y, 1200, 1);
mg_i = 0; loop(10, rivet(8 + mg_i * 132, mg_y + 8); mg_i += 1; );
gfx_setfont(9); ui_col(INK); TX(20, mg_y + 22, "PLAY");
gfx_setfont(14); ui_col(INK2); TX(110, mg_y + 10, "MIDI CH");
mbox(110, mg_y + 32, 80, 22, slider53 < 0.5 ? "OMNI" : sprintf(#ch, "%d", slider53)) ? (
  gfx_x = 110 * qs; gfx_y = (mg_y + 54) * qs; mg_k = gfx_showmenu("Omni|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16"); mg_k > 0 ? setv(53, mg_k - 1); );
knob(50, 250, mg_y + 28, 14, YEL, ""); gfx_setfont(14); ui_col(INK2); TC(250, mg_y + 49, "BEND");
knob(52, 330, mg_y + 28, 14, ORG, ""); gfx_setfont(14); ui_col(INK2); TC(330, mg_y + 49, "DRIFT");
knob(51, 410, mg_y + 28, 14, ORG, ""); gfx_setfont(14); ui_col(INK2); TC(410, mg_y + 49, "VOLUME");
ins_io(470 * 0.8, (mg_y + 14) * 0.8, 58, INK);
mgL.ui_ledm(760 * qs, (mg_y + 22) * qs, 400 * qs, 8 * qs, mt_pk_d); mgR.ui_ledm(760 * qs, (mg_y + 36) * qs, 400 * qs, 8 * qs, mt_pk_d);
mt_pk_d *= 0.85;
gfx_setfont(13); ui_col(INK2); TX(712, mg_y + 26, "OUTPUT");

pk_end();
ui_end();
