desc:Glow [additive synth] (GFX Lab)
//tags: instrument synth additive digital vintage 8-bit partials spectral morph poly
//author: JGWizrad / Garden FX (GFX)
//
// Requires gfx-lab-drift, gfx-lab-output, gfx-lab-ui, gfx-lab-panel and gfx-lab-instrument (.jsfx-inc) in the same folder.
//
// Additive synthesizer in the spirit of the first micro-computer digital synths of 1980-81 (a home computer
// with a pair of digital oscillator cards and a keyboard): every sound is built from harmonic partials.
// Lab rework: every oscillator holds TWO drawn spectra, START and END, and MORPHS between them over the life of
// a note (each partial glides from its START level to its END level).
//
// SIGNAL PATH (per voice, 8 voices)
//   two paths, A and B. Each path: an additive OSCILLATOR (16 / 32 / 64 partials, one level each, drawn on its
//   screen) + a NOISE generator, through its own ADSR, then its own LEVEL and PAN.
//   OSCILLATOR modes:
//     MODERN  - clean: the partials are summed into a 2048-point single cycle, rebuilt per octave without the
//               partials that would pass Nyquist (no aliasing), read with interpolation
//     VINTAGE - the old card: a 256-byte (8-bit) wavetable read by a 16-bit phase accumulator with no
//               interpolation and no band-limiting (it aliases at the top), its pitch quantised to the
//               accumulator's step, an 8-bit amplitude stage after the envelope (stepped tails) and a
//               31.25 kHz DAC clock (zero-order hold)
//   NOISE: WHITE (filtered by TONE) or LFSR (15-bit shift register clocked at the TONE rate: 80s digital grit)
//   PITCH ENV: the path's own envelope bends its oscillator (st at full envelope) - for thumps and zaps
//   X-MOD: A frequency-modulates B and/or B modulates A (phase modulation, DX style)
// SPECTRAL MORPH (per oscillator): START and END screens (the START / END keys pick the one you draw on; the
//   other shows faintly behind it). MODE: OFF (START only), ONCE (START to END over TIME, then holds END - the
//   release carries on from where it is), LOOP (START to END and back, TIME each way), ENVELOPE (follows the
//   path's own ADSR: silence = START, full envelope = END). CURVE shapes the move (+ = fast start, - = slow
//   start), DEPTH = how far towards END it goes, VELOCITY = how much softer notes hold back. An LFO can be routed
//   to MORPH too (it moves both oscillators, on top of the MODE). MODERN morphs smoothly (the two tables are
//   blended: exactly the partial levels in between); VINTAGE rewrites its 8-bit table once per cycle, as the old
//   card would have, so its morph moves in fine steps. The morph position glides (3 ms), so a mode change or a
//   stolen voice never clicks. COPY puts the screen you are drawing on into the other; SWAP exchanges them.
// ENVELOPES: one ADSR per path with a curve for each of attack, decay and release
//   (0 = linear, + = exponential / snappy, - = slow start)
// LFO 1 / 2: SINE / TRIANGLE / SQUARE / S&H, RATE, SKEW (shifts the peak / pulse width; on S&H it glides),
//   POLY (every note has its own LFO, restarted at the note) or GLOBAL (one for all),
//   to PITCH A+B / PITCH A / PITCH B / PAN (A one way, B the other) / LEVEL / X-MOD depth / NOISE / MORPH
// VOICE: POLY (8), MONO (legato: held notes glide, envelopes carry on), RETRIG (mono, every note restarts
//   the envelopes), GLIDE, BEND range, VELOCITY
// DELAY: stereo, each side in ms or a tempo division; FEEDBACK and CROSS-FEEDBACK (L <-> R) with LP / HP
//   filters inside the loop; STEREO or PING-PONG input (mono into the left, CROSS bounces it)
// DRIFT (0 - 100 %, 20 % = standard, as the Stable Glow): the clock wanders - slow pitch wander and a little
//   per-note tuning (the GFX instrument standard), plus each note's morph runs a little faster or slower (up to
//   +/-15 % at 100 %) and the morph position breathes slightly (MODE on only). With DRIFT at 0 and every MODE
//   OFF the unit sounds as the Stable Glow.
// PRESETS: a factory bank (the PRESETS key, the arrows, or click the display); a patch sets every sound
//   parameter and all four partial sets. RANDOM (per screen) makes a new musical partial set. Presets load on
//   the audio side; the last slider (Preset) loads one from the host's parameter list (0 = none).
// Draw on a screen to set partials (right-drag to erase). The partial sets are saved with the project.
// MIDI: pitch bend, CC1 adds to LFO 1's amount, CC64 sustain, CC120/123 all notes off.
// IN / OUT: the strip along the bottom holds SAT and STEREO (MONO, WIDTH).
// Latency: 0 samples.

import gfx-lab-drift.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

slider1:0<0,2,1{Poly,Mono legato,Mono retrigger}>-Voice mode
slider2:0<0,2000,1>-Glide (ms)
slider3:2<0,24,1>-Bend range (st)
slider4:60<0,100,1>-Velocity (%)
slider5:0<-24,6,0.1>-Volume (dB)
slider6:0<0,16,1>-MIDI channel (0 = omni)
slider7:20<0,100,1>-Drift (%)
slider8:0<0,1,1{Modern,Vintage}>-Osc A mode
slider9:0<0,2,1{16,32,64}>-Osc A partials
slider10:0<-36,36,1>-Osc A transpose (st)
slider11:0<-100,100,0.1>-Osc A fine (cents)
slider12:80<0,100,1>-Path A level (%)
slider13:0<-100,100,1>-Path A pan (%)
slider14:0<0,100,1>-Path A noise (%)
slider15:0<0,1,1{White,LFSR}>-Path A noise type
slider16:70<0,100,1>-Path A noise tone (%)
slider17:0<-48,48,0.1>-Osc A pitch env (st)
slider18:0<0,1,1{Modern,Vintage}>-Osc B mode
slider19:0<0,2,1{16,32,64}>-Osc B partials
slider20:0<-36,36,1>-Osc B transpose (st)
slider21:0<-100,100,0.1>-Osc B fine (cents)
slider22:0<0,100,1>-Path B level (%)
slider23:0<-100,100,1>-Path B pan (%)
slider24:0<0,100,1>-Path B noise (%)
slider25:0<0,1,1{White,LFSR}>-Path B noise type
slider26:70<0,100,1>-Path B noise tone (%)
slider27:0<-48,48,0.1>-Osc B pitch env (st)
slider28:0<0,100,0.1>-X-mod A > B (%)
slider29:0<0,100,0.1>-X-mod B > A (%)
slider30:2<0.5,10000,0.1>-Env A attack (ms)
slider31:400<1,20000,1>-Env A decay (ms)
slider32:70<0,100,1>-Env A sustain (%)
slider33:300<1,20000,1>-Env A release (ms)
slider34:0<-100,100,1>-Env A attack curve (%)
slider35:50<-100,100,1>-Env A decay curve (%)
slider36:50<-100,100,1>-Env A release curve (%)
slider37:2<0.5,10000,0.1>-Env B attack (ms)
slider38:400<1,20000,1>-Env B decay (ms)
slider39:70<0,100,1>-Env B sustain (%)
slider40:300<1,20000,1>-Env B release (ms)
slider41:0<-100,100,1>-Env B attack curve (%)
slider42:50<-100,100,1>-Env B decay curve (%)
slider43:50<-100,100,1>-Env B release curve (%)
slider44:0<0,3,1{Sine,Triangle,Square,S&H}>-LFO 1 wave
slider45:5<0.02,30,0.01>-LFO 1 rate (Hz)
slider46:50<0,100,1>-LFO 1 skew (%)
slider47:0<0,1,1{Poly,Global}>-LFO 1 mode
slider48:0<0,8,1{Off,Pitch A+B,Pitch A,Pitch B,Pan,Level,X-Mod,Noise,Morph}>-LFO 1 destination
slider49:0<0,100,0.1>-LFO 1 amount (%)
slider50:1<0,3,1{Sine,Triangle,Square,S&H}>-LFO 2 wave
slider51:0.5<0.02,30,0.01>-LFO 2 rate (Hz)
slider52:50<0,100,1>-LFO 2 skew (%)
slider53:1<0,1,1{Poly,Global}>-LFO 2 mode
slider54:0<0,8,1{Off,Pitch A+B,Pitch A,Pitch B,Pan,Level,X-Mod,Noise,Morph}>-LFO 2 destination
slider55:0<0,100,0.1>-LFO 2 amount (%)
slider56:0<0,1,1{Off,On}>-Delay
slider57:0<0,1,1{Stereo,Ping-pong}>-Delay input
slider58:9<0,13,1{ms,1/32,1/16T,1/16,1/8T,1/16D,1/8,1/4T,1/8D,1/4,1/2T,1/4D,1/2,1/1}>-Delay L division
slider59:375<1,2000,1>-Delay L time (ms)
slider60:8<0,13,1{ms,1/32,1/16T,1/16,1/8T,1/16D,1/8,1/4T,1/8D,1/4,1/2T,1/4D,1/2,1/1}>-Delay R division
slider61:500<1,2000,1>-Delay R time (ms)
slider62:35<0,95,1>-Delay feedback (%)
slider63:0<0,95,1>-Delay cross-feedback (%)
slider64:8000<500,20000,10>-Delay LP (Hz)
slider65:80<20,2000,1>-Delay HP (Hz)
slider66:25<0,100,1>-Delay mix (%)
slider67:0<0,100,1>-Band sat (%)
slider68:30<0,100,1>-Bias (%)
slider69:120<40,400,1>-Sat low band (Hz)
slider70:6000<2000,16000,10>-Sat high band (Hz)
slider71:-0.3<-24,0,0.1>-Ceiling (dB)
slider72:0<0,100,1>-Morph A velocity (%)
slider73:20<20,500,1>-Mono below (Hz, 20 = off)
slider74:0<0,100,1>-Stereo width (%)
slider75:0<0,3,1{Off,Once,Loop,Envelope}>-Morph A mode
slider76:2000<10,20000,1>-Morph A time (ms)
slider77:0<-100,100,1>-Morph A curve (%)
slider78:100<0,100,1>-Morph A depth (%)
slider79:0<0,3,1{Off,Once,Loop,Envelope}>-Morph B mode
slider80:2000<10,20000,1>-Morph B time (ms)
slider81:0<-100,100,1>-Morph B curve (%)
slider82:100<0,100,1>-Morph B depth (%)
slider83:0<0,100,1>-Morph B velocity (%)
slider84:prs_sel=0<0,22,1{No preset,1981 Micro Chime,Lo-Fi Bit Bass,Spectral Ambient Pad,E.Piano Tine,Digital Sci-Fi Drone,8-Bit Arcade Percussion,Dual-Timbral Poly,8-Bit Arcade Snare,Glass Organ,Vintage Strings,Hollow Flute,Bell Tower,Choir Formant,Square Sync Lead,Glass Sweep,Vowel Lead,Morph Pluck,Organ to Choir,Bit Shimmer,Tidal Pad,Velocity Brass,Init}>-Preset

in_pin:none
out_pin:left output
out_pin:right output

@init
#ui_man = "gfx-lab-g-glow";                              // the ? pop-up links to this manual
ext_noinit = 1;
!ui_dflt ? ( ui_sz = 0; pr_cur = 1; );

NV = 8; NPATCH = 22;
// ---------- memory map ----------
VB = 60000; VSZ = 64;                  // voice state blocks (8 x 64)
HN = 61000;                            // mono: held-note stack (128)
EQO = 61200; EQT = 61500; EQN = 61800; EQV = 62100;   // note event queue (300 each)
GL1 = 62500; GL2 = 62510;              // the global LFOs' state
PA = 140000;                           // partial levels: set * 64 (0..1) - saved with the project
                                       // set = osc + 2 * end: 0 START A, 1 START B, 2 END A, 3 END B
SINT = 141000;                          // sine, 2048 points
WRK = 143200;                          // build buffer (2048)
ost_init(150000);                      // output stage (4096)
DLL = 200000; DLR = 800000; DSZ = 524288;   // delay lines (2^19 each)
TA = 1400000; TLV = 2056;              // MODERN tables: set * 7 levels * 2056 (2048 + guard)
TV = 1460000;                          // VINTAGE tables: set * 256 (8-bit values)
memset(VB, 0, 3000); memset(TA, 0, 4 * 7 * TLV); memset(DLL, 0, DSZ); memset(DLR, 0, DSZ);
i = 0; loop(2048, SINT[i] = sin(2 * $pi * i / 2048); i += 1; );

// voice block fields
F_NOTE = 0; F_GATE = 1; F_VEL = 2; F_CUR = 3; F_TGT = 4; F_AGE = 5; F_SUS = 6; F_PLAYED = 7;
F_PHA = 8; F_PHB = 9; F_LASTA = 10; F_LASTB = 11;
F_EA = 12; F_EB = 16;                  // envelopes: stage, t, value, start level
F_L1 = 20; F_L2 = 24;                  // poly LFOs: phase, held S&H, smoothed S&H
F_NA = 28; F_NB = 32;                  // noise: filter state, LFSR register, clock phase, held output
F_ZA = 36; F_ZB = 38;                  // vintage DAC hold: clock phase, held value
F_DET = 40;                            // per-note detune (drift)
F_MA = 42; F_MB = 44;                  // morph runners: progress (0..1 once, 0..2 loop)
F_MSA = 46; F_MSB = 47;                // morph position, glided (3 ms)
F_MVA = 48; F_MVB = 49;                // VINTAGE: the morph position held for the cycle
F_MTS = 50;                            // per-note morph speed (drift)
F_MTA = 51; F_MTB = 52;                // morph targets, glided at control rate
F_MDA = 53; F_MDB = 54;                // morph steps per sample (the ramp to the next control-rate value)

// ---------- the partial tables ----------
// count: how many partials the oscillator uses (16 / 32 / 64)
function pcount(o) ( (o ? slider19 : slider9) == 0 ? 16 : (o ? slider19 : slider9) == 1 ? 32 : 64; );
// build both tables for one partial set from its levels. MODERN levels hold 64, 32, 16, 8, 4, 2, 1
// partials (the voice picks the richest level that stays under Nyquist); all levels share one gain, so a
// note does not jump in level crossing an octave. VINTAGE: the full set in 256 bytes, quantised to 8 bits.
function build_tables(s) local(n, k, a, i, lv, pk, base, g, x, step) (
  n = pcount(s & 1);
  memset(WRK, 0, 2048); pk = 0;
  k = 1;
  loop(64,
    a = k <= n ? PA[s * 64 + k - 1] : 0;
    a > 0.0005 ? ( i = 0; step = k; loop(2048, WRK[i] += a * SINT[(i * step) & 2047]; i += 1; ); );
    // the partials so far make up a mip level when k reaches 1, 2, 4 ... 64
    (k & (k - 1)) == 0 ? (
      lv = k == 64 ? 0 : k == 32 ? 1 : k == 16 ? 2 : k == 8 ? 3 : k == 4 ? 4 : k == 2 ? 5 : 6;
      base = TA + (s * 7 + lv) * TLV;
      i = 0; loop(2048, base[i] = WRK[i]; pk = max(pk, abs(WRK[i])); i += 1; );
    );
    k += 1;
  );
  g = pk > 0.000001 ? 0.98 / pk : 0;
  lv = 0;
  loop(7,
    base = TA + (s * 7 + lv) * TLV;
    i = 0; loop(2048, base[i] *= g; i += 1; ); base[2048] = base[0];
    lv += 1;
  );
  // vintage: the 64-partial level, every 8th point, as signed 8-bit
  base = TA + (s * 7) * TLV;
  i = 0; loop(256, x = base[i * 8]; TV[s * 256 + i] = floor(x * 127 + 0.5) / 127; i += 1; );
);
function set_dirty(s) ( s == 0 ? s0_dirty = 1 : s == 1 ? s1_dirty = 1 : s == 2 ? s2_dirty = 1 : s3_dirty = 1; );

// ---------- partial generators (the factory patches and RANDOM use these; s = the partial set 0..3) ----------
function pclear(o) ( memset(PA + o * 64, 0, 64); );
function pset(o, k, a) ( k >= 1 && k <= 64 ? PA[o * 64 + k - 1] = min(max(a, 0), 1); );
function pcopy(d, o) ( memcpy(PA + d * 64, PA + o * 64, 64); );
// shape: 0 sine, 1 saw (1/k), 2 square (odd 1/k), 3 triangle (odd 1/k^2), 4 odd emphasis (odd 1/k^a, even
// x b), 5 harmonic decay (1/k^a), 6 formant (a bump centred on partial a, width b, plus the fundamental),
// 7 organ (drawbar footages), 8 even emphasis (fundamental + even 1/k^a), 9 bright tine (fundamental +
// a cluster high up)
function pshape(o, shape, a, b) local(k, v) (
  pclear(o);
  k = 1;
  loop(64,
    v = shape == 0 ? (k == 1) :
        shape == 1 ? 1 / k :
        shape == 2 ? (k & 1) / k :
        shape == 3 ? (k & 1) / (k * k) :
        shape == 4 ? ((k & 1) ? 1 / k ^ a : b / k ^ a) :
        shape == 5 ? 1 / k ^ a :
        shape == 6 ? max(exp(-sqr((k - a) / b)), (k == 1) * 0.6) / (1 + k * 0.02) :
        shape == 7 ? (k == 1 ? 1 : k == 2 ? 0.8 : k == 3 ? 0.7 : k == 4 ? 0.55 : k == 6 ? 0.4 : k == 8 ? 0.35 : k == 10 ? 0.15 : k == 12 ? 0.2 : k == 16 ? 0.15 : 0) :
        shape == 8 ? (k == 1 ? 1 : (k & 1) ? 0 : 1 / k ^ a) :
        shape == 9 ? (k == 1 ? 1 : k == 2 ? 0.08 : (k >= a && k <= a + 3) ? 0.32 / (1 + (k - a)) : (k >= a * 2 - 1 && k <= a * 2 + 1) ? 0.12 : 0) :
        0;
    PA[o * 64 + k - 1] = min(max(v, 0), 1);
    k += 1;
  );
);
// RANDOM: a new partial set that is still musical - a harmonic decay with jitter, odd-only, random spikes,
// formant bumps or a comb (every n-th partial missing)
function randomize_partials(o) local(style, k, v, ex, n, c, w, sp, i, m) (
  style = floor(rand(5));
  pclear(o);
  ex = 0.5 + rand(1) * 1.8;
  style == 0 ? (                                        // harmonic decay with a little jitter
    k = 1; loop(64, pset(o, k, (1 / k ^ ex) * (0.6 + rand(1) * 0.4)); k += 1; );
  ) : style == 1 ? (                                    // odd harmonics only
    k = 1; loop(64, (k & 1) ? pset(o, k, (1 / k ^ (ex * 0.8)) * (0.7 + rand(1) * 0.3)); k += 1; );
  ) : style == 2 ? (                                    // random spikes over a fundamental
    pset(o, 1, 1); n = 4 + floor(rand(8));
    loop(n, k = 2 + floor(rand(rand(1) < 0.6 ? 16 : 48)); pset(o, k, max(PA[o * 64 + k - 1], (0.25 + rand(1) * 0.75) / (1 + k * 0.08))); );
  ) : style == 3 ? (                                    // one to three formant bumps
    m = 1 + floor(rand(3)); pset(o, 1, 0.7);
    loop(m,
      c = 2 + rand(30); w = 1 + rand(4); sp = 0.5 + rand(1) * 0.5;
      k = 1; loop(64, v = sp * exp(-sqr((k - c) / w)); v > PA[o * 64 + k - 1] ? pset(o, k, v); k += 1; );
    );
  ) : (                                                 // comb: a saw with every n-th partial removed
    n = 2 + floor(rand(4));
    k = 1; loop(64, (k % n) != 0 || k == 1 ? pset(o, k, 1 / k ^ (ex * 0.7)); k += 1; );
  );
  set_dirty(o);
);

// ---------- envelopes (per voice, in the voice block: stage, t, value, start level) ----------
// stage 0 idle, 1 attack, 2 decay, 3 sustain, 4 release; ai / di / ri = progress per sample;
// xpa / pd / pr = curve exponents (1 = linear, > 1 = exponential / snappy)
function env_run(b, ai, di, s, ri, xpa, pd, pr) local(st, t, v) (
  st = b[0];
  st == 1 ? ( t = b[1] + ai; t >= 1 ? ( b[0] = 2; t = 0; v = 1; ) : v = b[3] + (1 - b[3]) * (1 - (1 - t) ^ xpa); b[1] = t; ) :
  st == 2 ? ( t = b[1] + di; t >= 1 ? ( b[0] = 3; t = 0; v = s; ) : v = s + (1 - s) * (1 - t) ^ pd; b[1] = t; ) :
  st == 3 ? v = s :
  st == 4 ? ( t = b[1] + ri; t >= 1 ? ( b[0] = 0; v = 0; ) : v = b[3] * (1 - t) ^ pr; b[1] = t; ) :
  v = 0;
  b[2] = v;
  v;
);
function env_gate(b, on) (
  on ? ( b[0] = 1; b[1] = 0; b[3] = b[2]; ) : b[0] > 0 ? ( b[0] = 4; b[1] = 0; b[3] = b[2]; );
);
function crv(c) ( 2 ^ (c * 3) );                        // -1..1 -> exponent 1/8 .. 8

// ---------- LFOs (state: phase, held S&H value, smoothed S&H) ----------
function lfo_run(b, inc, wave, sk, slew) local(ph, y) (
  ph = b[0] + inc; ph >= 1 ? ( ph -= floor(ph); b[1] = rand(2) - 1; ); b[0] = ph;
  wave == 0 ? y = sin(2 * $pi * (ph < sk ? 0.5 * ph / sk : 0.5 + 0.5 * (ph - sk) / (1 - sk))) :
  wave == 1 ? y = (ph < sk ? 2 * ph / sk - 1 : 1 - 2 * (ph - sk) / (1 - sk)) :
  wave == 2 ? y = (ph < sk ? 1 : -1) :
  ( b[2] += (b[1] - b[2]) * slew; y = b[2]; );
  y;
);
// one LFO's value into the modulation sums (mp_a, mp_b in semitones; m_pan; m_lev; m_xm; m_nz; m_mo = morph)
function lfo_route(dest, amt, y) (
  dest == 1 ? ( mp_a += y * amt * amt * 12; mp_b += y * amt * amt * 12; ) :
  dest == 2 ? mp_a += y * amt * amt * 12 :
  dest == 3 ? mp_b += y * amt * amt * 12 :
  dest == 4 ? m_pan += y * amt :
  dest == 5 ? m_lev *= 1 - amt * 0.5 * (1 - y) :
  dest == 6 ? m_xm *= max(1 + y * amt * 1.5, 0) :
  dest == 7 ? m_nz += y * amt * 0.5 :
  dest == 8 ? m_mo += y * amt * 0.5;
);

// ---------- oscillators ----------
// MODERN: interpolated read of the mip level for this pitch, at phase p (0..1, any offset); m = the morph
// position: the START and END tables blended (the same as the partial levels blended, as both are sums of
// the same sines)
function osc_modern(o, p, inc, m) local(lv, mh, x, i, f, base, y) (
  mh = 0.45 / max(inc, 0.0000001);                    // highest partial that stays under Nyquist
  lv = mh >= 64 ? 0 : mh >= 32 ? 1 : mh >= 16 ? 2 : mh >= 8 ? 3 : mh >= 4 ? 4 : mh >= 2 ? 5 : 6;
  base = TA + (o * 7 + lv) * TLV;
  p -= floor(p); x = p * 2048; i = floor(x); f = x - i;
  y = base[i] + f * (base[i + 1] - base[i]);
  m > 0.0001 ? ( base += 14 * TLV; y += m * (base[i] + f * (base[i + 1] - base[i]) - y); );
  y;
);
function q8(x) ( floor(min(max(x, -1), 1) * 127 + 0.5) / 127; );
// VINTAGE: 256-byte table, truncated read (no interpolation, no band-limiting); a morph rewrites the table as
// 8-bit values between START and END
function osc_vintage(o, p, m) local(i, y) (
  p -= floor(p); i = o * 256 + floor(p * 256); y = TV[i];
  m > 0.0001 ? y = q8(y + m * (TV[i + 512] - y));
  y;
);
// the morph runner for one oscillator, every 16 samples (b = its progress): mode 1 ONCE, 2 LOOP (there and back), 3 ENVELOPE
// (e = the path's envelope); pc = the curve exponent. Returns 0..1 before DEPTH.
function morph_run(b, mode, inc, pc, e) local(t, x) (
  mode == 3 ? x = e : (
    t = b[0] + inc;
    mode == 1 ? ( t = min(t, 1); x = t; ) : ( t >= 2 ? t -= 2; x = t < 1 ? t : 2 - t; );
    b[0] = t;
  );
  1 - (1 - min(max(x, 0), 1)) ^ pc;
);

// noise for one path: b = its noise state (filter, LFSR, clock phase, held output)
function noise_run(b, lfsr, inc, c) local(r, bit) (
  lfsr ? (
    b[2] += inc;
    b[2] >= 1 ? (
      b[2] -= floor(b[2]);
      r = b[1]; r < 1 ? r = 0x7FFF;
      bit = (r ~ (r >> 1)) & 1; r = (r >> 1) | (bit << 14); b[1] = r;
      b[3] = (r & 1) * 2 - 1;
    );
    b[3];
  ) : ( b[0] += ((rand(2) - 1) - b[0]) * c; b[0] * (1 + (1 - c) * 2); );
);

// ---------- voices ----------
function vptr(v) ( VB + v * VSZ; );
function v_idle(v) local(b) ( b = vptr(v); b[F_EA] == 0 && b[F_EB] == 0; );
function v_start(v, n, vel, retrig) local(b) (
  b = vptr(v);
  b[F_TGT] = n; b[F_VEL] = vel / 127;
  glide_c >= 1 || !b[F_PLAYED] ? b[F_CUR] = n;
  retrig ? (
    b[F_DET] = ins_note_detune();
    b[F_MA] = 0; b[F_MB] = 0;                         // the morph starts again at START
    b[F_MTS] = 1 + (rand(2) - 1) * ins_amt * 0.15;    // drift: this note's morph a little faster or slower
    env_gate(b + F_EA, 1); env_gate(b + F_EB, 1);
    b[F_L1] = 0; b[F_L2] = 0;                         // poly LFOs restart with the note
    b[F_L1 + 1] = rand(2) - 1; b[F_L2 + 1] = rand(2) - 1;
  );
  b[F_PLAYED] = 1;
);
function v_stop(v) local(b) ( b = vptr(v); env_gate(b + F_EA, 0); env_gate(b + F_EB, 0); );
function v_glide(v, n) ( vptr(v)[F_TGT] = n; );

// one voice, one sample: adds into oL / oR
function v_run(v) local(b, ea, eb, qa, qb, fa, fb, ia, ib, xa, xb, ya, yb, na, nb, nza, nzb, g, pna, pnb, y1, y2, xab, xba, ma, mb) (
  b = vptr(v);
  b[F_EA] || b[F_EB] ? (
    b[F_CUR] += (b[F_TGT] - b[F_CUR]) * glide_c;
    ea = env_run(b + F_EA, eA_ai, eA_di, eA_s, eA_ri, eA_pa, eA_pd, eA_pr);
    eb = env_run(b + F_EB, eB_ai, eB_di, eB_s, eB_ri, eB_pa, eB_pd, eB_pr);
    // modulation: the global LFO values, or this voice's own
    mp_a = 0; mp_b = 0; m_pan = 0; m_lev = 1; m_xm = 1; m_nz = 0; m_mo = 0;
    l1_dest ? lfo_route(l1_dest, l1_amt, l1_glob ? l1_gy : lfo_run(b + F_L1, l1_inc, l1_wave, l1_sk, l1_slew));
    l2_dest ? lfo_route(l2_dest, l2_amt, l2_glob ? l2_gy : lfo_run(b + F_L2, l2_inc, l2_wave, l2_sk, l2_slew));
    // spectral morph: each oscillator's position, START (0) .. END (1), scaled by DEPTH and velocity, plus the
    // LFO and (MODE on) the drift; worked out every 16 samples, glided over 3 ms and ramped per sample, so a
    // mode change or a stolen voice never clicks
    mo_run ? (
      mo_tick ? (
        ma = mA_mode ? morph_run(b + F_MA, mA_mode, mA_inc * b[F_MTS], mA_pc, ea) * mA_dep * (1 - mA_vs + mA_vs * b[F_VEL]) + mo_drift : 0;
        mb = mB_mode ? morph_run(b + F_MB, mB_mode, mB_inc * b[F_MTS], mB_pc, eb) * mB_dep * (1 - mB_vs + mB_vs * b[F_VEL]) + mo_drift : 0;
        b[F_MTA] += (min(max(ma + m_mo, 0), 1) - b[F_MTA]) * msm;
        b[F_MTB] += (min(max(mb + m_mo, 0), 1) - b[F_MTB]) * msm;
        b[F_MDA] = (b[F_MTA] - b[F_MSA]) * 0.0625; b[F_MDB] = (b[F_MTB] - b[F_MSB]) * 0.0625;
      );
      b[F_MSA] += b[F_MDA]; b[F_MSB] += b[F_MDB];
    );
    // pitch
    qa = b[F_CUR] + oA_st + oA_penv * ea + mp_a + bend_st;
    qb = b[F_CUR] + oB_st + oB_penv * eb + mp_b + bend_st + ins_aux;
    fa = 440 * 2 ^ ((qa - 69) / 12) * ins_pitch * b[F_DET];
    fb = 440 * 2 ^ ((qb - 69) / 12) * ins_pitch * b[F_DET];
    ia = min(fa / srate, 0.49); ib = min(fb / srate, 0.49);
    // vintage: the phase step is a whole number of 16-bit accumulator steps
    oA_vin ? ia = floor(ia * 65536) / 65536;
    oB_vin ? ib = floor(ib * 65536) / 65536;
    // (vintage: the card rewrites its table between cycles, in 32 steps from START to END)
    b[F_PHA] += ia; b[F_PHA] >= 1 ? ( b[F_PHA] -= floor(b[F_PHA]); oA_vin ? b[F_MVA] = floor(b[F_MSA] * 32 + 0.5) / 32; );
    b[F_PHB] += ib; b[F_PHB] >= 1 ? ( b[F_PHB] -= floor(b[F_PHB]); oB_vin ? b[F_MVB] = floor(b[F_MSB] * 32 + 0.5) / 32; );
    // cross-modulation: A modulates B with A's current output, B modulates A with B's last
    xab = xm_ab * m_xm; xba = xm_ba * m_xm;
    xa = b[F_PHA] + xba * b[F_LASTB];
    ya = oA_on ? ( oA_vin ? osc_vintage(0, xa, b[F_MVA]) : osc_modern(0, xa, ia, b[F_MSA]) ) : 0;
    xb = b[F_PHB] + xab * ya;
    yb = oB_on ? ( oB_vin ? osc_vintage(1, xb, b[F_MVB]) : osc_modern(1, xb, ib, b[F_MSB]) ) : 0;
    b[F_LASTA] = ya; b[F_LASTB] = yb;
    // the vintage DAC: a 31.25 kHz zero-order hold
    oA_vin ? ( b[F_ZA] += dac_inc; b[F_ZA] >= 1 ? ( b[F_ZA] -= 1; b[F_ZA + 1] = ya; ); ya = b[F_ZA + 1]; );
    oB_vin ? ( b[F_ZB] += dac_inc; b[F_ZB] >= 1 ? ( b[F_ZB] -= 1; b[F_ZB + 1] = yb; ); yb = b[F_ZB + 1]; );
    // noise blend, envelope, the 8-bit amplitude stage on vintage paths
    nza = min(max(nA_mix + m_nz, 0), 1); nzb = min(max(nB_mix + m_nz, 0), 1);
    na = nza > 0 ? noise_run(b + F_NA, nA_lfsr, nA_inc, nA_c) : 0;
    nb = nzb > 0 ? noise_run(b + F_NB, nB_lfsr, nB_inc, nB_c) : 0;
    y1 = (ya * (1 - nza) + na * nza) * ea;
    y2 = (yb * (1 - nzb) + nb * nzb) * eb;
    oA_vin ? y1 = q8(y1);
    oB_vin ? y2 = q8(y2);
    g = (1 - velsens + velsens * b[F_VEL]) * m_lev;
    y1 *= g * pA_lvl; y2 *= g * pB_lvl;
    // pan (constant power, 0 dB in the centre); the LFO pans A one way and B the other
    pna = min(max(pA_pan + m_pan, -1), 1); pnb = min(max(pB_pan - m_pan, -1), 1);
    oL += y1 * sqrt(1 - pna) + y2 * sqrt(1 - pnb);
    oR += y1 * sqrt(1 + pna) + y2 * sqrt(1 + pnb);
  );
);

// ---------- notes ----------
function all_off() local(i) (
  i = 0; loop(NV, vptr(i)[F_GATE] ? v_stop(i); vptr(i)[F_GATE] = 0; vptr(i)[F_SUS] = 0; vptr(i)[F_NOTE] = -1; i += 1; );
  hcount = 0;
);
function poly_on(n, v) local(i, best, ba, b) (
  best = -1;
  i = 0; loop(NV, vptr(i)[F_NOTE] == n ? best = i; i += 1; );                  // same note: retrigger it
  best < 0 ? ( ba = -1; i = 0; loop(NV, v_idle(i) && (best < 0 || vptr(i)[F_AGE] < ba) ? ( best = i; ba = vptr(i)[F_AGE]; ); i += 1; ); );
  best < 0 ? ( ba = -1; i = 0; loop(NV, !vptr(i)[F_GATE] && (best < 0 || vptr(i)[F_AGE] < ba) ? ( best = i; ba = vptr(i)[F_AGE]; ); i += 1; ); );
  best < 0 ? ( ba = -1; i = 0; loop(NV, best < 0 || vptr(i)[F_AGE] < ba ? ( best = i; ba = vptr(i)[F_AGE]; ); i += 1; ); );
  vage += 1; b = vptr(best); b[F_AGE] = vage; b[F_NOTE] = n; b[F_GATE] = 1; b[F_SUS] = 0; nv_new = best;
  v_start(best, n, v, 1);
);
function poly_off(n) local(i, b) (
  i = 0; loop(NV, b = vptr(i); b[F_NOTE] == n && b[F_GATE] ? ( pedal ? b[F_SUS] = 1 : ( v_stop(i); b[F_GATE] = 0; ); ); i += 1; );
);
// mono: a stack of held notes, the newest sounds. MONO LEGATO glides between held notes without
// restarting the envelopes; RETRIG restarts them on every note.
function mono_on(n, v) local(i, f, legato) (
  legato = hcount > 0;
  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; );
  hcount < 127 ? ( HN[hcount] = n; hcount += 1; );
  legato ? ( v_glide(0, n); v_start(0, n, v, vmode == 2); ) : v_start(0, n, v, 1);
  nv_new = 0;
  vptr(0)[F_NOTE] = n; vptr(0)[F_GATE] = 1;
);
function mono_off(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;
    hcount > 0 ? v_glide(0, HN[hcount - 1]) : ( pedal ? vptr(0)[F_SUS] = 1 : ( v_stop(0); vptr(0)[F_GATE] = 0; ); );
  );
);
function note_on(n, v) ( vmode ? mono_on(n, v) : poly_on(n, v); );
function note_off(n) ( vmode ? mono_off(n) : poly_off(n); );
function pedal_up() local(i, b) ( i = 0; loop(NV, b = vptr(i); b[F_SUS] ? ( v_stop(i); b[F_GATE] = 0; b[F_SUS] = 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 div_name(d) (
  d == 1 ? "1/32" : d == 2 ? "1/16T" : d == 3 ? "1/16" : d == 4 ? "1/8T" : d == 5 ? "1/16D" : d == 6 ? "1/8" : d == 7 ? "1/4T" :
  d == 8 ? "1/8D" : d == 9 ? "1/4" : d == 10 ? "1/2T" : d == 11 ? "1/4D" : d == 12 ? "1/2" : "1/1";
);
function div_beats(d) (
  d == 1 ? 0.125 : d == 2 ? 1 / 6 : d == 3 ? 0.25 : d == 4 ? 1 / 3 : d == 5 ? 0.375 : d == 6 ? 0.5 : d == 7 ? 2 / 3 :
  d == 8 ? 0.75 : d == 9 ? 1 : d == 10 ? 4 / 3 : d == 11 ? 1.5 : d == 12 ? 2 : 4;
);
function wave_name(w) ( w == 0 ? "SINE" : w == 1 ? "TRI" : w == 2 ? "SQUARE" : "S&H"; );
function dest_name(d) (
  d == 0 ? "OFF" : d == 1 ? "PITCH" : d == 2 ? "PITCH A" : d == 3 ? "PITCH B" : d == 4 ? "PAN" : d == 5 ? "LEVEL" : d == 6 ? "X-MOD" : d == 7 ? "NOISE" : "MORPH";
);

// ---------- parameters ----------
function etime(ms) ( 1 / max(ms * 0.001 * srate, 1); );
function update() local(t) (
  vmode = slider1;
  glide_c = slider2 > 0 ? 1 - exp(-4.6 / (slider2 * 0.001 * srate)) : 1;
  bend_rng = slider3; velsens = slider4 / 100; vol_t = 10 ^ (slider5 / 20); chan = slider6; drift = slider7 / 100;
  oA_vin = slider8; oA_st = slider10 + slider11 / 100; pA_lvl = slider12 / 100; pA_pan = slider13 / 100;
  nA_mix = slider14 / 100; nA_lfsr = slider15; oA_penv = slider17;
  oB_vin = slider18; oB_st = slider20 + slider21 / 100; pB_lvl = slider22 / 100; pB_pan = slider23 / 100;
  nB_mix = slider24 / 100; nB_lfsr = slider25; oB_penv = slider27;
  // the oscillator only runs when its path can be heard (or it modulates the other)
  xm_ab = slider28 / 100; xm_ba = slider29 / 100;
  oA_on = pA_lvl > 0 && nA_mix < 1 || xm_ab > 0;
  oB_on = pB_lvl > 0 && nB_mix < 1 || xm_ba > 0;
  // noise TONE: 200 Hz .. ~36 kHz (the LFSR clock, or the white noise's low-pass)
  t = 200 * 2 ^ (slider16 / 100 * 7.5); nA_inc = min(t / srate, 1); nA_c = 1 - exp(-2 * $pi * min(t, srate * 0.45) / srate);
  t = 200 * 2 ^ (slider26 / 100 * 7.5); nB_inc = min(t / srate, 1); nB_c = 1 - exp(-2 * $pi * min(t, srate * 0.45) / srate);
  eA_ai = etime(slider30); eA_di = etime(slider31); eA_s = slider32 / 100; eA_ri = etime(slider33);
  eA_pa = crv(slider34 / 100); eA_pd = crv(slider35 / 100); eA_pr = crv(slider36 / 100);
  eB_ai = etime(slider37); eB_di = etime(slider38); eB_s = slider39 / 100; eB_ri = etime(slider40);
  eB_pa = crv(slider41 / 100); eB_pd = crv(slider42 / 100); eB_pr = crv(slider43 / 100);
  l1_wave = slider44; l1_inc = slider45 / srate; l1_sk = min(max(slider46 / 100, 0.02), 0.98); l1_glob = slider47; l1_dest = slider48;
  l1_slew = slider46 <= 50 ? 1 : 1 - exp(-l1_inc * 6 / ((slider46 - 50) / 50));
  l2_wave = slider50; l2_inc = slider51 / srate; l2_sk = min(max(slider52 / 100, 0.02), 0.98); l2_glob = slider53; l2_dest = slider54;
  l2_slew = slider52 <= 50 ? 1 : 1 - exp(-l2_inc * 6 / ((slider52 - 50) / 50));
  l2_amt = slider55 / 100;
  dl_on = slider56; dl_pp = slider57;
  dl_fb = slider62 / 100; dl_x = slider63 / 100;
  dl_fb + dl_x > 0.97 ? ( t = 0.97 / (dl_fb + dl_x); dl_fb *= t; dl_x *= t; );   // the loop always dies away
  dl_lpc = 1 - exp(-2 * $pi * min(slider64, srate * 0.45) / srate);
  dl_hpc = 1 - exp(-2 * $pi * slider65 / srate);
  dl_mix = slider66 / 100;
  ins_output_setup(67); ins_stereo_setup(73);   // output stage: CLIP, always on
  // spectral morph: mode, speed (1 / time), curve exponent, depth, velocity
  mA_mode = slider75; mA_inc = 16 / max(slider76 * 0.001 * srate, 1); mA_pc = crv(slider77 / 100);
  mA_dep = slider78 / 100; mA_vs = slider72 / 100;
  mB_mode = slider79; mB_inc = 16 / max(slider80 * 0.001 * srate, 1); mB_pc = crv(slider81 / 100);
  mB_dep = slider82 / 100; mB_vs = slider83 / 100;
  // partial count changed: rebuild that oscillator's START and END tables
  slider9 != last_n9 ? ( last_n9 = slider9; s0_dirty = 1; s2_dirty = 1; );
  slider19 != last_n19 ? ( last_n19 = slider19; s1_dirty = 1; s3_dirty = 1; );
);
function dl_time(div, ms) ( div > 0 ? div_beats(div) * 60 / max(tempo, 20) : ms * 0.001; );

// ---------- presets: the factory bank ----------
// Each patch is a set of rows: the voice, both paths, both envelopes, both LFOs, the delay, then the
// partial sets (shape generators and single partials; sets 2 / 3 are the END screens of A / B - a patch that
// draws no END gets a copy of its START), then the morph. Sliders 1-66, 72 and 75-83 are the patch; the MIDI
// channel, drift, volume, stereo and output stage are left alone.
function p_voice(mode, glide, bend, vel, xab, xba) ( slider1 = mode; slider2 = glide; slider3 = bend; slider4 = vel; slider28 = xab; slider29 = xba; );
function p_osc(o, mode, np, tr, fine, lvl, pan, nz, nt, tone, penv) local(b) (
  b = 8 + o * 10;
  slider(b) = mode; slider(b + 1) = np; slider(b + 2) = tr; slider(b + 3) = fine; slider(b + 4) = lvl;
  slider(b + 5) = pan; slider(b + 6) = nz; slider(b + 7) = nt; slider(b + 8) = tone; slider(b + 9) = penv;
);
function p_env(o, a, d, s, r, ca, cd, cr) local(b) (
  b = 30 + o * 7;
  slider(b) = a; slider(b + 1) = d; slider(b + 2) = s; slider(b + 3) = r; slider(b + 4) = ca; slider(b + 5) = cd; slider(b + 6) = cr;
);
function p_lfo(l, wave, rate, skew, glob, dest, amt) local(b) (
  b = 44 + l * 6;
  slider(b) = wave; slider(b + 1) = rate; slider(b + 2) = skew; slider(b + 3) = glob; slider(b + 4) = dest; slider(b + 5) = amt;
);
function p_delay(on, pp, dvl, msl, dvr, msr, fb, x, lp, hp, mix) (
  slider56 = on; slider57 = pp; slider58 = dvl; slider59 = msl; slider60 = dvr; slider61 = msr;
  slider62 = fb; slider63 = x; slider64 = lp; slider65 = hp; slider66 = mix;
);
// the morph for oscillator o: mode (0 off, 1 once, 2 loop, 3 envelope), time ms, curve %, depth %, velocity %
function p_morph(o, mode, t, c, dep, vel) (
  o ? ( slider79 = mode; slider80 = t; slider81 = c; slider82 = dep; slider83 = vel; ) :
      ( slider75 = mode; slider76 = t; slider77 = c; slider78 = dep; slider72 = vel; );
);
function auto_all() local(i) (
  no_auto ? i = 999 : i = 1;
  i < 999 ? ( loop(63, slider_automate(2 ^ (i - 1)); i += 1; );
  slider_automate(slider64); slider_automate(slider65); slider_automate(slider66); slider_automate(slider72);
  slider_automate(slider75); slider_automate(slider76); slider_automate(slider77); slider_automate(slider78);
  slider_automate(slider79); slider_automate(slider80); slider_automate(slider81); slider_automate(slider82);
  slider_automate(slider83);
  );
);
function load_patch(p) (
  p_lfo(0, 0, 5, 50, 0, 0, 0); p_lfo(1, 1, 0.5, 50, 1, 0, 0);
  p_delay(0, 0, 9, 375, 8, 500, 35, 0, 8000, 80, 25);
  p_morph(0, 0, 2000, 0, 100, 0); p_morph(1, 0, 2000, 0, 100, 0);
  PA[128] = -1; PA[192] = -1;                      // END not drawn yet (a patch's pclear / pshape replaces this)
  p == 0 ? (                                       // 1981 MICRO CHIME: vintage, 16 odd-heavy partials, ping-pong
    p_voice(0, 0, 2, 50, 0, 0);
    p_osc(0, 1, 0, 0, 0, 80, -15, 0, 0, 70, 0);   p_osc(1, 1, 0, 12, 3, 30, 25, 0, 0, 70, 0);
    p_env(0, 1, 2600, 0, 1800, 0, 70, 60);        p_env(1, 1, 1100, 0, 900, 0, 80, 60);
    p_lfo(0, 0, 5.5, 50, 1, 1, 9);
    p_delay(1, 1, 8, 375, 9, 500, 20, 55, 6000, 150, 32);
    pshape(0, 4, 0.7, 0.12); pshape(1, 3, 0, 0); pset(1, 2, 0.2);
    pshape(2, 3, 0, 0); pset(2, 2, 0.1); p_morph(0, 1, 2600, 40, 100, 0);   // the chime mellows as it rings
  ) : p == 1 ? (                                   // LO-FI BIT BASS: mono, vintage, partials 1-4 hard, a little LFSR
    p_voice(1, 25, 2, 40, 0, 0);
    p_osc(0, 1, 0, -12, 0, 100, 0, 8, 1, 55, 0);  p_osc(1, 1, 0, -24, 0, 40, 0, 0, 0, 70, 0);
    p_env(0, 0.5, 260, 55, 60, 0, 70, 60);        p_env(1, 0.5, 400, 70, 60, 0, 50, 50);
    pclear(0); pset(0, 1, 1); pset(0, 2, 1); pset(0, 3, 0.9); pset(0, 4, 0.85);
    pshape(1, 0, 0, 0);
  ) : p == 2 ? (                                   // SPECTRAL AMBIENT PAD: modern, 64 partials, +/-7 cents, slow pan
    p_voice(0, 0, 2, 30, 0, 0);
    p_osc(0, 0, 2, 0, -7, 75, -30, 0, 0, 70, 0);  p_osc(1, 0, 2, 0, 7, 75, 30, 4, 0, 85, 0);
    p_env(0, 2600, 3000, 80, 4500, 80, 40, 80);   p_env(1, 3200, 3000, 80, 5000, 80, 40, 80);
    p_lfo(0, 0, 0.11, 50, 1, 4, 70); p_lfo(1, 1, 0.07, 50, 1, 5, 20);
    p_delay(1, 0, 11, 375, 12, 500, 30, 55, 5200, 220, 45);
    pshape(0, 6, 7, 5); pshape(1, 5, 1.6, 0);
    pshape(2, 6, 16, 4); pshape(3, 8, 1.2, 0);                             // the formant drifts up and back
    p_morph(0, 2, 6000, 0, 100, 0); p_morph(1, 2, 9000, 0, 100, 0);
  ) : p == 3 ? (                                   // E.PIANO TINE: bright tine on A (fast decay), warm sine body on B
    p_voice(0, 0, 2, 75, 14, 0);
    p_osc(0, 0, 1, 0, 0, 55, 0, 0, 0, 70, 0);     p_osc(1, 0, 0, 0, 0, 90, 0, 0, 0, 70, 0);
    p_env(0, 0.7, 520, 0, 260, 0, 85, 70);        p_env(1, 2, 3800, 18, 420, 0, 60, 60);
    p_lfo(0, 0, 4.6, 50, 1, 4, 22);
    pshape(0, 9, 7, 0); pclear(1); pset(1, 1, 1); pset(1, 2, 0.12); pset(1, 3, 0.04);
    pshape(2, 0, 0, 0); pset(2, 2, 0.15); p_morph(0, 1, 900, 60, 100, 40);  // the tine's bark settles to a sine
  ) : p == 4 ? (                                   // DIGITAL SCI-FI DRONE: heavy A > B, S&H on the depth, dark delay
    p_voice(0, 0, 2, 20, 72, 0);
    p_osc(0, 0, 0, 7, 13, 18, -40, 0, 0, 70, 0);  p_osc(1, 0, 1, 0, 0, 85, 20, 0, 0, 70, 0);
    p_env(0, 900, 2000, 100, 3500, 60, 40, 70);   p_env(1, 1200, 2000, 100, 4000, 60, 40, 70);
    p_lfo(0, 3, 3.2, 60, 1, 6, 65); p_lfo(1, 0, 0.05, 50, 1, 4, 40);
    p_delay(1, 0, 11, 375, 10, 500, 78, 15, 2200, 120, 42);
    pclear(0); pset(0, 1, 1); pset(0, 2, 0.5); pset(0, 3, 0.3); pshape(1, 1, 0, 0);
  ) : p == 5 ? (                                   // 8-BIT ARCADE PERC: pitch-dropping vintage thump + LFSR snap
    p_voice(0, 0, 2, 60, 0, 0);
    p_osc(0, 1, 0, -12, 0, 100, 0, 0, 0, 70, 36); p_osc(1, 1, 0, 0, 0, 55, 0, 100, 1, 72, 0);
    p_env(0, 0.5, 150, 0, 90, 0, 90, 80);         p_env(1, 0.5, 70, 0, 50, 0, 85, 80);
    pclear(0); pset(0, 1, 1); pset(0, 3, 0.35); pset(0, 5, 0.12); pshape(1, 0, 0, 0);
  ) : p == 6 ? (                                   // DUAL-TIMBRAL POLY: saw on A hard left, square on B hard right
    p_voice(0, 0, 2, 55, 0, 0);
    p_osc(0, 0, 1, 0, -4, 75, -100, 0, 0, 70, 0); p_osc(1, 0, 1, 0, 4, 75, 100, 0, 0, 70, 0);
    p_env(0, 6, 700, 70, 450, 0, 50, 50);         p_env(1, 12, 900, 65, 500, 0, 50, 50);
    p_delay(1, 0, 8, 375, 9, 500, 25, 10, 7000, 120, 14);
    pshape(0, 1, 0, 0); pshape(1, 2, 0, 0);
  ) : p == 7 ? (                                   // 8-BIT ARCADE SNARE: short thump, bright LFSR body
    p_voice(0, 0, 2, 60, 0, 0);
    p_osc(0, 1, 0, 0, 0, 60, 0, 0, 0, 70, 24);    p_osc(1, 1, 0, 0, 0, 90, 0, 100, 1, 86, 0);
    p_env(0, 0.5, 70, 0, 40, 0, 90, 80);          p_env(1, 0.5, 190, 0, 120, 0, 75, 70);
    pclear(0); pset(0, 1, 1); pset(0, 2, 0.4); pshape(1, 0, 0, 0);
  ) : p == 8 ? (                                   // GLASS ORGAN: vintage drawbars, even partials on B
    p_voice(0, 0, 2, 0, 0, 0);
    p_osc(0, 1, 0, 0, 0, 75, -20, 0, 0, 70, 0);   p_osc(1, 0, 1, 12, 2, 35, 20, 0, 0, 70, 0);
    p_env(0, 4, 300, 100, 120, 0, 50, 60);        p_env(1, 4, 300, 100, 160, 0, 50, 60);
    p_lfo(0, 0, 6.2, 50, 1, 1, 7);
    p_delay(1, 0, 6, 375, 8, 500, 20, 15, 5000, 150, 15);
    pshape(0, 7, 0, 0); pshape(1, 8, 1.2, 0);
  ) : p == 9 ? (                                   // VINTAGE STRINGS: two detuned vintage saws, slow swell
    p_voice(0, 0, 2, 30, 0, 0);
    p_osc(0, 1, 1, 0, -9, 70, -45, 0, 0, 70, 0);  p_osc(1, 1, 1, 0, 9, 70, 45, 0, 0, 70, 0);
    p_env(0, 380, 900, 85, 900, 40, 40, 70);      p_env(1, 450, 900, 85, 1000, 40, 40, 70);
    p_lfo(0, 1, 5.6, 50, 0, 1, 10); p_lfo(1, 0, 0.3, 50, 1, 4, 25);
    p_delay(1, 0, 9, 375, 11, 500, 25, 20, 6000, 180, 22);
    pshape(0, 5, 1.1, 0); pshape(1, 5, 1.1, 0);
  ) : p == 10 ? (                                  // HOLLOW FLUTE: odd partials, breath noise, delayed vibrato feel
    p_voice(0, 0, 2, 50, 0, 0);
    p_osc(0, 0, 0, 12, 0, 80, 0, 10, 0, 78, 0);   p_osc(1, 0, 0, 24, 0, 12, 0, 0, 0, 70, 0);
    p_env(0, 60, 400, 85, 220, 30, 40, 60);       p_env(1, 90, 400, 70, 220, 30, 40, 60);
    p_lfo(0, 0, 5.2, 50, 0, 1, 11);
    p_delay(1, 0, 8, 375, 9, 500, 25, 20, 6500, 200, 18);
    pshape(0, 3, 0, 0); pset(0, 2, 0.08); pshape(1, 0, 0, 0);
  ) : p == 11 ? (                                  // BELL TOWER: spread partials, a fifth-off strike on B
    p_voice(0, 0, 2, 50, 6, 0);
    p_osc(0, 0, 1, 0, 0, 75, -20, 0, 0, 70, 0);   p_osc(1, 0, 0, 19, 14, 45, 25, 0, 0, 70, 0);
    p_env(0, 1, 5200, 0, 3500, 0, 75, 70);        p_env(1, 1, 1800, 0, 1500, 0, 85, 70);
    p_delay(1, 0, 11, 375, 12, 500, 30, 30, 4500, 200, 25);
    pclear(0); pset(0, 1, 1); pset(0, 2, 0.6); pset(0, 3, 0.45); pset(0, 5, 0.5); pset(0, 8, 0.35); pset(0, 11, 0.3);
    pset(0, 13, 0.2); pset(0, 17, 0.18); pset(0, 23, 0.12); pset(0, 29, 0.08);
    pclear(1); pset(1, 1, 1); pset(1, 3, 0.3); pset(1, 7, 0.2);
    pclear(2); pset(2, 1, 1); pset(2, 2, 0.5); pset(2, 3, 0.3); pset(2, 5, 0.12);   // the strike dies to the hum
    p_morph(0, 1, 5000, 50, 100, 0);
  ) : p == 12 ? (                                  // CHOIR FORMANT: two formant sets, slow vibrato, swell
    p_voice(0, 0, 2, 30, 0, 0);
    p_osc(0, 0, 2, 0, -5, 70, -25, 0, 0, 70, 0);  p_osc(1, 0, 2, 0, 5, 60, 25, 6, 0, 80, 0);
    p_env(0, 450, 800, 85, 900, 40, 40, 70);      p_env(1, 600, 800, 85, 1100, 40, 40, 70);
    p_lfo(0, 0, 5, 50, 0, 1, 9); p_lfo(1, 1, 0.2, 50, 1, 4, 30);
    p_delay(1, 0, 11, 375, 12, 500, 30, 35, 5000, 200, 30);
    pshape(0, 6, 4, 2.2); pset(0, 12, 0.35); pset(0, 13, 0.3); pshape(1, 6, 6, 2.5); pset(1, 16, 0.25);
    pshape(2, 6, 9, 2.5); pset(2, 2, 0.3); pset(2, 18, 0.3); p_morph(0, 2, 4200, 0, 100, 0);   // "ah" to "ee" and back
  ) : p == 13 ? (                                  // SQUARE SYNC LEAD: mono glide, B > A modulation, bright square
    p_voice(1, 60, 2, 50, 0, 18);
    p_osc(0, 0, 1, 0, 0, 80, 0, 0, 0, 70, 0);     p_osc(1, 0, 0, 12, 0, 0, 0, 0, 0, 70, 0);
    p_env(0, 2, 600, 80, 250, 0, 50, 50);         p_env(1, 2, 600, 80, 250, 0, 50, 50);
    p_lfo(0, 0, 5.4, 50, 0, 1, 9);
    p_delay(1, 0, 8, 375, 9, 500, 30, 20, 6000, 150, 20);
    pshape(0, 2, 0, 0); pshape(1, 0, 0, 0);
  ) : p == 14 ? (                                  // GLASS SWEEP: a dark pad that opens into a high glassy formant
    p_voice(0, 0, 2, 40, 0, 0);
    p_osc(0, 0, 2, 0, -4, 80, -20, 0, 0, 70, 0);  p_osc(1, 0, 1, 12, 4, 25, 30, 0, 0, 70, 0);
    p_env(0, 300, 2000, 80, 2500, 40, 40, 70);    p_env(1, 500, 2000, 80, 2500, 40, 40, 70);
    p_lfo(1, 0, 0.13, 50, 1, 4, 35);
    p_delay(1, 0, 9, 375, 11, 500, 30, 25, 6000, 180, 28);
    pshape(0, 5, 2, 0); pshape(2, 6, 24, 6); pset(2, 2, 0.4); pshape(1, 0, 0, 0);
    p_morph(0, 1, 3500, -30, 100, 0);
  ) : p == 15 ? (                                  // VOWEL LEAD: mono, the vowel opens with the envelope
    p_voice(1, 40, 2, 50, 0, 0);
    p_osc(0, 0, 2, 0, 0, 85, 0, 0, 0, 70, 0);     p_osc(1, 0, 0, -12, 0, 20, 0, 0, 0, 70, 0);
    p_env(0, 120, 900, 55, 300, 20, 40, 60);      p_env(1, 20, 600, 80, 300, 0, 50, 50);
    p_lfo(0, 0, 5.3, 50, 0, 1, 9); p_lfo(1, 0, 0.25, 50, 1, 8, 30);
    p_delay(1, 0, 8, 375, 9, 500, 25, 15, 6000, 150, 18);
    pshape(0, 6, 3, 1.6); pshape(2, 6, 11, 2.5); pset(2, 2, 0.35); pshape(1, 0, 0, 0);
    p_morph(0, 3, 2000, 0, 100, 30);
  ) : p == 16 ? (                                  // MORPH PLUCK: bright saw at the strike, dark as it dies
    p_voice(0, 0, 2, 70, 0, 0);
    p_osc(0, 0, 2, 0, 0, 85, -10, 0, 0, 70, 0);   p_osc(1, 0, 0, -12, 0, 30, 10, 0, 0, 70, 0);
    p_env(0, 0.5, 700, 0, 400, 0, 70, 60);        p_env(1, 0.5, 900, 0, 400, 0, 70, 60);
    p_delay(1, 1, 6, 375, 9, 500, 25, 35, 5500, 200, 22);
    pshape(0, 5, 2.5, 0); pshape(2, 1, 0, 0); pshape(1, 0, 0, 0);
    p_morph(0, 3, 2000, 0, 100, 70);
  ) : p == 17 ? (                                  // ORGAN TO CHOIR: drawbars that slowly turn into a voice
    p_voice(0, 0, 2, 20, 0, 0);
    p_osc(0, 0, 1, 0, 0, 80, 0, 0, 0, 70, 0);     p_osc(1, 0, 0, 0, 0, 0, 0, 0, 0, 70, 0);
    p_env(0, 10, 300, 100, 600, 0, 50, 60);       p_env(1, 2, 400, 70, 300, 0, 50, 50);
    p_lfo(0, 0, 5.8, 50, 0, 1, 6);
    p_delay(1, 0, 9, 375, 11, 500, 30, 25, 5000, 200, 25);
    pshape(0, 7, 0, 0); pshape(2, 6, 7, 2.2); pset(2, 14, 0.3); pshape(1, 0, 0, 0);
    p_morph(0, 1, 6000, -40, 100, 0);
  ) : p == 18 ? (                                  // BIT SHIMMER: the old card flickering between two tables
    p_voice(0, 0, 2, 50, 0, 0);
    p_osc(0, 1, 1, 0, 0, 80, -15, 0, 0, 70, 0);   p_osc(1, 1, 0, 12, 4, 20, 25, 0, 0, 70, 0);
    p_env(0, 2, 1500, 40, 1200, 0, 60, 60);       p_env(1, 2, 900, 0, 900, 0, 70, 60);
    p_delay(1, 1, 8, 375, 9, 500, 25, 40, 6000, 200, 30);
    pshape(0, 4, 0.8, 0.1); pshape(2, 9, 12, 0); pshape(1, 0, 0, 0);
    p_morph(0, 2, 700, 0, 100, 0);
  ) : p == 19 ? (                                  // TIDAL PAD: both screens rolling at their own pace
    p_voice(0, 0, 2, 30, 0, 0);
    p_osc(0, 0, 2, 0, -6, 70, -40, 0, 0, 70, 0);  p_osc(1, 0, 2, 0, 6, 70, 40, 0, 0, 70, 0);
    p_env(0, 1800, 3000, 85, 4000, 70, 40, 80);   p_env(1, 2200, 3000, 85, 4500, 70, 40, 80);
    p_lfo(1, 1, 0.09, 50, 1, 4, 25);
    p_delay(1, 0, 11, 375, 12, 500, 35, 40, 5000, 200, 40);
    pshape(0, 5, 1.4, 0); pshape(2, 8, 1, 0); pshape(1, 6, 8, 4); pshape(3, 6, 20, 5);
    p_morph(0, 2, 7000, 0, 100, 0); p_morph(1, 2, 11000, 30, 100, 0);
  ) : p == 20 ? (                                  // VELOCITY BRASS: dark when soft, the full saw when hit hard
    p_voice(0, 0, 2, 70, 0, 0);
    p_osc(0, 0, 2, 0, 0, 75, -15, 0, 0, 70, 0);   p_osc(1, 0, 2, 0, 7, 45, 15, 0, 0, 70, 0);
    p_env(0, 60, 600, 70, 250, 20, 50, 60);       p_env(1, 80, 600, 70, 250, 20, 50, 60);
    p_lfo(0, 0, 5.5, 50, 0, 1, 6);
    p_delay(1, 0, 9, 375, 11, 500, 20, 15, 6000, 180, 14);
    pshape(0, 5, 2.2, 0); pshape(2, 1, 0, 0); pshape(1, 5, 2.2, 0); pshape(3, 1, 0, 0);
    p_morph(0, 3, 2000, -20, 100, 80); p_morph(1, 3, 2000, -20, 100, 80);
  ) : (                                            // INIT: one modern saw
    p_voice(0, 0, 2, 60, 0, 0);
    p_osc(0, 0, 0, 0, 0, 80, 0, 0, 0, 70, 0);     p_osc(1, 0, 0, 0, 0, 0, 0, 0, 0, 70, 0);
    p_env(0, 2, 400, 70, 300, 0, 50, 50);         p_env(1, 2, 400, 70, 300, 0, 50, 50);
    pshape(0, 1, 0, 0); pshape(1, 0, 0, 0);
  );
  PA[128] < 0 ? pcopy(2, 0); PA[192] < 0 ? pcopy(3, 1);
  s0_dirty = 1; s1_dirty = 1; s2_dirty = 1; s3_dirty = 1;
  auto_all();
  panic_dirty = 1; update();
);

// per-patch level trim (single notes and chords land at a similar loudness)
function patch_trim(p) (
  p == 0 ? 0.541 : p == 1 ? 1.089 : p == 2 ? 0.405 : p == 3 ? 0.377 : p == 4 ? 0.356 : p == 5 ? 0.253 : p == 6 ? 0.316 :
  p == 7 ? 0.242 : p == 8 ? 0.357 : p == 9 ? 0.315 : p == 10 ? 0.345 : p == 11 ? 0.431 : p == 12 ? 0.568 : p == 13 ? 0.613 :
  p == 14 ? 0.328 : p == 15 ? 1.480 : p == 16 ? 0.415 : p == 17 ? 0.363 : p == 18 ? 0.553 : p == 19 ? 0.357 : p == 20 ? 0.450 :
  0.544;
);
// load_preset(k): factory patch k (1 .. NPATCH), with its level trim
function load_preset(k) (
  k = min(max(floor(k), 1), NPATCH);
  load_patch(k - 1); ptr_t = patch_trim(k - 1);
);

// ---------- state ----------
ins_drift_init();
ostL.ost_ch_init(); ostR.ost_ch_init();
sm = 1 - exp(-1 / (0.03 * srate));
dac_inc = 31250 / srate;
bend = 0; bend_st = 0; modw = 0; pedal = 0; hcount = 0; vage = 0; qcount = 0;
i = 0; loop(NV, vptr(i)[F_NOTE] = -1; vptr(i)[F_DET] = 1; vptr(i)[F_NB + 1] = 0x5A5A; vptr(i)[F_NA + 1] = 0x1234 + i; i += 1; );
last_n9 = -1; last_n19 = -1; pkl_disp = 0; pkr_disp = 0;
dwL = 0; dwR = 0; dlpL = dlpR = dhpL = dhpR = 0; dtL = dtR = 0.3 * srate; dpos = 0;
// a fresh instance starts on the first factory patch (a saved project brings its own state back)
!init_done ? ( no_auto = 1; load_preset(1); no_auto = 0; init_done = 1; );
!ui_dflt ? ui_thm = 5; !ui_dflt ? ui_hw = 1;                                 // fixed faceplate: graphite with phosphor green
!ui_dflt ? ui_kstyle = 0;
PNL = 0x1B1D1C; INKL = 0xD6E4D8; PHO = 0x3DFF86; PHOD = 0x0B2A16; AMBL = 0xFFB030; REDL = 0xFF3A2A;
SH1 = 0x222524; SH2 = 0x1F2221; SH3 = 0x1C1E1D;
function mgroup(x, y, w, h, sec, title, shade) (
  pk_inset(x * sc, y * sc, w * sc, h * sc, shade);
  pk_box(x * sc, y * sc, w * sc, h * sc, ui_mix(PHO, INKL, 0.45), title);
  ui_section(sec == 0 ? 2 : sec); C_INK = INKL;
);
update();
s0_dirty = 1; s1_dirty = 1; s2_dirty = 1; s3_dirty = 1;
vol_s = vol_t; ptr_s = ptr_t;
duck_inc = 1 / (0.006 * srate); duck_g = 1; duck_t = 1; pr_pend = 0;   // the preset-change duck (6 ms)
msm = 1 - exp(-16 / (0.003 * srate));               // the morph glide (3 ms, at the 16-sample control rate)
ui_dflt = 1;   // theme defaults above apply once: @init re-runs (playback start, device reset) must not undo the saved theme

// PRESET parameter: 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 never reloads a preset over its settings.
// A preset change never clicks: the voices duck out over 6 ms (pr_pend waits for it), the patch loads, the old
// notes stop and all four tables are rebuilt at once, then the voices come back (the delay's tail runs on).
function prs_chk() local(k) (
  prs_dirty > 0 ? (
    k = prs_dirty; prs_dirty = 0; pr_pend = k;
    prs_sel = k; slider_automate(prs_sel); prs_cur_d = k;
    prs_last = prs_sel;
  ) : prs_ok ? (
    prs_sel = max(0, min(NPATCH, prs_sel | 0));
    prs_sel != prs_last ? ( prs_last = prs_sel; prs_sel > 0 ? ( pr_pend = prs_sel; prs_cur_d = prs_sel; ); );
  ) : ( prs_last = prs_sel | 0; prs_ok = 1; );
  pr_pend > 0 ? (
    duck_t = 0;
    duck_g <= 0.000001 ? (
      load_preset(pr_pend); pr_pend = 0;
      memset(VB, 0, NV * VSZ);                       // the old notes stop (silent by now)
      i = 0; loop(NV, vptr(i)[F_NOTE] = -1; vptr(i)[F_DET] = 1; vptr(i)[F_NB + 1] = 0x5A5A; vptr(i)[F_NA + 1] = 0x1234 + i; i += 1; );
      hcount = 0; panic_dirty = 0;
      build_tables(0); build_tables(1); build_tables(2); build_tables(3);
      s0_dirty = 0; s1_dirty = 0; s2_dirty = 0; s3_dirty = 0;
      duck_t = 1;
    );
  );
);
// the panel's partial-set requests (SHAPES, RANDOM, COPY, SWAP) run here, on the audio side: ed_dirty = the job,
// ed_set_d = the set (0..3) it works on. 1..10 a shape, 11 clear, 12 random, 13 copy to the other side, 14 swap.
function ed_job(j, st) local(t, i) (
  j == 1 ? pshape(st, 0, 0, 0) : j == 2 ? pshape(st, 1, 0, 0) : j == 3 ? pshape(st, 2, 0, 0) : j == 4 ? pshape(st, 3, 0, 0) :
  j == 5 ? pshape(st, 4, 0.8, 0.15) : j == 6 ? pshape(st, 5, 1.6, 0) : j == 7 ? pshape(st, 6, 6, 3) : j == 8 ? pshape(st, 7, 0, 0) :
  j == 9 ? pshape(st, 8, 1, 0) : j == 10 ? pshape(st, 9, 7, 0) : j == 11 ? pclear(st) : j == 12 ? randomize_partials(st) :
  j == 13 ? ( pcopy(st ~ 2, st); set_dirty(st ~ 2); ) :
  j == 14 ? ( i = 0; loop(64, t = PA[st * 64 + i]; PA[st * 64 + i] = PA[(st ~ 2) * 64 + i]; PA[(st ~ 2) * 64 + i] = t; i += 1; ); set_dirty(st ~ 2); );
  set_dirty(st);
);
prs_ok = 0; prs_dirty = 0; ed_dirty = 0;

@slider
prs_chk();
update();

@block
prs_chk();
update();                                  // knobs moved on the panel do not run @slider, so read them here
ed_dirty > 0 ? ( ed_job(ed_dirty, ed_set_d); ed_dirty = 0; );
// partial tables: rebuilt when a screen was drawn on, a patch loaded or the partial count changed
// (at most every 25 ms, two sets at a time: the STARTs first)
s0_dirty || s1_dirty || s2_dirty || s3_dirty ? (
  time_precise() - last_build > 0.025 ? (
    bn = 0;
    s0_dirty ? ( s0_dirty = 0; build_tables(0); bn += 1; );
    s1_dirty ? ( s1_dirty = 0; build_tables(1); bn += 1; );
    s2_dirty && bn < 2 ? ( s2_dirty = 0; build_tables(2); bn += 1; );
    s3_dirty && bn < 2 ? ( s3_dirty = 0; build_tables(3); bn += 1; );
    last_build = time_precise();
  );
);
mo_drift = ins_tone * 0.12;                 // DRIFT: the morph position breathes a little (MODE on only)
// the morph runs while any MODE is on, an LFO goes to MORPH, or a voice is still on its way back to START
mo_run = mA_mode || mB_mode || l1_dest == 8 || l2_dest == 8;
!mo_run ? ( i = 0; loop(NV, vptr(i)[F_MSA] > 0.0001 || vptr(i)[F_MSB] > 0.0001 ? mo_run = 1; i += 1; ); );
vptr(nv_new)[F_EA] || vptr(nv_new)[F_EB] ? ( mA_disp = vptr(nv_new)[F_MSA]; mB_disp = vptr(nv_new)[F_MSB]; ) : ( mA_disp = 0; mB_disp = 0; );
l1_amt = min(slider49 / 100 + modw * (1 - slider49 / 100), 1);   // the mod wheel adds to LFO 1

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 == 0x90 && m3 > 0 ? ins_midi_hit();
    ) :
    st == 0xE0 ? bend_st = (((m3 * 128 + m2) - 8192) / 8192) * bend_rng :
    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);
    );
  );
  midisend(mofs, m1, m2, m3);
);
vmode != last_vmode ? ( panic_dirty = 1; last_vmode = vmode; );
panic_dirty ? ( all_off(); panic_dirty = 0; );
eqi = 0; spos = 0;
ins_drift_block(drift, samplesblock);
// delay times (samples)
dl_tgtL = min(dl_time(slider58, slider59) * srate, DSZ - 4);
dl_tgtR = min(dl_time(slider60, slider61) * srate, DSZ - 4);

@serialize
file_var(0, ui_sz);
file_mem(0, PA, 128);
// the END screens (a project from before them: END = a copy of START, the morph off anyway)
file_avail(0) != 0 ? file_mem(0, PA + 128, 128) : ( pcopy(2, 0); pcopy(3, 1); );
file_avail(0) != 0 ? file_var(0, prs_last) : prs_last = prs_sel | 0; prs_ok = 1;   // PRESET parameter: no reload on recall
file_avail(0) != 0 ? file_var(0, ptr_t);
file_avail(0) != 0 ? file_var(0, pr_cur);
ptr_s = ptr_t;
s0_dirty = 1; s1_dirty = 1; s2_dirty = 1; s3_dirty = 1;

@sample
while (eqi < qcount && (EQO[eqi] <= spos || spos >= samplesblock - 1)) (   // (an event stamped at / past the block end: handled at its last sample)
  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;
mo_tick = (spos & 15) == 1;                 // the morph's control rate

// global LFOs
l1_glob && l1_dest ? l1_gy = lfo_run(GL1, l1_inc, l1_wave, l1_sk, l1_slew);
l2_glob && l2_dest ? l2_gy = lfo_run(GL2, l2_inc, l2_wave, l2_sk, l2_slew);

oL = 0; oR = 0;
sv_i = 0; loop(NV, v_run(sv_i); sv_i += 1; );        // (not gi: @gfx loops with it)
duck_t > duck_g ? duck_g = min(duck_g + duck_inc, 1) : duck_t < duck_g ? duck_g = max(duck_g - duck_inc, 0);   // (no ==: EEL's is approximate)
oL *= 0.3 * duck_g; oR *= 0.3 * duck_g;

// ---------- dual delay ----------
dl_on ? (
  dtL += (dl_tgtL - dtL) * 0.0004; dtR += (dl_tgtR - dtR) * 0.0004;   // glide to new times (no zipper)
  // read both lines (linear interpolation)
  rp = dpos - dtL; rp < 0 ? rp += DSZ; ri = floor(rp); rf = rp - ri;
  dyL = DLL[ri] + rf * (DLL[(ri + 1) % DSZ] - DLL[ri]);
  rp = dpos - dtR; rp < 0 ? rp += DSZ; ri = floor(rp); rf = rp - ri;
  dyR = DLR[ri] + rf * (DLR[(ri + 1) % DSZ] - DLR[ri]);
  // the feedback loop: own + cross feedback, through the LP and HP
  fL = dyL * dl_fb + dyR * dl_x; fR = dyR * dl_fb + dyL * dl_x;
  dlpL += (fL - dlpL) * dl_lpc; dlpR += (fR - dlpR) * dl_lpc;
  dhpL += (dlpL - dhpL) * dl_hpc; dhpR += (dlpR - dhpR) * dl_hpc;
  fL = dlpL - dhpL; fR = dlpR - dhpR;
  inL = dl_pp ? (oL + oR) * 0.5 : oL; inR = dl_pp ? 0 : oR;
  DLL[dpos] = inL + fL; DLR[dpos] = inR + fR;
  dpos += 1; dpos >= DSZ ? dpos = 0;
  oL += dyL * dl_mix; oR += dyR * dl_mix;
) : ( dyL = dyR = 0; );

vol_s += (vol_t - vol_s) * sm;
ptr_s += (ptr_t - ptr_s) * sm;
oL = ostL.ost_band(oL * ptr_s) * vol_s;
oR = ostR.ost_band(oR * ptr_s) * vol_s;
ins_stereo(oL, oR); ost_out(ins_sl, ins_sr);
spl0 = ost_l;
spl1 = ost_r;
pkl_disp = max(pkl_disp, abs(spl0)); pkr_disp = max(pkr_disp, abs(spl1));

@gfx 1180 908
prs_cur_d > 0 ? ( pr_cur = prs_cur_d; prs_cur_d = 0; );     // PRESET parameter: show a preset loaded by the host
ui_thm = 5; ui_hw = 1;
fh = 904;                                          // the panel, with the IN / OUT strip along the bottom
ui_begin(1180, fh + 4);
// phosphor green replaces the graphite theme's accents
C_ACC = PHO; C_S0 = 0x39C978; C_S2 = 0x9CEFBC; C_KC = 0xE6F2E8;
#gl_pm = "1981 Micro Chime|Lo-Fi Bit Bass|Spectral Ambient Pad|E.Piano Tine|Digital Sci-Fi Drone|8-Bit Arcade Percussion|Dual-Timbral Poly|8-Bit Arcade Snare|Glass Organ|Vintage Strings|Hollow Flute|Bell Tower|Choir Formant|Square Sync Lead|Glass Sweep|Vowel Lead|Morph Pluck|Organ to Choir|Bit Shimmer|Tidal Pad|Velocity Brass|Init";
// the panel's own copies (never one function on both threads)
function pcount_g(o) ( (o ? slider19 : slider9) == 0 ? 16 : (o ? slider19 : slider9) == 1 ? 32 : 64; );
function crv_g(c) ( 2 ^ (c * 3) );
function g_dirty(st) ( st == 0 ? s0_dirty = 1 : st == 1 ? s1_dirty = 1 : st == 2 ? s2_dirty = 1 : s3_dirty = 1; );
function ed_ask(j, st) ( ed_set_d = st; ed_dirty = j; );
// START is drawn in phosphor green, END in amber (a two-colour screen)
function side_col(e) ( e ? AMBL : PHO; );

// ---------- chassis ----------
ui_col(0x0A0B0B); gfx_rect(0, 0, gfx_w, gfx_h);
pk_chassis(8 * sc, 4 * sc, 1164 * sc, fh * sc, PNL);
ui_col(PHO); gfx_rect(8 * sc, 58 * sc, 1164 * sc, 2 * sc);
ui_col(PHO); gfx_circle(36 * sc, 31 * sc, 14 * sc, 1, 1);
gfx_setfont(4); ui_col(PNL); ui_text(36 * sc, 26 * sc, "GFX");
gfx_setfont(6, "Trebuchet MS", 22 * sc, 'b'); ui_col(INKL); gfx_x = 60 * sc; gfx_y = 18 * sc; gfx_drawstr("GLOW"); gfx_measurestr("GLOW", gl_tw, gl_th);
gfx_setfont(4); ui_cola(INKL, 0.6); gfx_x = 60 * sc + gl_tw + 10 * sc; gfx_y = 27 * sc; gfx_drawstr("SPECTRAL MORPH SYNTHESIZER");

// preset bar: [PRESETS][<][>][display]
pk_lcdc = PHO;
pk_k = pk_preset(370 * sc, 13 * sc, 470 * sc, 34 * sc, pr_cur, #gl_pm);
pk_k > 0 ? ( prs_dirty = pk_k; pr_cur = pk_k; );
pk_led(914 * sc, 31 * sc, 3 * sc, ins_midi_lit(), PHO);
gfx_setfont(4); ui_col(INKL); gfx_x = 922 * sc; gfx_y = 26 * sc; gfx_drawstr("MIDI");
ui_size_picker(968 * sc, 20 * sc);
pk_ledbtn(1060 * sc, 12 * sc, 32 * sc, 38 * sc, 0, REDL) ? panic_dirty = 1;
gfx_setfont(4); ui_col(INKL); gfx_x = 1098 * sc; gfx_y = 26 * sc; gfx_drawstr("PANIC");

// ---------- the two partial screens ----------
// each oscillator has a START and an END screen: the START / END keys pick the one drawn here (es); the other
// shows behind it as faint outlines. Draw: left-drag sets partials (filling the gaps between mouse positions),
// right-drag erases.
function screen(o, x, y, w, h, es) local(n, bw, k, a, bx, by, hx, mx, my, b0, b1, a0, a1, j, t, base, px, py, qx, qy, vin, st, gs, col, gcol, hh) (
  n = pcount_g(o); vin = o ? slider18 : slider8;
  st = o + 2 * es; gs = o + 2 * (1 - es); col = side_col(es); gcol = side_col(1 - es);
  // phosphor CRT: black glass, faint grid and scanlines
  ui_col(0x040806); ui_rrect(x * sc, y * sc, w * sc, h * sc, 6 * sc);
  ui_col(0x061209); gfx_rect((x + 4) * sc, (y + 4) * sc, (w - 8) * sc, (h - 8) * sc);
  ui_cola(PHO, 0.07);
  j = 1; loop(3, gfx_line((x + 4) * sc, (y + 4 + (h - 8) * j / 4) * sc, (x + w - 4) * sc, (y + 4 + (h - 8) * j / 4) * sc); j += 1; );
  ui_cola(0x000000, 0.25); j = 0; loop(floor((h - 8) / 3), gfx_line((x + 4) * sc, (y + 5 + j * 3) * sc, (x + w - 4) * sc, (y + 5 + j * 3) * sc); j += 1; );
  bw = (w - 16) / n; hh = h - 44;
  k = 0;
  loop(n,
    bx = (x + 8 + k * bw) * sc; by = (y + h - 10) * sc;
    hx = max((bw - (n > 32 ? 1 : 2)) * sc, 1);
    // the other screen, faintly behind
    a = PA[gs * 64 + k];
    vin ? a = floor(a * 15 + 0.5) / 15;
    a > 0.002 ? ( ui_cola(gcol, 0.6); gfx_rect(bx, by - a * hh * sc, hx, max(1, 1.5 * sc)); gfx_rect(bx, by - a * hh * sc, max(1, sc), a * hh * sc); );
    a = PA[st * 64 + k];
    vin ? a = floor(a * 15 + 0.5) / 15;               // vintage: drawn in 16 steps, like the old screen
    a > 0.002 ? (
      ui_cola(col, 0.16); gfx_rect(bx - 1, by - a * hh * sc - 2, hx + 2, a * hh * sc + 2);
      ui_cola(col, 0.8); gfx_rect(bx, by - a * hh * sc, hx, a * hh * sc);
      ui_col(ui_mix(col, 0xFFFFFF, 0.6)); gfx_rect(bx, by - a * hh * sc, hx, max(1, 1.5 * sc));
    ) : ( ui_cola(col, 0.25); gfx_rect(bx, by - 1, hx, 1); );
    (k + 1) % 8 == 0 || k == 0 ? (
      gfx_setfont(8, "Courier New", 10 * sc, 'b'); ui_cola(PHO, 0.5);
      sprintf(#pn, "%d", k + 1); gfx_x = bx; gfx_y = by + 1 * sc; gfx_drawstr(#pn);
    );
    k += 1;
  );
  // the single cycle it makes, top right
  ui_col(0x030604); gfx_rect((x + w - 132) * sc, (y + 8) * sc, 124 * sc, 46 * sc);
  ui_cola(col, 0.2); gfx_line((x + w - 130) * sc, (y + 31) * sc, (x + w - 10) * sc, (y + 31) * sc);
  ui_cola(col, 0.9);
  j = 0; base = TA + (st * 7) * TLV;
  loop(121,
    t = j / 120;
    a = vin ? TV[st * 256 + floor(t * 255.99)] : base[floor(t * 2047.99)];
    qx = (x + w - 130 + j) * sc; qy = (y + 31 - a * 20) * sc;
    j > 0 ? gfx_line(px, py, qx, qy);
    px = qx; py = qy; j += 1;
  );
  // which screen this is, large, then the oscillator mode
  gfx_setfont(8, "Courier New", 15 * sc, 'b'); ui_col(col);
  gfx_x = (x + 12) * sc; gfx_y = (y + 9) * sc; gfx_drawstr(es ? "END" : "START");
  gfx_setfont(8, "Courier New", 11 * sc, 'b'); ui_cola(PHO, 0.75);
  gfx_x = (x + 80) * sc; gfx_y = (y + 12) * sc; gfx_drawstr(vin ? "VINTAGE  8-BIT 256 BYTE" : "MODERN  BAND-LIMITED");
  // editing
  mx = mouse_x / sc; my = mouse_y / sc;
  (pressed || ((mouse_cap & 2) && !scr_rprev)) && drag_id == 0 && mx >= x + 4 && mx < x + w - 4 && my >= y + 4 && my < y + h - 4 &&
    !(mx >= x + w - 132 && my < y + 54) ? (
    scr_erase = (mouse_cap & 2) ? 1 : 0; scr_last = -1;
    scr_erase ? scr_eo = o + 1 : drag_id = 900 + o;
  );
  scr_eo == o + 1 && (mouse_cap & 2) == 0 ? scr_eo = 0;
  drag_id == 900 + o || scr_eo == o + 1 ? (
    b1 = min(max(floor((mx - x - 8) / bw), 0), n - 1);
    a1 = scr_erase ? 0 : min(max((y + h - 10 - my) / hh, 0), 1);
    b0 = scr_last < 0 ? b1 : scr_last; a0 = scr_last < 0 ? a1 : scr_lasta;
    j = min(b0, b1);
    loop(abs(b1 - b0) + 1,
      t = b1 == b0 ? 1 : (j - b0) / (b1 - b0);
      PA[st * 64 + j] = a0 + (a1 - a0) * t;
      j += 1;
    );
    scr_last = b1; scr_lasta = a1;
    g_dirty(st);
  );
);
// one oscillator's header: MODERN / VINTAGE, 16 / 32 / 64, RANDOM, the partial shapes menu (both act on the
// screen being drawn)
function osc_head(o, x, y, es) local(mb, nb, j) (
  mb = o ? 18 : 8; nb = o ? 19 : 9;
  ui_noled = 1;
  ui_keycap(x * sc, y * sc, 70 * sc, 22 * sc, "MODERN", slider(mb) == 0, 1) ? ( slider(mb) = 0; slider_automate(2 ^ (mb - 1)); );
  ui_keycap((x + 74) * sc, y * sc, 70 * sc, 22 * sc, "VINTAGE", slider(mb) == 1, 1) ? ( slider(mb) = 1; slider_automate(2 ^ (mb - 1)); );
  j = 0;
  loop(3,
    ui_keycap((x + 166 + j * 40) * sc, y * sc, 36 * sc, 22 * sc, j == 0 ? "16" : j == 1 ? "32" : "64", slider(nb) == j, 1) ? (
      slider(nb) = j; slider_automate(2 ^ (nb - 1)); );
    j += 1;
  );
  ui_keycap((x + 300) * sc, y * sc, 76 * sc, 22 * sc, "RANDOM", 0, 0) ? ed_ask(12, o + 2 * es);
  ui_keycap((x + 380) * sc, y * sc, 76 * sc, 22 * sc, "SHAPES", 0, 0) ? (
    gfx_x = (x + 380) * sc; gfx_y = (y + 22) * sc;
    j = gfx_showmenu("Sine|Saw|Square|Triangle|Odd emphasis|Soft decay|Formant|Organ|Even emphasis|Bright tine|Clear");
    j > 0 ? ed_ask(j, o + 2 * es);
  );
  ui_noled = 0;
);
mgroup(16, 72, 570, 236, 0, "OSCILLATOR A", SH1);
osc_head(0, 28, 84, ed_a);
screen(0, 28, 112, 546, 186, ed_a);
mgroup(594, 72, 570, 236, 0, "OSCILLATOR B", SH1);
osc_head(1, 606, 84, ed_b);
screen(1, 606, 112, 546, 186, ed_b);
scr_rprev = mouse_cap & 2;

// ---------- spectral morph A / B ----------
// START / END: the screen you draw on; COPY / SWAP between them; MODE (a menu), with the position of the
// newest note under it; TIME, CURVE, DEPTH, VELOCITY (greyed while MODE is OFF)
function morph_row(o, x, y, es, pos) local(bm, bt, bv, md, j, bx, tw, th) (
  bm = o ? 79 : 75; bt = bm + 1; bv = o ? 83 : 72;
  md = slider(bm);
  ui_noled = 1;
  ui_keycap((x + 14) * sc, (y + 20) * sc, 62 * sc, 22 * sc, "START", es == 0, 1) ? ( o ? ed_b = 0 : ed_a = 0; );
  ui_keycap((x + 14) * sc, (y + 50) * sc, 62 * sc, 22 * sc, "END", es == 1, 1) ? ( o ? ed_b = 1 : ed_a = 1; );
  ui_keycap((x + 84) * sc, (y + 20) * sc, 62 * sc, 22 * sc, "COPY", 0, 0) ? ed_ask(13, o + 2 * es);
  ui_keycap((x + 84) * sc, (y + 50) * sc, 62 * sc, 22 * sc, "SWAP", 0, 0) ? ed_ask(14, o + 2 * es);
  ui_noled = 0;
  // MODE: a display box, click for the menu
  bx = x + 160;
  gfx_setfont(4); ui_cola(INKL, 0.75); ui_text((bx + 48) * sc, (y + 14) * sc, "MODE");
  ui_col(0x040806); ui_rrect(bx * sc, (y + 24) * sc, 96 * sc, 26 * sc, 3 * sc);
  ui_cola(PHO, 0.25); gfx_rect(bx * sc, (y + 24) * sc, 96 * sc, 1);
  gfx_setfont(8, "Courier New", 14 * sc, 'b'); ui_col(md ? PHO : ui_mix(PHO, 0x000000, 0.45));
  #mm = md == 0 ? "OFF" : md == 1 ? "ONCE" : md == 2 ? "LOOP" : "ENVELOPE";
  gfx_measurestr(#mm, tw, th); gfx_x = (bx + 48) * sc - tw * 0.5; gfx_y = (y + 37) * sc - th * 0.5; gfx_drawstr(#mm);
  pressed && drag_id == 0 && mouse_x >= bx * sc && mouse_x < (bx + 96) * sc && mouse_y >= (y + 24) * sc && mouse_y < (y + 50) * sc ? (
    drag_id = -1;
    gfx_x = bx * sc; gfx_y = (y + 50) * sc;
    #mn = ""; j = 0;
    loop(4, md == j ? strcat(#mn, "!"); strcat(#mn, j == 0 ? "Off" : j == 1 ? "Once" : j == 2 ? "Loop" : "Envelope"); j < 3 ? strcat(#mn, "|"); j += 1; );
    j = gfx_showmenu(#mn);
    j > 0 ? ( slider(bm) = j - 1; o ? slider_automate(slider79) : slider_automate(slider75); );
  );
  // where the newest note is between START and END
  gfx_setfont(4); ui_col(PHO); ui_text((bx - 8) * sc, (y + 62) * sc, "S");
  ui_col(AMBL); ui_text((bx + 104) * sc, (y + 62) * sc, "E");
  ui_col(0x040806); gfx_rect(bx * sc, (y + 58) * sc, 96 * sc, 9 * sc);
  pos > 0.004 ? ( ui_col(ui_mix(PHO, AMBL, pos)); gfx_rect((bx + 1) * sc, (y + 59) * sc, 94 * pos * sc, 7 * sc); );
  // the knobs
  ui_knob((x + 298) * sc, (y + 50) * sc, bt, 10, 20000, 2000, 1, "TIME", slider(bt) < 1000 ? "%.0f ms" : "%.2f s");
  ui_section(1); C_INK = INKL;
  ui_knob((x + 362) * sc, (y + 50) * sc, bt + 1, -100, 100, 0, 0, "CURVE", "%+.0f");
  ui_section(2); C_INK = INKL;
  ui_knob((x + 426) * sc, (y + 50) * sc, bt + 2, 0, 100, 100, 0, "DEPTH", "%.0f%%");
  ui_knob((x + 494) * sc, (y + 50) * sc, bv, 0, 100, 0, 0, "VELOCITY", "%.0f%%");
  !md ? ( ui_cola(o ? SH2 : SH2, 0.55); gfx_rect((x + 262) * sc, (y + 12) * sc, 296 * sc, 80 * sc); );
);
mgroup(16, 320, 570, 96, 0, "MORPH A", SH2);
morph_row(0, 16, 320, ed_a, mA_disp);
mgroup(594, 320, 570, 96, 0, "MORPH B", SH2);
morph_row(1, 594, 320, ed_b, mB_disp);

// ---------- paths A / B: pitch, level, pan, noise ----------
function path_row(o, x, y) local(b) (
  b = o ? 20 : 10;
  ui_knob((x + 40) * sc, (y + 50) * sc, b, -36, 36, 0, 0, "TRANSPOSE", "%+.0f st");
  ui_knob((x + 108) * sc, (y + 50) * sc, b + 1, -100, 100, 0, 0, "FINE", "%+.1f c");
  ui_knob((x + 176) * sc, (y + 50) * sc, b + 7, -48, 48, 0, 0, "PITCH ENV", "%+.1f st");
  ui_knob((x + 244) * sc, (y + 50) * sc, b + 2, 0, 100, o ? 0 : 80, 0, "LEVEL", "%.0f%%");
  ui_knob((x + 312) * sc, (y + 50) * sc, b + 3, -100, 100, 0, 0, "PAN", slider(b + 3) == 0 ? "C" : slider(b + 3) < 0 ? "L %.0f" : "R %.0f");
  ui_knob((x + 380) * sc, (y + 50) * sc, b + 4, 0, 100, 0, 0, "NOISE", "%.0f%%");
  ui_knob((x + 448) * sc, (y + 50) * sc, b + 6, 0, 100, 70, 0, "TONE", "%.0f%%");
  ui_noled = 1;
  ui_keycap((x + 492) * sc, (y + 20) * sc, 62 * sc, 22 * sc, "WHITE", slider(b + 5) == 0, 1) ? ( slider(b + 5) = 0; slider_automate(2 ^ (b + 4)); );
  ui_keycap((x + 492) * sc, (y + 48) * sc, 62 * sc, 22 * sc, "LFSR", slider(b + 5) == 1, 1) ? ( slider(b + 5) = 1; slider_automate(2 ^ (b + 4)); );
  ui_noled = 0;
);
mgroup(16, 428, 570, 96, 0, "PATH A", SH2);
path_row(0, 16, 428);
mgroup(594, 428, 570, 96, 0, "PATH B", SH2);
path_row(1, 594, 428);

// ---------- envelopes, with a sketch of the shape ----------
function env_row(o, x, y) local(b, j, t, v, px, py, qx, qy, at, dt, rt, tot, s, cpa, cpd, cpr, gx, gw, gy, gh) (
  b = o ? 37 : 30;
  ui_knob((x + 40) * sc, (y + 50) * sc, b, 0.5, 10000, 2, 1, "ATTACK", slider(b) < 10 ? "%.1f ms" : "%.0f ms");
  ui_knob((x + 100) * sc, (y + 50) * sc, b + 1, 1, 20000, 400, 1, "DECAY", "%.0f ms");
  ui_knob((x + 160) * sc, (y + 50) * sc, b + 2, 0, 100, 70, 0, "SUSTAIN", "%.0f%%");
  ui_knob((x + 220) * sc, (y + 50) * sc, b + 3, 1, 20000, 300, 1, "RELEASE", "%.0f ms");
  ui_section(1); C_INK = INKL;
  ui_knob((x + 284) * sc, (y + 50) * sc, b + 4, -100, 100, 0, 0, "A CURVE", "%+.0f");
  ui_knob((x + 340) * sc, (y + 50) * sc, b + 5, -100, 100, 50, 0, "D CURVE", "%+.0f");
  ui_knob((x + 396) * sc, (y + 50) * sc, b + 6, -100, 100, 50, 0, "R CURVE", "%+.0f");
  ui_section(2); C_INK = INKL;
  // the shape: attack, decay, a held sustain, release (time on a log-ish scale so short and long both read)
  gx = x + 432; gw = 126; gy = y + 14; gh = 70;
  ui_col(0x040806); ui_rrect(gx * sc, gy * sc, gw * sc, gh * sc, 4 * sc);
  at = sqrt(slider(b)); dt = sqrt(slider(b + 1)); rt = sqrt(slider(b + 3)); tot = at + dt + rt + 0.35 * (at + dt + rt);
  s = slider(b + 2) / 100; cpa = crv_g(slider(b + 4) / 100); cpd = crv_g(slider(b + 5) / 100); cpr = crv_g(slider(b + 6) / 100);
  ui_cola(PHO, 0.9);
  j = 0;
  loop(101,
    t = j / 100 * tot;
    v = t < at ? 1 - (1 - t / at) ^ cpa :
        t < at + dt ? s + (1 - s) * (1 - (t - at) / dt) ^ cpd :
        t < at + dt + 0.35 * (at + dt + rt) ? s :
        s * (1 - min((t - at - dt - 0.35 * (at + dt + rt)) / rt, 1)) ^ cpr;
    qx = (gx + 4 + j * (gw - 8) / 100) * sc; qy = (gy + gh - 6 - v * (gh - 14)) * sc;
    j > 0 ? gfx_line(px, py, qx, qy);
    px = qx; py = qy; j += 1;
  );
);
mgroup(16, 536, 570, 96, 0, "ENVELOPE A", SH2);
env_row(0, 16, 536);
mgroup(594, 536, 570, 96, 0, "ENVELOPE B", SH2);
env_row(1, 594, 536);

// ---------- VOICE | X-MOD | LFO 1 | LFO 2 ----------
mgroup(16, 644, 290, 96, 0, "VOICE", SH3);
gi = 0;
loop(3,
  pk_ledbtn(28 * sc, (654 + gi * 27) * sc, 24 * sc, 22 * sc, slider1 == gi, PHO) ? ( slider1 = gi; slider_automate(1); );
  gfx_setfont(4); ui_col(INKL); gfx_x = 58 * sc; gfx_y = (660 + gi * 27) * sc; gfx_drawstr(gi == 0 ? "POLY" : gi == 1 ? "MONO" : "RETRIG");
  gi += 1;
);
ui_knob(150 * sc, 694 * sc, 2, 0, 2000, 0, 0, "GLIDE", "%.0f ms");
knob_step = 1; ui_knob(208 * sc, 694 * sc, 3, 0, 24, 2, 0, "BEND", "%.0f st");
ui_knob(266 * sc, 694 * sc, 4, 0, 100, 60, 0, "VELOCITY", "%.0f%%");

mgroup(314, 644, 150, 96, 0, "X-MOD", SH3);
ui_knob(352 * sc, 694 * sc, 28, 0, 100, 0, 0, "A > B", "%.1f%%");
ui_knob(424 * sc, 694 * sc, 29, 0, 100, 0, 0, "B > A", "%.1f%%");

function lfo_row(l, x, y) local(b) (
  b = 44 + l * 6;
  knob_step = 1; ui_knob((x + 34) * sc, (y + 50) * sc, b, 0, 3, l, 0, "WAVE", wave_name(slider(b)));
  ui_knob((x + 92) * sc, (y + 50) * sc, b + 1, 0.02, 30, 1, 1, "RATE", "%.2f Hz");
  ui_knob((x + 150) * sc, (y + 50) * sc, b + 2, 0, 100, 50, 0, "SKEW", "%.0f%%");
  knob_step = 1; ui_knob((x + 208) * sc, (y + 50) * sc, b + 4, 0, 8, 0, 0, "DEST", dest_name(slider(b + 4)));
  ui_knob((x + 266) * sc, (y + 50) * sc, b + 5, 0, 100, 0, 0, "AMOUNT", "%.1f%%");
  ui_noled = 1;
  ui_keycap((x + 300) * sc, (y + 20) * sc, 54 * sc, 22 * sc, "POLY", slider(b + 3) == 0, 1) ? ( slider(b + 3) = 0; slider_automate(2 ^ (b + 2)); );
  ui_keycap((x + 300) * sc, (y + 48) * sc, 54 * sc, 22 * sc, "GLOBAL", slider(b + 3) == 1, 1) ? ( slider(b + 3) = 1; slider_automate(2 ^ (b + 2)); );
  ui_noled = 0;
);
mgroup(472, 644, 342, 96, 0, "LFO 1", SH3);
lfo_row(0, 466, 644);
mgroup(822, 644, 342, 96, 0, "LFO 2", SH3);
lfo_row(1, 816, 644);

// ---------- DELAY | MASTER ----------
mgroup(16, 752, 826, 96, 0, "DUAL DELAY", SH3);
pk_ledbtn(28 * sc, 764 * sc, 36 * sc, 32 * sc, slider56, PHO) ? ( slider56 = 1 - slider56; slider_automate(2 ^ 55); );
pk_ledbtn(72 * sc, 764 * sc, 36 * sc, 32 * sc, slider57, AMBL) ? ( slider57 = 1 - slider57; slider_automate(2 ^ 56); );
gfx_setfont(4); ui_col(INKL); ui_text(46 * sc, 806 * sc, "ON"); ui_text(90 * sc, 806 * sc, "PING");
knob_step = 1; ui_knob(152 * sc, 802 * sc, 58, 0, 13, 9, 0, "SYNC L", slider58 == 0 ? "OFF (ms)" : div_name(slider58));
ui_knob(214 * sc, 802 * sc, 59, 1, 2000, 375, 1, slider58 ? "(TIME L)" : "TIME L", "%.0f ms");
knob_step = 1; ui_knob(282 * sc, 802 * sc, 60, 0, 13, 8, 0, "SYNC R", slider60 == 0 ? "OFF (ms)" : div_name(slider60));
ui_knob(344 * sc, 802 * sc, 61, 1, 2000, 500, 1, slider60 ? "(TIME R)" : "TIME R", "%.0f ms");
ui_knob(420 * sc, 802 * sc, 62, 0, 95, 35, 0, "FEEDBACK", "%.0f%%");
ui_knob(488 * sc, 802 * sc, 63, 0, 95, 0, 0, "CROSS", "%.0f%%");
ui_knob(560 * sc, 802 * sc, 64, 500, 20000, 8000, 1, "LP", "%.0f Hz");
ui_kfilter = 1; ui_knob(624 * sc, 802 * sc, 65, 20, 2000, 80, 1, "HP", "%.0f Hz");
ui_knob(700 * sc, 802 * sc, 66, 0, 100, 25, 0, "MIX", "%.0f%%");
ui_cola(INKL, 0.5); gfx_setfont(4);
gfx_x = 740 * sc; gfx_y = 772 * sc; gfx_drawstr("L <-> R");
gfx_x = 740 * sc; gfx_y = 788 * sc; gfx_drawstr("cross-feedback");
gfx_x = 740 * sc; gfx_y = 804 * sc; gfx_drawstr("LP / HP inside");
gfx_x = 740 * sc; gfx_y = 820 * sc; gfx_drawstr("the loop");

mgroup(850, 752, 314, 96, 2, "MASTER", SH3);
ui_section(1); C_INK = INKL;
ui_knob(884 * sc, 802 * sc, 7, 0, 100, 20, 0, "DRIFT", "%.0f%%");
ui_section(2); C_INK = INKL;
ui_knob(942 * sc, 802 * sc, 5, -24, 6, 0, 0, "VOLUME", "%.1f dB");
ui_section(3); C_INK = INKL; knob_step = 1;
ui_knob(1000 * sc, 802 * sc, 6, 0, 16, 0, 0, "CHANNEL", slider6 == 0 ? "OMNI" : "CH %.0f");
mL.ui_ledm(1118 * sc, 762 * sc, 14 * sc, 70 * sc, pkl_disp);
mR.ui_ledm(1138 * sc, 762 * sc, 14 * sc, 70 * sc, pkr_disp);
pkl_disp *= 0.85; pkr_disp *= 0.85;

// ---------- IN / OUT strip: SAT, OUT, STEREO ----------
ins_strip(16, 858, 1148, 67, 73, INKL, SH3);
pk_end();
ui_end();
