desc:BOB34 [modular effects] (GFX Lab)
//tags: modular ladder lowpass envelope ring modulator phaser resonant filter array sequencer saturator noise cv patch bay envelope follower
//author: JGWizrad / Garden FX (GFX)
//
// Requires gfx-lab-input, gfx-lab-output, gfx-lab-drift, gfx-lab-ui, gfx-lab-panel, gfx-lab-dyn, gfx-lab-clip and gfx-lab-os
// (.jsfx-inc) in the same folder.
//
// BOB34 (Lab). Five modular effects in one rack, in the spirit of a classic American range of analogue effects pedals
// (the sound is designed from scratch), now with a PATCH BAY: three patch cables, each taking any module's envelope (or
// an LFO, the noise, or the input's own envelope) to any other module's controls - something the pedals, one CV jack
// each, never did. The signal runs through the five modules in the CHAIN order (the arrow keys on each module move it
// earlier or later); each module has an IN key, its own DRIVE (an input stage that warms and crunches, level-
// compensated) and MIX.
//
//   LOWPASS    a 4-pole (or 2-pole) transistor-ladder low-pass, CUTOFF 20 Hz .. 12 kHz, RESONANCE 0 - 10 (it sings and
//              self-oscillates near the top), and an envelope follower: ENV AMOUNT -10 .. +10 (up to 5 octaves of
//              sweep, either way), FAST / SLOW response.
//   RING       a ring modulator: a sine CARRIER 0.6 Hz .. 4 kHz, swept by an LFO (RATE 0.1 .. 25 Hz, AMOUNT up to 3
//              octaves, sine or SQUARE).
//   PHASER     6 or 12 stages, SWEEP 100 Hz .. 8 kHz, RESONANCE (feedback), its LFO (RATE 0.05 .. 20 Hz, AMOUNT up to
//              2.5 octaves). MIX 50 % gives the deepest notches.
//   MURF       eight resonant band-pass filters (BASS or MID voicing) whose levels are set by the eight faders and
//              animated by a 16-step PATTERN (12 built in): each step fires some of the filters. RATE 0.5 .. 20 steps / s,
//              ENVELOPE short and percussive .. long and swelling.
//   SATURATOR  a drive with an envelope follower on it: DRIVE 0 - 10, ENV AMOUNT -10 .. +10 (above 0 the drive rises with
//              the level: harder, expanding; below 0 it falls: smoother, compressing), and a NOISE generator with a TONE
//              filter (low-pass, or high-pass with the HP key).
//   PATCH BAY  three cables, A - C (each its own cord colour: red, amber, blue). FROM: OFF, INPUT ENV (the dry input's
//              level), LP ENV, RING ENV, PH ENV, MURF ENV, SAT ENV (every module sends its own envelope, taken at its
//              input, so a module patched to itself never feeds back), RING LFO, PH LFO, NOISE. TO: LP CUTOFF (4 oct),
//              LP RES (5), LP MIX, RING FREQ (4 oct), RING MIX, PH SWEEP (3 oct), PH RES (5), PH MIX, MURF RATE (2 oct),
//              MURF MIX, SAT DRIVE (12 dB, level-compensated: more grit, not more level), SAT NOISE. A patched
//              resonance stops at 8 (short of self-oscillation) unless the knob is already higher. AMOUNT -100 .. +100 %
//              (below 0 the cable works upside down: an envelope that closes instead of opening), LAG 0 - 100 % (a slew
//              on the cable, 20 ms .. 2 s: at 0 an envelope's ripple and drum hits are just smoothed off; higher, it
//              swells and lets go slowly). Cables landing on the same control add up. A cable's jack lamp glows with
//              what it carries; each module's CV IN jacks light in the colour of every cable patched into it. Changing
//              a cable's FROM / TO fades it out and back in (10 ms): no click.
//   STEREO     the ring carrier's right side runs 90 deg ahead, the phaser's right LFO is inverted, MURF's filters split
//              left / right, and the noise is inverted on the right. Off: everything mono-linked.
//   OS         1x / 2x / 4x: the whole chain (every drive, the ladder, the ring modulator, the saturator) runs
//              oversampled. 1x = the Stable sound. Latency 0 / 31 / 37 samples (reported).
//   OUTPUT     -12 .. +12 dB. The house output stage: CLIP (ceiling -0.3 dBFS).
//   Order changes run the new chain alongside the old one for 40 ms, then crossfade into it over 25 ms; every key fades
//   (10 ms): no click.
//   DRIFT      (0 - 100 %, 50 % = standard, 0 = none): filter tuning (the ladder's and the phaser's frequencies wander a
//              few cents), LFO rates (the ring and phaser LFOs and MURF's clock wander a little) and the patch bay's
//              supply rail: its voltage breathes, so every cable's depth wanders together (about +/- 5 %).
//   PRESETS    17 (Init + 16; the last five show off the patch bay) plus Reset all to defaults. A preset sets the
//              modules, the cables, the chain, STEREO and OUTPUT (each preset level-matched on programme at 0 VU, its
//              peaks clear of the ceiling); DRIFT and OS are left alone (Reset all restores them).
//   Latency: 0 samples at 1x, 31 at 2x, 37 at 4x (reported).

slider1:0<0,119,1>-Chain order
slider2:0<0,1,1{Off,On}>-Stereo
slider3:0<-12,12,0.1>-Output (dB)
slider4:50<0,100,1>-Drift (%)
slider5:0<0,2,1{1x,2x,4x}>-Oversampling
slider6:1<0,1,1{Out,In}>-Lowpass in
slider7:2<0,10,0.1>-Lowpass drive
slider8:800<20,12000,1>-Lowpass cutoff (Hz)
slider9:4<0,10,0.1>-Lowpass resonance
slider10:1<0,1,1{2-pole,4-pole}>-Lowpass poles
slider11:5<-10,10,0.1>-Lowpass env amount
slider12:1<0,1,1{Slow,Fast}>-Lowpass env response
slider13:100<0,100,1>-Lowpass mix (%)
slider14:0<0,1,1{Out,In}>-Ring in
slider15:2<0,10,0.1>-Ring drive
slider16:200<0.6,4000,0.1>-Ring carrier (Hz)
slider17:2<0.1,25,0.01>-Ring LFO rate (Hz)
slider18:0<0,100,1>-Ring LFO amount (%)
slider19:0<0,1,1{Sine,Square}>-Ring LFO wave
slider20:100<0,100,1>-Ring mix (%)
slider21:0<0,1,1{Out,In}>-Phaser in
slider22:2<0,10,0.1>-Phaser drive
slider23:800<100,8000,1>-Phaser sweep (Hz)
slider24:4<0,10,0.1>-Phaser resonance
slider25:0<0,1,1{6-stage,12-stage}>-Phaser stages
slider26:0.5<0.05,20,0.01>-Phaser LFO rate (Hz)
slider27:50<0,100,1>-Phaser LFO amount (%)
slider28:50<0,100,1>-Phaser mix (%)
slider29:0<0,1,1{Out,In}>-MuRF in
slider30:2<0,10,0.1>-MuRF drive
slider31:4<0.5,20,0.01>-MuRF rate (steps/s)
slider32:4<0,10,0.1>-MuRF envelope
slider33:1<1,12,1>-MuRF pattern
slider34:1<0,1,1{Bass,Mid}>-MuRF voicing
slider35:8<0,10,0.1>-MuRF level 1
slider36:8<0,10,0.1>-MuRF level 2
slider37:8<0,10,0.1>-MuRF level 3
slider38:8<0,10,0.1>-MuRF level 4
slider39:8<0,10,0.1>-MuRF level 5
slider40:8<0,10,0.1>-MuRF level 6
slider41:8<0,10,0.1>-MuRF level 7
slider42:8<0,10,0.1>-MuRF level 8
slider43:100<0,100,1>-MuRF mix (%)
slider44:0<0,1,1{Out,In}>-Saturator in
slider45:3<0,10,0.1>-Saturator drive
slider46:0<-10,10,0.1>-Saturator env amount
slider47:0<0,10,0.1>-Saturator noise
slider48:3000<100,12000,1>-Saturator noise tone (Hz)
slider49:0<0,1,1{LP,HP}>-Saturator noise filter
slider50:100<0,100,1>-Saturator mix (%)
slider51:0<0,9,1{Off,Input env,LP env,Ring env,Phaser env,MuRF env,Sat env,Ring LFO,Phaser LFO,Noise}>-Cable A from
slider52:0<0,11,1{LP cutoff,LP resonance,LP mix,Ring carrier,Ring mix,Phaser sweep,Phaser resonance,Phaser mix,MuRF rate,MuRF mix,Sat drive,Sat noise}>-Cable A to
slider53:50<-100,100,1>-Cable A amount (%)
slider54:0<0,100,1>-Cable A lag (%)
slider55:0<0,9,1{Off,Input env,LP env,Ring env,Phaser env,MuRF env,Sat env,Ring LFO,Phaser LFO,Noise}>-Cable B from
slider56:3<0,11,1{LP cutoff,LP resonance,LP mix,Ring carrier,Ring mix,Phaser sweep,Phaser resonance,Phaser mix,MuRF rate,MuRF mix,Sat drive,Sat noise}>-Cable B to
slider57:50<-100,100,1>-Cable B amount (%)
slider58:0<0,100,1>-Cable B lag (%)
slider59:0<0,9,1{Off,Input env,LP env,Ring env,Phaser env,MuRF env,Sat env,Ring LFO,Phaser LFO,Noise}>-Cable C from
slider60:5<0,11,1{LP cutoff,LP resonance,LP mix,Ring carrier,Ring mix,Phaser sweep,Phaser resonance,Phaser mix,MuRF rate,MuRF mix,Sat drive,Sat noise}>-Cable C to
slider61:50<-100,100,1>-Cable C amount (%)
slider62:0<0,100,1>-Cable C lag (%)
slider63:prs_sel=0<0,17,1{No preset,Init,Funky Wah,Slow Sweep,Ring Bells,Ring Tremolo,Jet Phaser,Classic Phase,MuRF Pulse,MuRF Bass Steps,Saturated Wah,Noise Wash,Full Rack,Envelope Phaser,Ducking Ring,Cross Talk,Swell Steps,Breathing Noise}>-Preset

in_pin:left input
in_pin:right input
out_pin:left output
out_pin:right output

import gfx-lab-input.jsfx-inc
import gfx-lab-output.jsfx-inc
import gfx-lab-drift.jsfx-inc
import gfx-lab-ui.jsfx-inc
import gfx-lab-panel.jsfx-inc
import gfx-lab-dyn.jsfx-inc
import gfx-lab-clip.jsfx-inc
import gfx-lab-os.jsfx-inc

@init
#ui_man = "gfx-lab-bob34";                                // the ? pop-up links to this manual
ext_noinit = 1;
clp_aa = 1; clp_pf_on = 0; clp_kt_on = 0;             // gfx-clip: ADAA clippers only

function bb_tn(x) ( abs(x) < 0.000000000001 ? 0 : x; );
function bb_tc(ms) ( 1 - exp(-1 / (max(ms, 0.005) * 0.001 * srate)); );
function bb_to(ms) ( 1 - exp(-1 / (max(ms, 0.005) * 0.001 * bb_fs)); );   // at the running (oversampled) rate
function bb_sm(g) ( g * g * (3 - 2 * g); );
function bb_th(x) local(y) ( y = min(max(x, -3), 3); y * (27 + y * y) / (27 + 9 * y * y); );   // a fast tanh

// ---------- memory ----------
MFC = 100;  CVS = 200; MFE = 210; MFA = 220; MFL = 230; NZC = 150;   // MuRF coefficients / CV sources / MuRF envelopes
CAB = 300; DST = 400; CBV = 450;                                     // the patch bay: three cables (8 fields), the 12 destinations, the cables' values
MPAT = 2000; PERM = 3000;                                            // patterns (12 x 16 steps), the 120 chain orders
PHA = 4000; PHB = 4100; MSA = 4200; MSB = 4300;                      // phaser and MuRF states, chain set A / B

// ---------- the 120 chain orders (lexicographic) ----------
bb_n = 0; bb_i0 = 0;
loop(5, bb_i1 = 0; loop(5, bb_i2 = 0; loop(5, bb_i3 = 0; loop(5, bb_i4 = 0; loop(5,
  bb_i0 != bb_i1 && bb_i0 != bb_i2 && bb_i0 != bb_i3 && bb_i0 != bb_i4 && bb_i1 != bb_i2 && bb_i1 != bb_i3 && bb_i1 != bb_i4
  && bb_i2 != bb_i3 && bb_i2 != bb_i4 && bb_i3 != bb_i4 ? (
    PERM[bb_n * 5] = bb_i0; PERM[bb_n * 5 + 1] = bb_i1; PERM[bb_n * 5 + 2] = bb_i2; PERM[bb_n * 5 + 3] = bb_i3; PERM[bb_n * 5 + 4] = bb_i4;
    bb_n += 1;
  ); bb_i4 += 1; ); bb_i3 += 1; ); bb_i2 += 1; ); bb_i1 += 1; ); bb_i0 += 1; );
function bb_ord(a, b, c, d, e) local(i, k) (
  k = 0; i = 0;
  loop(120, PERM[i * 5] == a && PERM[i * 5 + 1] == b && PERM[i * 5 + 2] == c && PERM[i * 5 + 3] == d && PERM[i * 5 + 4] == e ? k = i; i += 1; );
  k;
);
function bb_ord_au(a, b, c, d, e) local(i, k) (
  k = 0; i = 0;
  loop(120, PERM[i * 5] == a && PERM[i * 5 + 1] == b && PERM[i * 5 + 2] == c && PERM[i * 5 + 3] == d && PERM[i * 5 + 4] == e ? k = i; i += 1; );
  k;
);

// ---------- MuRF patterns: 16 steps each, a bit per filter (1 = the lowest) ----------
function bb_pat(p, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) local(o) (
  o = MPAT + (p - 1) * 16;
  o[0] = s0; o[1] = s1; o[2] = s2; o[3] = s3; o[4] = s4; o[5] = s5; o[6] = s6; o[7] = s7;
  o[8] = s8; o[9] = s9; o[10] = s10; o[11] = s11; o[12] = s12; o[13] = s13; o[14] = s14; o[15] = s15;
);
bb_pat(1,  1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128);                  // rising
bb_pat(2,  128, 64, 32, 16, 8, 4, 2, 1, 128, 64, 32, 16, 8, 4, 2, 1);                  // falling
bb_pat(3,  1, 2, 4, 8, 16, 32, 64, 128, 64, 32, 16, 8, 4, 2, 1, 2);                    // up and down
bb_pat(4,  17, 34, 68, 136, 17, 34, 68, 136, 136, 68, 34, 17, 136, 68, 34, 17);       // pairs
bb_pat(5,  1, 0, 3, 0, 1, 4, 0, 2, 1, 0, 3, 0, 8, 0, 4, 2);                            // bass pulse
bb_pat(6,  9, 64, 18, 132, 36, 1, 72, 16, 130, 4, 33, 8, 66, 128, 20, 2);              // scattered
bb_pat(7,  21, 0, 42, 0, 21, 0, 42, 0, 21, 42, 21, 42, 84, 0, 168, 0);                 // chords
bb_pat(8,  1, 128, 2, 64, 4, 32, 8, 16, 16, 8, 32, 4, 64, 2, 128, 1);                  // leaps
bb_pat(9,  5, 5, 80, 0, 5, 5, 80, 0, 10, 10, 160, 0, 10, 10, 160, 0);                  // gallop
bb_pat(10, 255, 0, 0, 0, 15, 0, 0, 0, 240, 0, 0, 0, 255, 0, 85, 0);                     // swells
bb_pat(11, 129, 66, 36, 24, 36, 66, 129, 0, 129, 66, 36, 24, 36, 66, 129, 0);           // spiral
bb_pat(12, 1, 0, 0, 16, 0, 0, 4, 0, 0, 64, 0, 0, 2, 0, 32, 0);                          // sparse

// ---------- MuRF filters: eight constant-peak band-passes (Q 6) ----------
function bb_mfset(mid) local(i, f, w, cw, al, n, o) (
  i = 0;
  loop(8,
    f = (mid ? 200 : 100) * 1.5 ^ i;                                 // BASS 100 Hz .. 1.7 kHz, MID 200 Hz .. 3.4 kHz
    w = 2 * $pi * min(f * (1 + 0.004 * dm_a * bb_vf), 0.45 * bb_fs) / bb_fs; cw = cos(w); al = sin(w) / 12; n = 1 + al;
    o = MFC + i * 5; o[0] = al / n; o[1] = 0; o[2] = -al / n; o[3] = -2 * cw / n; o[4] = (1 - al) / n;
    i += 1;
  );
);

// =====================================================================
// THE MODULES - each reads and writes the pair bb_a / bb_b
// =====================================================================
// the input stage of every module (but the SATURATOR, which is all drive): level-compensated
function md_drv(g) instance(ca, cb) (
  bb_a = this.ca.clp_run(bb_a * g * 0.35) / (g * 0.35); bb_b = this.cb.clp_run(bb_b * g * 0.35) / (g * 0.35);
);
function md_cfg() ( this.ca.clp_set(1, 1, 0.9, 0); this.cb.clp_set(1, 1, 0.9, 0); );
function md_reset() ( this.ca.clp_reset(); this.cb.clp_reset(); );

// ---- LOWPASS: the ladder and its envelope follower ----
function lp_run(cv) instance(e, a1, a2, a3, a4, b1, b2, b3, b4, pm) local(l, en, fc, g, k, ua, ub, ya, yb) (
  l = max(abs(bb_a), abs(bb_b)); e += (l > e ? lp_ka : lp_kd) * (l - e);
  en = min(e * 3, 1);
  fc = min(max(lp_fc * 2 ^ (lp_env * en * 5 + cv * 4), 20), 0.42 * srate);
  g = 1 - exp(-2 * $pi * fc / bb_fs);
  k = lp_k;
  ua = bb_th(bb_a * (1 + 0.35 * k) - k * a4); ub = bb_th(bb_b * (1 + 0.35 * k) - k * b4);
  a1 += g * (ua - a1); a2 += g * (a1 - a2); a3 += g * (a2 - a3); a4 += g * (a3 - a4);
  b1 += g * (ub - b1); b2 += g * (b1 - b2); b3 += g * (b2 - b3); b4 += g * (b3 - b4);
  pm += (lp_p4 - pm) * bb_k10o;                                        // 2 / 4-pole: a 10 ms blend
  bb_a = a2 + pm * (a4 - a2); bb_b = b2 + pm * (b4 - b2);
  bb_lpe = en;
);
function lp_reset() instance(e, a1, a2, a3, a4, b1, b2, b3, b4, pm) ( e = a1 = a2 = a3 = a4 = b1 = b2 = b3 = b4 = 0; pm = lp_p4; );
function lp_tidy() instance(e, a1, a2, a3, a4, b1, b2, b3, b4) (
  e = bb_tn(e); a1 = bb_tn(a1); a2 = bb_tn(a2); a3 = bb_tn(a3); a4 = bb_tn(a4); b1 = bb_tn(b1); b2 = bb_tn(b2); b3 = bb_tn(b3); b4 = bb_tn(b4);
);

// ---- every module's envelope (taken at its input, like LOWPASS and SATURATOR), 0 .. 1 over -40 .. 0 dBFS, for the patch bay ----
function md_env() instance(e) local(l) (
  l = max(abs(bb_a), abs(bb_b)); e += (l > e ? st_ka : st_kd) * (l - e);
  min(max((20 * log10(max(e, 0.00001)) + 40) / 40, 0), 1);
);

// ---- RING: the carrier is shared (bb_cl / bb_cr, computed per sample) ----
function rg_run() ( bb_a *= bb_cl * 1.3; bb_b *= bb_cr * 1.3; );

// ---- PHASER: 12 first-order all-passes per side (states at pm), feedback; the 6 / 12-stage tap blended ----
function ph_run() instance(pm, fa, fb, sm) local(va, vb, i, ya, yb, a6, b6, o) (
  va = bb_a + ph_fb * fa; vb = bb_b + ph_fb * fb;
  o = pm; i = 0;
  loop(12,
    ya = ph_aL * va + o[0]; o[0] = va - ph_aL * ya; va = ya;
    yb = ph_aR * vb + o[1]; o[1] = vb - ph_aR * yb; vb = yb;
    i == 5 ? ( a6 = va; b6 = vb; );
    o += 2; i += 1;
  );
  sm += (ph_12 - sm) * bb_k10o;
  fa = a6 + sm * (va - a6); fb = b6 + sm * (vb - b6);
  fa = bb_th(fa); fb = bb_th(fb);
  bb_a = fa; bb_b = fb;
);
function ph_reset() instance(pm, fa, fb, sm) ( memset(pm, 0, 24); fa = fb = 0; sm = ph_12; );
function ph_tidy() instance(pm, fa, fb) local(i) ( i = 0; loop(24, pm[i] = bb_tn(pm[i]); i += 1; ); fa = bb_tn(fa); fb = bb_tn(fb); );

// ---- MURF: eight band-passes per side (states at mm), each at its fader level x its envelope ----
function mf_run() instance(mm) local(i, o, s, xa, xb, ya, yb, ga, gb, qa, qb, g) (
  xa = bb_a; xb = bb_b; qa = qb = 0; i = 0; o = MFC; s = mm;
  loop(8,
    ya = o[0] * xa + s[0]; s[0] = o[1] * xa - o[3] * ya + s[1]; s[1] = o[2] * xa - o[4] * ya;
    yb = o[0] * xb + s[2]; s[2] = o[1] * xb - o[3] * yb + s[3]; s[3] = o[2] * xb - o[4] * yb;
    g = MFL[i] * MFE[i];
    bb_st ? ( ga = (i & 1) ? 0.25 : 1; gb = (i & 1) ? 1 : 0.25; ) : ga = gb = 1;
    qa += ya * g * ga; qb += yb * g * gb;
    o += 5; s += 4; i += 1;
  );
  bb_a = qa * mf_mk * mf_vg; bb_b = qb * mf_mk * mf_vg;
);
function mf_reset() instance(mm) ( memset(mm, 0, 32); );
function mf_tidy() instance(mm) local(i) ( i = 0; loop(32, mm[i] = bb_tn(mm[i]); i += 1; ); );

// ---- SATURATOR: the drive moved by its envelope (and its CV), statically compensated; the noise added ----
function st_run(cv) instance(e) local(l, db, en, gd, gc) (
  l = max(abs(bb_a), abs(bb_b)); e += (l > e ? st_ka : st_kd) * (l - e);
  db = 20 * log10(max(e, 0.00001)); en = min(max((db + 40) / 40, 0), 1);
  gd = 10 ^ ((st_db + st_env * 24 * (en - 0.5) + cv * 12) / 20) * 0.35;
  gc = 10 ^ (-cv * 0.6) / st_gs;                                     // a patched drive is level-compensated (more grit, not more level)
  bb_a = this.ca.clp_run(bb_a * gd) * gc + bb_nl; bb_b = this.cb.clp_run(bb_b * gd) * gc + bb_nr;
  bb_ste = en;
);
function st_cfg() ( this.ca.clp_set(1, 1, 0.85, 0); this.cb.clp_set(1, 1, 0.85, 0); );
function st_reset() instance(e) ( e = 0; this.ca.clp_reset(); this.cb.clp_reset(); );

// ---------- one chain set: the five modules in the order o; each faded by its IN key and mixed ----------
function set_run(xl, xr, o) local(k, m, da, db) (
  bb_a = xl; bb_b = xr; k = 0;
  loop(5,
    m = PERM[o * 5 + k]; da = bb_a; db = bb_b;
    m == 0 ? ( bb_g0 > 0 ? ( this.d0.md_drv(bb_dg0); this.lp.lp_run(DST[0]);
                              bb_a = da + bb_e0 * bb_mm0 * (bb_a - da); bb_b = db + bb_e0 * bb_mm0 * (bb_b - db); ); ) :
    m == 1 ? ( bb_g1 > 0 ? ( bb_rge = this.re.md_env(); this.d1.md_drv(bb_dg1); rg_run();
                              bb_a = da + bb_e1 * bb_mm1 * (bb_a - da); bb_b = db + bb_e1 * bb_mm1 * (bb_b - db); ); ) :
    m == 2 ? ( bb_g2 > 0 ? ( bb_phe = this.pe.md_env(); this.d2.md_drv(bb_dg2); this.ph.ph_run();
                              bb_a = da + bb_e2 * bb_mm2 * (bb_a - da); bb_b = db + bb_e2 * bb_mm2 * (bb_b - db); ); ) :
    m == 3 ? ( bb_g3 > 0 ? ( this.d3.md_drv(bb_dg3); this.mf.mf_run();
                              bb_a = da + bb_e3 * bb_mm3 * (bb_a - da); bb_b = db + bb_e3 * bb_mm3 * (bb_b - db); ); ) :
             ( bb_g4 > 0 ? ( this.st.st_run(DST[10]);
                              bb_a = da + bb_e4 * bb_mx4 * (bb_a - da); bb_b = db + bb_e4 * bb_mx4 * (bb_b - db); ); );
    k += 1;
  );
);
function set_cfg() ( this.d0.md_cfg(); this.d1.md_cfg(); this.d2.md_cfg(); this.d3.md_cfg(); this.st.st_cfg(); );
function set_reset() (
  this.d0.md_reset(); this.d1.md_reset(); this.d2.md_reset(); this.d3.md_reset();
  this.lp.lp_reset(); this.ph.ph_reset(); this.mf.mf_reset(); this.st.st_reset(); this.re.e = this.pe.e = 0;
);
function set_tidy() ( this.lp.lp_tidy(); this.ph.ph_tidy(); this.mf.mf_tidy(); this.d0.ca.clp_tidy(); this.d0.cb.clp_tidy();
  this.d1.ca.clp_tidy(); this.d1.cb.clp_tidy(); this.d2.ca.clp_tidy(); this.d2.cb.clp_tidy(); this.d3.ca.clp_tidy(); this.d3.cb.clp_tidy();
  this.st.ca.clp_tidy(); this.st.cb.clp_tidy(); this.st.e = bb_tn(this.st.e); this.re.e = bb_tn(this.re.e); this.pe.e = bb_tn(this.pe.e); );

// every parameter to the host after a preset (audio side): 1 - 63 by mask
function bb_autoall() (
  slider_automate(4294967295); slider_automate(2147483647 * 4294967296);
);

// ---------- presets ----------
#bb_pm = "Init|Funky Wah|Slow Sweep|Ring Bells|Ring Tremolo|Jet Phaser|Classic Phase|MuRF Pulse|MuRF Bass Steps|Saturated Wah|Noise Wash|Full Rack|Envelope Phaser|Ducking Ring|Cross Talk|Swell Steps|Breathing Noise";
function p_lp(i, d, c, r, p, e, f, m) ( slider6 = i; slider7 = d; slider8 = c; slider9 = r; slider10 = p; slider11 = e; slider12 = f; slider13 = m; );
function p_rg(i, d, c, r, a, w, m) ( slider14 = i; slider15 = d; slider16 = c; slider17 = r; slider18 = a; slider19 = w; slider20 = m; );
function p_ph(i, d, w, r, n, t, a, m) ( slider21 = i; slider22 = d; slider23 = w; slider24 = r; slider25 = n; slider26 = t; slider27 = a; slider28 = m; );
function p_mf(i, d, r, e, p, v, l1, l2, l3, l4, l5, l6, l7, l8, m) (
  slider29 = i; slider30 = d; slider31 = r; slider32 = e; slider33 = p; slider34 = v;
  slider35 = l1; slider36 = l2; slider37 = l3; slider38 = l4; slider39 = l5; slider40 = l6; slider41 = l7; slider42 = l8;
  slider43 = m;
);
function p_st(i, d, e, n, t, h, m) ( slider44 = i; slider45 = d; slider46 = e; slider47 = n; slider48 = t; slider49 = h; slider50 = m; );
// a patch cable: k 0 - 2 (A - C), from, to, amount (%), lag (%)
function p_cb(k, f, t, a, l) ( slider(51 + 4 * k) = f; slider(52 + 4 * k) = t; slider(53 + 4 * k) = a; slider(54 + 4 * k) = l; );
function p_off() (
  p_rg(0, 2, 200, 2, 0, 0, 100); p_ph(0, 2, 800, 4, 0, 0.5, 50, 50);
  p_mf(0, 2, 4, 4, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 100); p_st(0, 3, 0, 0, 3000, 0, 100);
  p_cb(0, 0, 0, 50, 0); p_cb(1, 0, 3, 50, 0); p_cb(2, 0, 5, 50, 0);   // unpatched
);
// cable sources: 1 INPUT ENV, 2 LP ENV, 3 RING ENV, 4 PH ENV, 5 MURF ENV, 6 SAT ENV, 7 RING LFO, 8 PH LFO, 9 NOISE
// destinations: 0 LP CUTOFF, 1 LP RES, 2 LP MIX, 3 RING FREQ, 4 RING MIX, 5 PH SWEEP, 6 PH RES, 7 PH MIX, 8 MURF RATE, 9 MURF MIX,
//               10 SAT DRIVE, 11 SAT NOISE
function load_preset(p) (
  p_off();
  p == 1 || p == 18 ? ( p_lp(1, 2, 800, 4, 1, 5, 1, 100); slider1 = 0; slider2 = 0; ) :                                // Init
  p == 2  ? ( p_lp(1, 3, 350, 6.5, 0, 8, 1, 100); slider1 = 0; slider2 = 0; ) :                                       // Funky Wah
  p == 3  ? ( p_lp(1, 2, 600, 6, 1, 0, 0, 100); p_ph(1, 2, 700, 3, 0, 0.12, 60, 50); p_cb(0, 8, 0, 60, 0);
              slider1 = bb_ord_au(2, 0, 1, 3, 4); slider2 = 1; ) :                                                    // Slow Sweep
  p == 4  ? ( p_rg(1, 2, 640, 0.3, 10, 0, 70); p_lp(1, 2, 4000, 2, 1, 0, 1, 100); slider1 = bb_ord_au(1, 0, 2, 3, 4); slider2 = 1; ) :     // Ring Bells
  p == 5  ? ( p_rg(1, 2, 6, 0.5, 0, 0, 100); slider1 = bb_ord_au(1, 0, 2, 3, 4); slider2 = 1; p_lp(0, 2, 800, 4, 1, 5, 1, 100); ) :     // Ring Tremolo
  p == 6  ? ( p_ph(1, 3, 1200, 8, 1, 0.08, 90, 50); p_lp(0, 2, 800, 4, 1, 5, 1, 100); slider1 = bb_ord_au(2, 0, 1, 3, 4); slider2 = 1; ) :   // Jet Phaser
  p == 7  ? ( p_ph(1, 2, 600, 3, 0, 0.4, 70, 50); p_lp(0, 2, 800, 4, 1, 5, 1, 100); slider1 = bb_ord_au(2, 0, 1, 3, 4); slider2 = 0; ) :    // Classic Phase
  p == 8  ? ( p_mf(1, 2, 6, 3, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 100); p_lp(0, 2, 800, 4, 1, 5, 1, 100); slider1 = bb_ord_au(3, 0, 1, 2, 4); slider2 = 1; ) :   // MuRF Pulse
  p == 9  ? ( p_mf(1, 3, 4, 5, 5, 0, 10, 9, 8, 7, 6, 5, 4, 3, 100); p_lp(0, 2, 800, 4, 1, 5, 1, 100); slider1 = bb_ord_au(3, 0, 1, 2, 4); slider2 = 0; ) :  // MuRF Bass Steps
  p == 10 ? ( p_st(1, 6, 4, 0, 3000, 0, 100); p_lp(1, 2, 500, 6, 1, 6, 1, 100); p_cb(0, 6, 0, 40, 0);
              slider1 = bb_ord_au(4, 0, 1, 2, 3); slider2 = 0; ) :                                                    // Saturated Wah
  p == 11 ? ( p_st(1, 2, -4, 5, 1500, 0, 100); p_lp(1, 2, 900, 5, 1, 4, 0, 100); p_ph(1, 2, 900, 5, 1, 0.1, 60, 50); p_cb(0, 9, 0, 50, 0);
              slider1 = bb_ord_au(4, 0, 2, 1, 3); slider2 = 1; ) :                                                    // Noise Wash
  p == 12 ? ( p_st(1, 4, 2, 0, 3000, 0, 100); p_lp(1, 2, 700, 5, 1, 5, 1, 70); p_rg(1, 2, 3, 0.2, 40, 0, 35);
              p_ph(1, 2, 800, 4, 1, 0.3, 50, 50); p_mf(1, 2, 4, 4, 3, 1, 6, 6, 6, 6, 6, 6, 6, 6, 40); p_cb(0, 2, 5, 30, 0);
              slider1 = bb_ord_au(4, 0, 1, 2, 3); slider2 = 1; ) :                                                    // Full Rack
  // ---- the patch bay presets ----
  p == 13 ? ( p_lp(0, 2, 800, 4, 1, 5, 1, 100); p_ph(1, 2, 330, 5, 1, 0.2, 10, 50);
              p_cb(0, 1, 5, 80, 15); p_cb(1, 1, 6, 40, 15);
              slider1 = bb_ord_au(2, 0, 1, 3, 4); slider2 = 1; ) :                                                    // Envelope Phaser: your playing opens the sweep
  p == 14 ? ( p_rg(1, 2, 330, 0.5, 0, 0, 100); p_lp(1, 2, 3300, 3, 1, 0, 1, 100);
              p_cb(0, 1, 4, -90, 25); p_cb(1, 3, 0, -40, 10);
              slider1 = bb_ord_au(1, 0, 2, 3, 4); slider2 = 1; ) :                                                    // Ducking Ring: the ring blooms in the tails
  p == 15 ? ( p_st(1, 4, 0, 0, 3000, 0, 100); p_lp(1, 2, 660, 6, 1, 0, 1, 100); p_ph(1, 2, 880, 4, 0, 0.3, 30, 50);
              p_cb(0, 6, 0, 60, 0); p_cb(1, 2, 5, 50, 30); p_cb(2, 8, 10, 30, 0);
              slider1 = bb_ord_au(4, 0, 2, 1, 3); slider2 = 1; ) :                                                    // Cross Talk: each module plays the next
  p == 16 ? ( p_mf(1, 2, 3, 5, 3, 1, 8, 8, 8, 8, 8, 8, 8, 8, 100); p_lp(1, 2, 550, 5, 1, 0, 0, 100);
              p_cb(0, 1, 8, 70, 40); p_cb(1, 5, 0, 50, 0);
              slider1 = bb_ord_au(3, 0, 1, 2, 4); slider2 = 1; ) :                                                    // Swell Steps: play harder, the steps run faster
  p == 17 ? ( p_st(1, 2, 0, 4, 2200, 0, 100); p_ph(1, 2, 660, 4, 1, 0.15, 50, 50); p_lp(0, 2, 800, 4, 1, 5, 1, 100);
              p_cb(0, 1, 11, -100, 60); p_cb(1, 1, 7, -40, 30);
              slider1 = bb_ord_au(4, 2, 0, 1, 3); slider2 = 1; );                                                     // Breathing Noise: noise in the gaps
  p == 18 ? ( slider3 = 0; slider4 = 50; slider5 = 0; );                                                           // Reset all to defaults
  // OUTPUT: each preset level-matched on programme at 0 VU (-18 dBFS), its peaks kept clear of the ceiling
  p >= 2 && p <= 17 ? slider3 = p == 2 ? 1 : p == 3 ? 2.5 : p == 4 ? 1.8 : p == 5 ? -3.7 : p == 6 ? -3.1 : p == 7 ? -2.6 : p == 8 ? 2
    : p == 9 ? -7.6 : p == 10 ? 1.8 : p == 11 ? 6 : p == 12 ? 6 : p == 13 ? -1.4 : p == 14 ? 2.7 : p == 15 ? 3.5 : p == 16 ? 5 : 0;
  p == 1 ? slider3 = 0;
  bb_autoall();
  pr_cur = p == 18 ? 1 : p;
);

// ---------- state ----------
function bb_srinit() (
  bb_sr = srate;
  bb_fs = srate * os_f;                               // the chain's running rate (OS)
  st_ka = bb_to(5); st_kd = bb_to(120);              // (the chain's envelopes run at the OS rate)
  bb_k10o = bb_to(10);
  bb_ika = bb_tc(5); bb_ikd = bb_tc(120);            // INPUT ENV (the dry input's level, for the patch bay)
  bb_fst = 1 / (0.01 * srate);                        // IN keys: 10 ms
  bb_xst = 1 / (0.025 * srate);
  bb_rss = 1 / (0.015 * srate);                       // the fade-in after REAPER's bypass: 15 ms                       // the chain crossfade: 25 ms
  bb_k10 = bb_tc(10);
  bb_gsm = 1 - exp(-1 / (0.015 * srate));
  bb_nk = bb_tc(1000 / (2 * $pi * 20));               // the noise CV: noise through 20 Hz
  ost_init(0);
);
os_design(); os_base = 0; os_f = os_tf = os_f2n(slider5);
bb_srinit();
os_gsetup();
in_init();
dF.drift_init(0.8 + rand(1) * 0.4); dL.drift_init(0.8 + rand(1) * 0.4); dV.drift_init(0.8 + rand(1) * 0.4);
drift_macro(50);
chA.ph.pm = PHA; chB.ph.pm = PHB; chA.mf.mm = MSA; chB.mf.mm = MSB;
chA.set_reset(); chB.set_reset();
bb_cur = 0; bb_oCur = bb_oNew = floor(min(max(slider1, 0), 119) + 0.5); bb_xf = 0;
bb_snap = 1; bb_vce = -1; bb_rsf = 1; bb_rsq = 0; bb_last = 0;
memset(CAB, 0, 48); memset(DST, 0, 12); memset(CBV, 0, 6); memset(CVS, 0, 10);
ouL.os_clr(); ouR.os_clr(); odL.os_clr(); odR.os_clr();
mf_step = 15;

ui_thm = 5; ui_hw = 1;                                // house keycaps for the preset bar and the size picker
!ui_dflt ? ( ui_sz = 0; pr_cur = 1; );                // new instances open in Medium, on Init
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() (
  0
);
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(17, 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 <= 17 ? ( 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();

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

@block
prs_chk();
// resume after REAPER's bypass (or any gap in processing): the filters and the oversampler hold stale audio, so
// @sample clears them and fades in from the dry input (15 ms)
bb_now = time_precise();
bb_last > 0 && bb_now - bb_last > max(0.15, 4 * samplesblock / srate) ? bb_rsq = 1;
bb_last = bb_now;
srate != bb_sr ? ( bb_srinit(); os_gsetup(); bb_mfset(bb_vce); );
in_setup(0, 0);
ost_setup(0, 0.3, 120, 6000, -0.3, 1);                // the house output stage: CLIP (drive units)
os_tf = os_f2n(slider5);                             // OS: the factor asked for (os_gate swaps it in)
pdc_bot_ch = 0; pdc_top_ch = 2; pdc_delay = os_base + os_lat(os_f);   // (ost_setup zeroes it)
drift_macro(slider4);
// the chain: a change starts the crossfade into the other set (warmed up first); one more change waits for it
bb_ot = floor(min(max(slider1, 0), 119) + 0.5);
bb_xf <= 0 && bb_ot != bb_oCur ? (
  bb_snap ? bb_oCur = bb_ot : (
    bb_oNew = bb_ot; bb_xf = 0.000001; bb_wu = floor(0.04 * srate);
    bb_cur ? chA.set_reset() : chB.set_reset();
  );
);
bb_st = slider2 >= 0.5;
bb_t0 = slider6 >= 0.5; bb_t1 = slider14 >= 0.5; bb_t2 = slider21 >= 0.5; bb_t3 = slider29 >= 0.5; bb_t4 = slider44 >= 0.5;
bb_gl = bb_snap ? 1 : 1 - exp(-samplesblock / (0.03 * srate));
// DRIFT: filter tuning and LFO rates
bb_n = samplesblock / srate;
bb_vf = dF.drift_tick(0.05 * dm_s * bb_n); bb_vl = dL.drift_tick(0.05 * dm_s * bb_n);
bb_vv = dV.drift_tick(0.07 * dm_s * bb_n); bb_rail = 1 + 0.05 * dm_a * bb_vv;      // the patch bay's supply rail breathes
bb_tune = 2 ^ (0.03 * dm_a * bb_vf); bb_rate = 1 + 0.03 * dm_a * bb_vl;
// the modules' drives (0 .. +24 dB) and mixes
bb_dg0 += (10 ^ (2.4 * min(max(slider7, 0), 10) / 20) - bb_dg0) * bb_gl;
bb_dg1 += (10 ^ (2.4 * min(max(slider15, 0), 10) / 20) - bb_dg1) * bb_gl;
bb_dg2 += (10 ^ (2.4 * min(max(slider22, 0), 10) / 20) - bb_dg2) * bb_gl;
bb_dg3 += (10 ^ (2.4 * min(max(slider30, 0), 10) / 20) - bb_dg3) * bb_gl;
bb_mx0 += (slider13 / 100 - bb_mx0) * bb_gl; bb_mx1 += (slider20 / 100 - bb_mx1) * bb_gl; bb_mx2 += (slider28 / 100 - bb_mx2) * bb_gl;
bb_mx3 += (slider43 / 100 - bb_mx3) * bb_gl; bb_mx4 += (slider50 / 100 - bb_mx4) * bb_gl;
// the PATCH BAY: each cable's FROM / TO (a change fades the cable out and back, in @sample), AMOUNT (glided), LAG
// fields: 0 from, 1 to (in use), 2 the cable's fade, 3 the lagged source, 4 amount, 5 the lag coefficient, 6 from, 7 to (asked for)
bb_k = 0;
loop(3,
  bb_o = CAB + bb_k * 8;
  bb_o[6] = floor(min(max(slider(51 + 4 * bb_k), 0), 9) + 0.5); bb_o[7] = floor(min(max(slider(52 + 4 * bb_k), 0), 11) + 0.5);
  bb_o[4] += (min(max(slider(53 + 4 * bb_k), -100), 100) / 100 - bb_o[4]) * bb_gl;
  bb_lg = min(max(slider(54 + 4 * bb_k), 0), 100) / 100;
  bb_o[5] = 1 - exp(-1 / ((0.02 + 2 * bb_lg * bb_lg) * srate));                    // LAG: 20 ms (an envelope's ripple and hits smoothed) .. 2 s
  bb_snap ? ( bb_o[0] = bb_o[6]; bb_o[1] = bb_o[7]; bb_o[2] = 1; );
  bb_k += 1;
);
// LOWPASS
bb_lfc += (log(min(max(slider8, 20), 12000)) - bb_lfc) * bb_gl; lp_fc = exp(bb_lfc) * bb_tune;
lp_res = min(max(slider9, 0), 10); lp_p4 = slider10 >= 0.5; lp_env = min(max(slider11, -10), 10) / 10;
slider12 >= 0.5 ? ( lp_ka = bb_to(2); lp_kd = bb_to(60); ) : ( lp_ka = bb_to(25); lp_kd = bb_to(400); );
// RING
bb_rfc += (log(min(max(slider16, 0.6), 4000)) - bb_rfc) * bb_gl;
rg_lr = min(max(slider17, 0.1), 25) * bb_rate; rg_la = min(max(slider18, 0), 100) / 100 * 3; rg_sq = slider19 >= 0.5;
// PHASER
bb_pfc += (log(min(max(slider23, 100), 8000)) - bb_pfc) * bb_gl;
ph_res = min(max(slider24, 0), 10); ph_12 = slider25 >= 0.5;
ph_lr = min(max(slider26, 0.05), 20) * bb_rate; ph_la = min(max(slider27, 0), 100) / 100 * 2.5;
// MURF
mf_rt = min(max(slider31, 0.5), 20) * bb_rate; mf_pat = floor(min(max(slider33, 1), 12) + 0.5);
mf_e = min(max(slider32, 0), 10) / 10; mf_ka = bb_tc(1 + 150 * mf_e * mf_e); mf_kd = bb_tc(60 + 1400 * mf_e);
bb_i = 0; loop(8, MFL[bb_i] = sqr(min(max(slider(35 + bb_i), 0), 10) / 10); bb_i += 1; );
mf_mk = 5;
bb_vc = slider34 >= 0.5;
bb_snap ? ( bb_vce = bb_vc; bb_mfset(bb_vc); mf_vg = 1; );            // (a voicing change: faded out, swapped, faded in)
// SATURATOR
st_db = 2.4 * min(max(slider45, 0), 10); st_gs = 10 ^ (st_db / 20) * 0.35; st_env = min(max(slider46, -10), 10) / 10;
bb_nlr = min(max(slider47, 0), 10) / 10;
bb_nf = min(max(slider48, 100), 12000); bb_nhp = slider49 >= 0.5;
bb_nw = 2 * $pi * min(bb_nf, 0.45 * srate) / srate; bb_ncw = cos(bb_nw); bb_nal = sin(bb_nw) / 1.41421356; bb_nn = 1 + bb_nal;
bb_nhp ? ( NZC[0] = (1 + bb_ncw) * 0.5 / bb_nn; NZC[1] = -(1 + bb_ncw) / bb_nn; ) : ( NZC[0] = (1 - bb_ncw) * 0.5 / bb_nn; NZC[1] = (1 - bb_ncw) / bb_nn; );
NZC[2] = NZC[0]; NZC[3] = -2 * bb_ncw / bb_nn; NZC[4] = (1 - bb_nal) / bb_nn;
bb_outt = 10 ^ (min(max(slider3, -12), 12) / 20);
chA.set_cfg(); chB.set_cfg();
bb_snap ? ( bb_g0 = bb_t0; bb_g1 = bb_t1; bb_g2 = bb_t2; bb_g3 = bb_t3; bb_g4 = bb_t4; bb_outs = bb_outt; chA.lp.pm = chB.lp.pm = lp_p4; chA.ph.sm = chB.ph.sm = ph_12; );
bb_snap = 0;
chA.set_tidy(); chB.set_tidy(); nz.s1 = bb_tn(nz.s1); nz.s2 = bb_tn(nz.s2);
// the panel's mailboxes
bba_ol_d = max(bba_ol_d, bb_ol); bba_or_d = max(bba_or_d, bb_or); bb_ol = bb_or = 0;
bba_step = mf_step; bba_lpe = CVS[2]; bba_ste = CVS[6];

@sample
bb_rsq ? (
  bb_rsq = 0; bb_rsf = 0;
  chA.set_reset(); chB.set_reset(); ouL.os_clr(); ouR.os_clr(); odL.os_clr(); odR.os_clr();
  nz.s1 = nz.s2 = 0; bb_ie = 0;
);
spl0 != spl0 ? spl0 = 0; spl1 != spl1 ? spl1 = 0;
in_process(spl0, spl1);
bb_xl = in_l; bb_xr = in_r;
abs(bb_xl) < 0.000000000000001 ? bb_xl = 0; abs(bb_xr) < 0.000000000000001 ? bb_xr = 0;
// IN keys (a module coming back in starts from rest)
bb_g0 != bb_t0 ? ( bb_g0 == 0 ? ( chA.lp.lp_reset(); chB.lp.lp_reset(); ); bb_g0 = bb_t0 ? min(bb_g0 + bb_fst, 1) : max(bb_g0 - bb_fst, 0); );
bb_g1 != bb_t1 ? ( bb_g1 = bb_t1 ? min(bb_g1 + bb_fst, 1) : max(bb_g1 - bb_fst, 0); );
bb_g2 != bb_t2 ? ( bb_g2 == 0 ? ( chA.ph.ph_reset(); chB.ph.ph_reset(); ); bb_g2 = bb_t2 ? min(bb_g2 + bb_fst, 1) : max(bb_g2 - bb_fst, 0); );
bb_g3 != bb_t3 ? ( bb_g3 == 0 ? ( chA.mf.mf_reset(); chB.mf.mf_reset(); ); bb_g3 = bb_t3 ? min(bb_g3 + bb_fst, 1) : max(bb_g3 - bb_fst, 0); );
bb_g4 != bb_t4 ? ( bb_g4 == 0 ? ( chA.st.st_reset(); chB.st.st_reset(); ); bb_g4 = bb_t4 ? min(bb_g4 + bb_fst, 1) : max(bb_g4 - bb_fst, 0); );
bb_e0 = bb_sm(bb_g0); bb_e1 = bb_sm(bb_g1); bb_e2 = bb_sm(bb_g2); bb_e3 = bb_sm(bb_g3); bb_e4 = bb_sm(bb_g4);
bb_xf > 0 ? ( bb_wu > 0 ? bb_wu -= 1 : ( bb_xf += bb_xst; bb_xf >= 1 ? ( bb_xf = 0; bb_cur = 1 - bb_cur; bb_oCur = bb_oNew; ); ); );
bb_ex = bb_sm(bb_xf);
bb_outs += (bb_outt - bb_outs) * bb_gsm;
// ---- OS: a factor change swaps in at the silent point (os_gate); everything at the chain's rate follows ----
os_gate();
os_chg ? (
  bb_srinit(); bb_mfset(bb_vce);
  chA.set_reset(); chB.set_reset();
  ouL.os_clr(); ouR.os_clr(); odL.os_clr(); odR.os_clr();
  slider12 >= 0.5 ? ( lp_ka = bb_to(2); lp_kd = bb_to(60); ) : ( lp_ka = bb_to(25); lp_kd = bb_to(400); );
);
// ---- the control signals: LFOs, the MuRF sequencer, the noise, the input's envelope; the cable sources (CVS[1..9]) ----
rg_ph += rg_lr / srate; rg_ph >= 1 ? rg_ph -= 1;
rg_lfo = rg_sq ? ( rg_ph < 0.5 ? 1 : -1 ) : sin(2 * $pi * rg_ph);
rg_sl += (rg_lfo - rg_sl) * bb_k10 * 0.3;                             // (the square's edges softened a little)
ph_ph += ph_lr / srate; ph_ph >= 1 ? ph_ph -= 1;
ph_lfo = sin(2 * $pi * ph_ph);
bb_nz = rand(2) - 1; bb_nc += bb_nk * (bb_nz - bb_nc);
bb_il = max(abs(bb_xl), abs(bb_xr)); bb_ie += (bb_il > bb_ie ? bb_ika : bb_ikd) * (bb_il - bb_ie);
CVS[1] = min(max((20 * log10(max(bb_ie, 0.00001)) + 40) / 40, 0), 1);     // INPUT ENV: -40 .. 0 dBFS
CVS[7] = rg_sl; CVS[8] = ph_lfo; CVS[9] = min(max(bb_nc * 6, -1), 1);
// ---- the PATCH BAY: each cable lags its source, scales it (the supply rail breathes with DRIFT) and adds it to its
// destination; a cable whose FROM / TO changes fades out (10 ms), swaps, fades back in ----
memset(DST, 0, 12);
bb_k = 0;
loop(3,
  bb_o = CAB + bb_k * 8;
  bb_o[6] != bb_o[0] || bb_o[7] != bb_o[1] ? (
    bb_o[2] = max(bb_o[2] - bb_fst, 0);
    bb_o[2] == 0 ? ( bb_o[0] = bb_o[6]; bb_o[1] = bb_o[7]; bb_o[3] = CVS[bb_o[0]]; );
  ) : bb_o[2] < 1 ? bb_o[2] = min(bb_o[2] + bb_fst, 1);
  bb_o[0] > 0 ? (
    bb_o[3] += bb_o[5] * (CVS[bb_o[0]] - bb_o[3]);
    bb_v = bb_o[3] * bb_o[4] * bb_o[2] * bb_rail;
    DST[bb_o[1]] += bb_v; CBV[bb_k] = bb_v;
  ) : CBV[bb_k] = 0;
  bb_k += 1;
);
bb_mm0 = min(max(bb_mx0 + DST[2], 0), 1); bb_mm1 = min(max(bb_mx1 + DST[4], 0), 1);
bb_mm2 = min(max(bb_mx2 + DST[7], 0), 1); bb_mm3 = min(max(bb_mx3 + DST[9], 0), 1);
lp_k = min(max(lp_res + DST[1] * 5, 0), max(lp_res, 8)) * 0.43;          // (a patched resonance stops short of self-oscillation)
ph_fb = min(max(ph_res + DST[6] * 5, 0), max(ph_res, 8)) * 0.085;
// RING: the carrier's frequency (its phase runs at the chain's rate, below)
bb_cf = min(exp(bb_rfc) * 2 ^ (rg_sl * rg_la + DST[3] * 4), 0.45 * srate);
// PHASER: the all-pass coefficients (left, right: the right LFO inverted in STEREO)
bb_f = min(exp(bb_pfc) * bb_tune * 2 ^ (ph_lfo * ph_la + DST[5] * 3), 0.24 * srate); bb_w = $pi * bb_f / bb_fs;
bb_t = bb_w * (1 + bb_w * bb_w * (0.33333 + 0.13333 * bb_w * bb_w)); ph_aL = (bb_t - 1) / (bb_t + 1);
bb_st ? (
  bb_f = min(exp(bb_pfc) * bb_tune * 2 ^ (-ph_lfo * ph_la + DST[5] * 3), 0.24 * srate); bb_w = $pi * bb_f / bb_fs;
  bb_t = bb_w * (1 + bb_w * bb_w * (0.33333 + 0.13333 * bb_w * bb_w)); ph_aR = (bb_t - 1) / (bb_t + 1);
) : ph_aR = ph_aL;
// MURF: a voicing change fades the array out (10 ms), swaps the filters, fades back in
bb_vc != bb_vce ? ( mf_vg = max(mf_vg - bb_fst, 0); mf_vg == 0 ? ( bb_vce = bb_vc; bb_mfset(bb_vc); chA.mf.mf_reset(); chB.mf.mf_reset(); ); ) : mf_vg < 1 ? mf_vg = min(mf_vg + bb_fst, 1);
// MURF: the clock, each step firing its filters; their attack / decay envelopes
mf_clk += mf_rt * 2 ^ (DST[8] * 2) / srate;
mf_clk >= 1 ? ( mf_clk -= 1; mf_step = (mf_step + 1) % 16; mf_m = MPAT[(mf_pat - 1) * 16 + mf_step];
  bb_i = 0; loop(8, (mf_m & (1 << bb_i)) ? MFA[bb_i] = 1; bb_i += 1; ); );
bb_i = 0; mf_cv = 0;
loop(8,
  MFA[bb_i] ? ( MFE[bb_i] += mf_ka * (1.02 - MFE[bb_i]); MFE[bb_i] >= 1 ? ( MFE[bb_i] = 1; MFA[bb_i] = 0; ); )
            : MFE[bb_i] -= mf_kd * MFE[bb_i];
  mf_cv = max(mf_cv, MFE[bb_i]); bb_i += 1;
);
CVS[5] = bb_g3 > 0 ? mf_cv : 0;
// SATURATOR: the noise (filtered), its level patchable
bb_nn = NZC[0] * bb_nz + nz.s1; nz.s1 = NZC[1] * bb_nz - NZC[3] * bb_nn + nz.s2; nz.s2 = NZC[2] * bb_nz - NZC[4] * bb_nn;
bb_nl = bb_nn * sqr(min(max(bb_nlr + DST[11], 0), 1)) * 0.1; bb_nr = bb_st ? -bb_nl : bb_nl;
// ---- the chain, at the OS rate: up, os_f passes (the playing set, and the new one while the order crossfades), down ----
ouL.os_up(bb_xl); ouR.os_up(bb_xr);
bb_j = 0;
loop(os_f,
  bb_cp += bb_cf / bb_fs; bb_cp >= 1 ? bb_cp -= 1;
  bb_cl = sin(2 * $pi * bb_cp); bb_cr = bb_st ? cos(2 * $pi * bb_cp) : bb_cl;
  bb_ua = ouL.os_s(bb_j); bb_ub = ouR.os_s(bb_j);
  bb_cur ? chB.set_run(bb_ua, bb_ub, bb_oCur) : chA.set_run(bb_ua, bb_ub, bb_oCur);
  bb_ya = bb_a; bb_yb = bb_b;
  bb_ce0 = bb_lpe; bb_ce1 = bb_rge; bb_ce2 = bb_phe; bb_ce4 = bb_ste;     // (the playing set's envelopes)
  bb_xf > 0 ? (
    bb_cur ? chA.set_run(bb_ua, bb_ub, bb_oNew) : chB.set_run(bb_ua, bb_ub, bb_oNew);
    bb_ya += bb_ex * (bb_a - bb_ya); bb_yb += bb_ex * (bb_b - bb_yb);
  );
  bb_j == 0 ? ( bb_p0l = bb_ya; bb_p0r = bb_yb; ) : bb_j == 1 ? ( bb_p1l = bb_ya; bb_p1r = bb_yb; ) :
  bb_j == 2 ? ( bb_p2l = bb_ya; bb_p2r = bb_yb; ) : ( bb_p3l = bb_ya; bb_p3r = bb_yb; );
  bb_j += 1;
);
bb_yl = odL.os_dn(bb_p0l, bb_p1l, bb_p2l, bb_p3l); bb_yr = odR.os_dn(bb_p0r, bb_p1r, bb_p2r, bb_p3r);
os_g < 1 ? ( bb_yl *= os_g; bb_yr *= os_g; );
CVS[2] = bb_g0 > 0 ? bb_ce0 : 0; CVS[3] = bb_g1 > 0 ? bb_ce1 : 0; CVS[4] = bb_g2 > 0 ? bb_ce2 : 0; CVS[6] = bb_g4 > 0 ? bb_ce4 : 0;
// OUTPUT (after a resume: faded in from the dry input)
bb_yl *= bb_outs; bb_yr *= bb_outs;
bb_rsf < 1 ? ( bb_rsf = min(bb_rsf + bb_rss, 1); bb_rsw = bb_sm(bb_rsf); bb_yl = bb_xl + bb_rsw * (bb_yl - bb_xl); bb_yr = bb_xr + bb_rsw * (bb_yr - bb_xr); );
bb_yl != bb_yl ? bb_yl = 0; bb_yr != bb_yr ? bb_yr = 0;
ost_out(bb_yl, bb_yr);
spl0 = max(-4, min(4, ost_l));
spl1 = max(-4, min(4, ost_r));
bb_ol = max(bb_ol, abs(spl0)); bb_or = max(bb_or, 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 1080 716
ui_begin(1080, 716);
// =====================================================================
// GUI - in the 34 series manner (KRT81), as three stacked panels: LOWPASS, RING and PHASER above; MURF, SATURATOR and
// OUTPUT in the middle, with the CHAIN shown across its strip; the PATCH BAY below (three cables, FROM / TO menus in dark
// display boxes, AMOUNT and LAG; no drawn cords - house rule). Two looks: VFD (charcoal and warm orange-brown) and
// MATRIX (battleship grey, elephant's breath print, wood cream). Design space 1080 x 560. Knobs: drag (SHIFT = fine),
// wheel, double-click = default; the name shows the value while the knob is touched. Faders likewise.
// =====================================================================
bv_mx = 0;                                  // one look (VFD)
!bv_mx ? (                                                // VFD: charcoal and warm orange-brown
  KRP = 0x2E2F31; KRF = 0x202123; KRI = 0xF0E2D0; KRA = 0xC98A4B; KRW = 0x121314; KRM = 0x55575A;
  KRC = 0xC98A4B; KRPT = 0x24140A; KRCN = 0xC98A4B; KRGH = 0x332A22; KRAMB = 0xFFD24A; KRRED = 0xFF3B1F; KRKT = 0x24140A;
) : (                                                     // MATRIX: battleship grey, elephant's breath, wood cream
  KRP = 0x222528; KRF = ui_mix(0x222528, 0x000000, 0.25); KRI = 0xC7BEB3; KRA = 0xE8D3AE; KRW = ui_mix(0x222528, 0x000000, 0.45);
  KRM = 0x4A4E4A; KRC = 0xE8D3AE; KRPT = 0x17191B; KRCN = 0x1E5631; KRGH = 0x2C302E; KRAMB = 0xD4A373; KRRED = 0xBC3908; KRKT = 0x17191B;
);
KRHD = ui_mix(KRP, 0x000000, 0.35); KRI2 = ui_mix(KRI, KRF, 0.35);

gfx_setfont(6,  "Trebuchet MS", 27 * sc, 'b');
gfx_setfont(10, "Trebuchet MS", 12 * sc, 'b');
gfx_setfont(11, "Trebuchet MS", 13 * sc, 'b');
gfx_setfont(12, "Trebuchet MS", 13 * sc, 'b');
gfx_setfont(13, "Trebuchet MS", 12 * sc, 'b');
gfx_setfont(14, "Trebuchet MS", 15 * sc, 'b');
gfx_setfont(15, "Trebuchet MS", 12 * sc, 'b');     // lamp key legends

function kr_r(x, y, w, h) ( gfx_rect(x * sc, y * sc, w * sc, h * sc); );
function kr_txt(x, y, s) ( gfx_x = x * sc; gfx_y = y * sc; gfx_drawstr(s); );
function kr_ctxt(cx, cy, s) local(tw, th) ( tw = ui_symw(s); th = ui_symh; gfx_x = cx * sc - tw * 0.5; gfx_y = cy * sc - th * 0.5; ui_symstr(s); );
function kr_rtxt(rx, cy, s) local(tw, th) ( gfx_measurestr(s, tw, th); gfx_x = rx * sc - tw; gfx_y = cy * sc - th * 0.5; gfx_drawstr(s); );
function kr_lbl(x, y, s) ( gfx_setfont(11); ui_col(KRI2); kr_txt(x, y, s); );
function kr_hov(x, y, w, h) ( mouse_x >= x * sc && mouse_x < (x + w) * sc && mouse_y >= y * sc && mouse_y < (y + h) * sc; );
function kr_thick(x1, y1, x2, y2, w) local(dx, dy, l, nx, ny, k) (       // (drawn here: works with any system files)
  dx = x2 - x1; dy = y2 - y1; l = max(sqrt(dx * dx + dy * dy), 0.001); nx = -dy / l; ny = dx / l;
  k = -0.5 * w; loop(floor(w * 2.5) + 1, gfx_line(x1 + nx * k, y1 + ny * k, x2 + nx * k, y2 + ny * k, 1); k += 0.4; );
);
// panel keys: house lamp keys (gfx-ui). kr_kc = the key's lamp colour (set before the call; cleared after;
// none = the face's accent colour). By job: IN green, CV blue, module options yellow, move white,
// face orange, STEREO red.
function kr_key(x, y, w, h, lbl, on) local(c) (
  c = kr_kc ? kr_kc : KRA; kr_kc = 0;
  lk_well = 0x0A0A0B; lk_font = 15; lk_font2 = 15;
  lk_fit(x, y, w, h, on, c, lbl);
);
// a section that's switched off: greyed slightly (its IN key is redrawn on top)
function kr_off(x, y, w, h) ( ui_cola(KRF, 0.5); kr_r(x + 1, y + 1, w - 2, h - 2); );
// an end-of-row key: an unlit lamp key, not live, dimmed
function kr_dim(x, y, w, h, lbl) local(c) (
  c = kr_kc ? kr_kc : KRA; kr_kc = 0;
  lk_well = 0x0A0A0B; lk_font = 15; lk_font2 = 15; lk_inert = 1;
  lk_fit(x, y, w, h, 0, c, lbl);
  ui_cola(KRF, 0.55); kr_r(x, y, w, h); 0;
);
function kr_sect(x, y, w, h, name) (
  ui_col(0x040404); kr_r(x, y, w, h); ui_col(KRF); kr_r(x + 1, y + 1, w - 2, h - 2);
  strlen(name) ? kr_lbl(x + 9, y + 6, name);
);
function kr_scale(cx, cy, r, n, nr, labels, minor) local(i, k, k0, len, c, a, ca, sa, m) (
  len = strlen(labels); k0 = 0; i = 0; k = 0;
  minor > 0 ? (
    m = 0; ui_col(KRI2);
    loop((n - 1) * minor + 1,
      a = -0.75 * $pi + m / ((n - 1) * minor) * 1.5 * $pi; ca = sin(a); sa = -cos(a);
      gfx_line((cx + ca * (r + 4)) * sc, (cy + sa * (r + 4)) * sc, (cx + ca * (r + 6)) * sc, (cy + sa * (r + 6)) * sc, 1);
      m += 1;
    );
  );
  gfx_setfont(10);
  loop(len + 1,
    c = k < len ? str_getchar(labels, k) : 124;
    c == 124 ? (
      strcpy_substr(#krv_sl, labels, k0, k - k0);
      a = -0.75 * $pi + i / max(n - 1, 1) * 1.5 * $pi; ca = sin(a); sa = -cos(a);
      ui_col(KRI);
      gfx_line((cx + ca * (r + 3)) * sc, (cy + sa * (r + 3)) * sc, (cx + ca * (r + 7)) * sc, (cy + sa * (r + 7)) * sc, 1);
      strlen(#krv_sl) ? kr_ctxt(cx + ca * (r + nr), cy + sa * (r + nr), #krv_sl);
      i += 1; k0 = k + 1;
    );
    k += 1;
  );
);
function kr_bake(cx, cy, r, a) local(i, t, ca, sa, k) (
  ca = sin(a); sa = -cos(a);
  ui_cola(0x000000, 0.55); gfx_circle(cx + 2.5 * sc, cy + 4.5 * sc, r * 1.02, 1, 1);
  ui_col(0x0A0A0A); gfx_circle(cx, cy, r + 1 * sc, 1, 1);
  ui_col(0x191817); gfx_circle(cx, cy, r, 1, 1);
  i = 0;
  loop(16,
    t = i / 16 * 2 * $pi; k = 0.5 + 0.5 * cos(t + 0.8);
    ui_col(ui_mix(0x0D0C0B, 0x48443F, k * 0.8)); gfx_circle(cx + sin(t) * r * 0.86, cy - cos(t) * r * 0.86, r * 0.15, 1, 1);
    ui_col(0x0D0C0B); gfx_circle(cx + sin(t + 0.196) * r * 0.9, cy - cos(t + 0.196) * r * 0.9, r * 0.075, 1, 1);
    i += 1;
  );
  ui_col(0x090808); gfx_circle(cx, cy, r * 0.70 + 1 * sc, 1, 1);
  ui_col(0x22201E); gfx_circle(cx, cy, r * 0.70, 1, 1);
  ui_cola(0xFFFFFF, 0.08); gfx_circle(cx, cy, r * 0.70, 0, 1);
  ui_col(0x060606); kr_thick(cx + ca * r * 0.18, cy + sa * r * 0.18, cx + ca * r * 0.98, cy + sa * r * 0.98, 4.2 * sc);
  ui_col(KRA); kr_thick(cx + ca * r * 0.22, cy + sa * r * 0.22, cx + ca * r * 0.95, cy + sa * r * 0.95, 2.5 * sc);
);
function kr_scap(cx, cy, r, a, cap) local(ca, sa, k, pc) (
  ca = sin(a); sa = -cos(a);
  ui_cola(0x000000, 0.5); gfx_circle(cx + 1.5 * sc, cy + 2.5 * sc, r, 1, 1);
  ui_col(0x121212); gfx_circle(cx, cy, r, 1, 1);
  ui_cola(0xFFFFFF, 0.12); gfx_circle(cx, cy, r, 0, 1);
  k = r * 0.74;
  ui_col(cap); gfx_circle(cx, cy, k, 1, 1);
  ui_col(ui_mix(cap, 0x000000, 0.35)); gfx_circle(cx, cy, k, 0, 1);
  pc = (cap & 255) + ((cap >> 8) & 255) + ((cap >> 16) & 255) > 380 ? KRPT : 0xF4F0E6;
  ui_col(pc); kr_thick(cx + ca * k * 0.2, cy + sa * k * 0.2, cx + ca * r * 0.94, cy + sa * r * 0.94, 2.6 * sc);
);
krv_small = 0; krv_lg = 0; krv_step = 0; krv_cap = 0;
function kr_kn(v, lo, hi) ( krv_lg ? log(max(v, lo) / lo) / log(hi / lo) : (v - lo) / (hi - lo); );
function kr_kv(n, lo, hi) ( krv_lg ? lo * exp(n * log(hi / lo)) : lo + n * (hi - lo); );
function kr_knob(cx, cy, r, idx, lo, hi, def) local(v, n, dx, dy, hov, did) (
  did = 1700 + idx; v = slider(idx);
  dx = mouse_x - cx * sc; dy = mouse_y - cy * sc; hov = dx * dx + dy * dy <= sqr((r + 5) * sc);
  pressed && hov && drag_id == 0 ? (
    time_precise() - krv_lt < 0.35 && krv_lid == idx ? ( slider(idx) = def; ui_auto(idx); drag_id = -1; krv_lt = 0; ) : (
      drag_id = did; krv_y = mouse_y; krv_lt = time_precise(); krv_lid = idx; krv_n = kr_kn(v, lo, hi);
    );
  );
  drag_id == did ? (
    krv_n = min(max(krv_n + (krv_y - mouse_y) / (((mouse_cap & 8) ? 1200 : (krv_step ? 160 : 240)) * sc), 0), 1); krv_y = mouse_y;
    v = kr_kv(krv_n, lo, hi); krv_step ? v = floor(v + 0.5);
    slider(idx) != v ? ( slider(idx) = v; ui_auto(idx); );
  );
  hov && mouse_wheel != 0 && drag_id == 0 ? (
    krv_step ? slider(idx) = min(max(floor(v + 0.5) + sign(mouse_wheel), lo), hi) : (
      n = min(max(kr_kn(v, lo, hi) + mouse_wheel / 120 * ((mouse_cap & 8) ? 0.005 : 0.025), 0), 1);
      slider(idx) = kr_kv(n, lo, hi);
    );
    ui_auto(idx); mouse_wheel = 0;
  );
  v = slider(idx); n = min(max(kr_kn(v, lo, hi), 0), 1);
  krv_small ? kr_scap(cx * sc, cy * sc, r * sc, -0.75 * $pi + n * 1.5 * $pi, krv_cap ? krv_cap : KRC) : kr_bake(cx * sc, cy * sc, r * sc, -0.75 * $pi + n * 1.5 * $pi);
  hov || drag_id == did ? ( ui_cola(0xFFFFFF, 0.07); gfx_circle(cx * sc, cy * sc, (r + 2) * sc, 1, 1); );
  hov || drag_id == did;
);
function kr_name(cx, cy, act, name, val) ( gfx_setfont(14); ui_col(act ? KRA : KRI); kr_ctxt(cx, cy, act ? val : name); );
// a small knob with a tick ring (or n step ticks) and its name; the value while touched
function kr_small(cx, cy, r, idx, lo, hi, def, nt, name, val) local(i, t, act) (
  ui_cola(KRI, 0.85); i = 0;
  loop(nt, t = -0.75 * $pi + i / (nt - 1) * 1.5 * $pi;
    gfx_line((cx + sin(t) * (r + 4)) * sc, (cy - cos(t) * (r + 4)) * sc, (cx + sin(t) * (r + 8)) * sc, (cy - cos(t) * (r + 8)) * sc, 1); i += 1; );
  krv_small = 1; act = kr_knob(cx, cy, r, idx, lo, hi, def); krv_small = 0;
  strlen(name) ? ( gfx_setfont(11); ui_col(act ? KRA : KRI); kr_ctxt(cx, cy + r + 15, act ? val : name); );
  act;
);
function kr_cell(x, y, w, h, lit, zone) local(c) (
  c = zone == 2 ? KRRED : zone == 1 ? KRAMB : KRCN;
  lit ? ( ui_cola(c, 0.16); kr_r(x - 0.5, y - 1, w + 1, h + 2); ui_col(c); kr_r(x, y, w, h); ui_cola(0xFFFFFF, 0.22); kr_r(x, y, w, 1.5); )
      : ( ui_col(KRGH); kr_r(x, y, w, h); ui_cola(0x000000, 0.25); kr_r(x, y + h - 1, w, 1); );
);
function kr_hbar(x, y, w, h, v) local(n, i, sw, d) (
  n = 32; sw = w / n; i = 0;
  loop(n, d = -48 + (i + 0.5) / n * 48; kr_cell(x + i * sw + 0.5, y, sw - 2, h, v >= d, d > -3 ? 2 : d > -12 ? 1 : 0); i += 1; );
);
function kr_grbar(x, y, w, h, gr) local(n, i, sw, d) (                     // gain reduction: 0 .. -24 dB, from the left
  n = 24; sw = w / n; i = 0;
  loop(n, d = -(i + 0.5); kr_cell(x + i * sw + 0.5, y, sw - 2, h, gr <= d, 1); i += 1; );
);
function kr_window(x, y, w, h) ( ui_col(0x040404); kr_r(x, y, w, h); ui_col(KRW); kr_r(x + 1, y + 1, w - 2, h - 2); );
function kr_db(v) ( sprintf(#krd, "%+.1f dB", v); #krd; );
function kr_tog(idx) ( slider(idx) = slider(idx) >= 0.5 ? 0 : 1; ui_auto(idx); );
function kr_hz(f) ( f >= 1000 ? sprintf(#krh, "%.1f kHz", f / 1000) : f >= 10 ? sprintf(#krh, "%d Hz", floor(f + 0.5)) : sprintf(#krh, "%.1f Hz", f); #krh; );
function kr_lamp(cx, cy, c, k) (
  ui_col(ui_mix(KRF, 0x000000, 0.3)); gfx_circle(cx * sc, cy * sc, 5 * sc, 1, 1);
  k > 0.02 ? ( ui_cola(c, 0.25 * k); gfx_circle(cx * sc, cy * sc, 8 * sc, 1, 1); ui_cola(c, min(k * 1.2, 1)); gfx_circle(cx * sc, cy * sc, 5 * sc, 1, 1); );
  ui_cola(0xFFFFFF, 0.08 + 0.27 * k); gfx_circle((cx - 1.5) * sc, (cy - 1.5) * sc, 1.6 * sc, 1, 1);
);
function kr_panel(y0, h) (
  ui_col(KRP); kr_r(12, y0, 1056, h);
  ui_cola(0xFFFFFF, 0.07); kr_r(12, y0, 1056, 1.5); kr_r(12, y0, 1.5, h);
  ui_cola(0x000000, 0.35); kr_r(12, y0 + h - 1.5, 1056, 1.5); kr_r(1066.5, y0, 1.5, h);
  ui_col(KRHD); kr_r(13.5, y0 + 1.5, 1053, 42);
  ui_col(KRA); kr_r(13.5, y0 + 43.5, 1053, 2);
);
// a vertical fader (drag, SHIFT = fine, wheel, double-click = default)
function kr_fader(x, y, w, h, idx, lo, hi, def, lit) local(v, n, hov, did, ty) (
  did = 1800 + idx; v = slider(idx);
  hov = mouse_x >= (x - 2) * sc && mouse_x < (x + w + 2) * sc && mouse_y >= (y - 6) * sc && mouse_y < (y + h + 6) * sc;
  pressed && hov && drag_id == 0 ? (
    time_precise() - kf_lt < 0.35 && kf_lid == idx ? ( slider(idx) = def; ui_auto(idx); drag_id = -1; kf_lt = 0; ) : (
      drag_id = did; kf_y = mouse_y; kf_lt = time_precise(); kf_lid = idx; kf_n = (v - lo) / (hi - lo);
    );
  );
  drag_id == did ? (
    kf_n = min(max(kf_n + (kf_y - mouse_y) / (((mouse_cap & 8) ? 600 : h) * sc), 0), 1); kf_y = mouse_y;
    slider(idx) = lo + kf_n * (hi - lo); ui_auto(idx);
  );
  hov && mouse_wheel != 0 && drag_id == 0 ? ( slider(idx) = min(max(v + mouse_wheel / 120 * (hi - lo) * 0.04, lo), hi); ui_auto(idx); mouse_wheel = 0; );
  n = (slider(idx) - lo) / (hi - lo);
  ui_col(0x050505); kr_r(x + w * 0.5 - 2.5, y, 5, h); ui_col(ui_mix(KRF, 0x000000, 0.5)); kr_r(x + w * 0.5 - 1.5, y + 1, 3, h - 2);
  ty = y + (1 - n) * h;
  ui_cola(KRA, 0.35); kr_r(x + w * 0.5 - 1.5, ty, 3, y + h - ty);
  ui_cola(0x000000, 0.5); kr_r(x + 1, ty - 6 + 2, w, 13);
  ui_col(0x141414); kr_r(x, ty - 7, w, 14); ui_col(ui_mix(0x2A2A2A, KRA, 0.12 + 0.6 * lit)); kr_r(x + 1, ty - 6, w - 2, 12);
  ui_col(lit > 0.05 ? ui_mix(KRA, 0xFFFFFF, 0.3 * lit) : KRA); kr_r(x + 2, ty - 1, w - 4, 2);
  hov || drag_id == did;
);
// move module m one place earlier (-1) or later (+1) in the chain
function bv_move(m, dir) local(o, p, a, b, c, d, e, t) (
  o = floor(min(max(slider1, 0), 119) + 0.5);
  a = PERM[o * 5]; b = PERM[o * 5 + 1]; c = PERM[o * 5 + 2]; d = PERM[o * 5 + 3]; e = PERM[o * 5 + 4];
  p = a == m ? 0 : b == m ? 1 : c == m ? 2 : d == m ? 3 : 4;
  dir < 0 ? p -= 1;                                        // swap places p and p + 1
  p == 0 ? ( t = a; a = b; b = t; ) : p == 1 ? ( t = b; b = c; c = t; ) : p == 2 ? ( t = c; c = d; d = t; ) : p == 3 ? ( t = d; d = e; e = t; );
  slider1 = bb_ord(a, b, c, d, e); ui_auto(1);
);
function bv_pos(m) local(o, k) ( o = floor(min(max(slider1, 0), 119) + 0.5); k = 0; loop(5, PERM[o * 5 + k] == m ? bv_p = k; k += 1; ); bv_p; );
// the patch bay's names: sources (FROM) and destinations (TO), in the boxes and in the menus; the cords' colours
function bv_srn(i) ( i == 0 ? "OFF" : i == 1 ? "INPUT ENV" : i == 2 ? "LP ENV" : i == 3 ? "RING ENV" : i == 4 ? "PH ENV" : i == 5 ? "MURF ENV"
  : i == 6 ? "SAT ENV" : i == 7 ? "RING LFO" : i == 8 ? "PH LFO" : "NOISE"; );
function bv_srm(i) ( i == 0 ? "Off" : i == 1 ? "Input envelope" : i == 2 ? "Lowpass envelope" : i == 3 ? "Ring envelope" : i == 4 ? "Phaser envelope"
  : i == 5 ? "MuRF envelope" : i == 6 ? "Saturator envelope" : i == 7 ? "Ring LFO" : i == 8 ? "Phaser LFO" : "Noise"; );
function bv_dsn(i) ( i == 0 ? "LP CUTOFF" : i == 1 ? "LP RES" : i == 2 ? "LP MIX" : i == 3 ? "RING FREQ" : i == 4 ? "RING MIX" : i == 5 ? "PH SWEEP"
  : i == 6 ? "PH RES" : i == 7 ? "PH MIX" : i == 8 ? "MURF RATE" : i == 9 ? "MURF MIX" : i == 10 ? "SAT DRIVE" : "SAT NOISE"; );
function bv_dsm(i) ( i == 0 ? "Lowpass cutoff" : i == 1 ? "Lowpass resonance" : i == 2 ? "Lowpass mix" : i == 3 ? "Ring carrier" : i == 4 ? "Ring mix"
  : i == 5 ? "Phaser sweep" : i == 6 ? "Phaser resonance" : i == 7 ? "Phaser mix" : i == 8 ? "MuRF rate" : i == 9 ? "MuRF mix"
  : i == 10 ? "Saturator drive" : "Saturator noise"; );
function bv_dmod(d) ( d <= 2 ? 0 : d <= 4 ? 1 : d <= 7 ? 2 : d <= 9 ? 3 : 4; );          // the module a destination belongs to
function bv_cord(k) ( k == 0 ? 0xE8533A : k == 1 ? 0xF2B530 : 0x4E9BE8; );
// a dark recessed display box with light text (hover: the accent colour); returns 1 when clicked
function bv_box(x, y, w, h, txt) local(hov) (
  hov = kr_hov(x, y, w, h);
  ui_col(0x050505); kr_r(x, y, w, h); ui_col(KRW); kr_r(x + 1, y + 1, w - 2, h - 2);
  ui_cola(0x000000, 0.45); kr_r(x + 1, y + 1, w - 2, 2); kr_r(x + 1, y + 1, 2, h - 2);
  ui_cola(0xFFFFFF, 0.06); kr_r(x + 1, y + h - 2, w - 2, 1);
  gfx_setfont(12); ui_col(hov ? KRA : KRI); kr_ctxt(x + w * 0.5, y + h * 0.5 + 0.5, txt);
  hov && pressed && drag_id == 0 ? ( drag_id = -1; 1 ) : 0;
);
// a module's section: name, sub-title, its place in the chain (with the arrow keys), IN; its CV IN jacks at (cvx, y + 40):
// one per cable, lit in the cable's colour when it is patched into this module
function bv_mod(m, x, y, w, name, sub, inn, cvx) local(p, k, c, on) (
  kr_sect(x, y, w, 206, name);
  gfx_setfont(13); ui_col(KRI2); kr_txt(x + 9, y + 22, sub);
  p = bv_pos(m);
  ui_col(KRA); gfx_circle((x + w - 152) * sc, (y + 18) * sc, 9 * sc, 1, 1);
  gfx_setfont(11); ui_col(KRKT); sprintf(#bvp, "%d", p + 1); kr_ctxt(x + w - 152, y + 18.5, #bvp);
  kr_kc = LK_WHT; p > 0 ? ( kr_key(x + w - 136, y + 7, 28, 22, "◄", 0) ? bv_move(m, -1); ) : kr_dim(x + w - 136, y + 7, 28, 22, "◄");
  kr_kc = LK_WHT; p < 4 ? ( kr_key(x + w - 104, y + 7, 28, 22, "►", 0) ? bv_move(m, 1); ) : kr_dim(x + w - 104, y + 7, 28, 22, "►");
  kr_kc = LK_AMB; kr_key(x + w - 52, y + 7, 44, 22, "IN", slider(inn) >= 0.5) ? kr_tog(inn);
  ui_col(0x050505); kr_r(cvx + 22, y + 40, 96, 22); ui_col(KRW); kr_r(cvx + 23, y + 41, 94, 20);
  gfx_setfont(10); ui_col(KRI2); kr_ctxt(cvx + 46, y + 51.5, "CV IN");
  k = 0;
  loop(3,
    on = slider(51 + 4 * k) >= 0.5 && bv_dmod(floor(slider(52 + 4 * k) + 0.5)) == m; c = bv_cord(k);
    ui_col(0x000000); gfx_circle((cvx + 78 + k * 14) * sc, (y + 51) * sc, 5 * sc, 1, 1);
    on ? ( ui_cola(c, 0.3); gfx_circle((cvx + 78 + k * 14) * sc, (y + 51) * sc, 6.5 * sc, 1, 1);
           ui_col(c); gfx_circle((cvx + 78 + k * 14) * sc, (y + 51) * sc, 4 * sc, 1, 1); )
       : ( ui_col(ui_mix(KRW, 0xFFFFFF, 0.08)); gfx_circle((cvx + 78 + k * 14) * sc, (y + 51) * sc, 3 * sc, 1, 1); );
    k += 1;
  );
);
// a module switched off: greyed slightly, its IN key redrawn on top
function bv_off(x, y, w, inn) (
  slider(inn) < 0.5 ? ( kr_off(x, y, w, 206); kr_kc = LK_AMB; kr_key(x + w - 52, y + 7, 44, 22, "IN", 0) ? kr_tog(inn); );
);
function bv_pct(v) ( sprintf(#bvq, "%d%%", floor(v + 0.5)); #bvq; );
function bv_sgn(v) ( sprintf(#bvs, "%+.1f", v); #bvs; );

// =====================================================================
// The rack: two panels
// =====================================================================
ui_col(ui_mix(KRP, 0x000000, 0.55)); gfx_rect(0, 0, gfx_w, gfx_h);
kr_panel(8, 268); kr_panel(284, 268); kr_panel(560, 148);
// ---------- upper header ----------
ui_col(KRA); gfx_circle(40 * sc, 30 * sc, 14 * sc, 1, 1);
gfx_setfont(4); ui_col(KRKT); ui_text(40 * sc, 24.5 * sc, "GFX");
gfx_setfont(6); ui_col(KRI); kr_txt(64, 15, "BOB34"); gfx_measurestr("BOB34", bv_tw, bv_th);
bv_tx = 64 + bv_tw / sc + 14;
gfx_setfont(11); ui_col(KRI2); kr_txt(bv_tx, 15, "MODULAR EFFECTS"); kr_txt(bv_tx, 30, "FIVE MODULES · PATCH BAY");
pk_pextra = "Reset all to defaults";
prs_cur_d > 0 ? ( pr_cur = prs_cur_d; prs_cur_d = 0; );     // PRESET parameter: show a preset loaded by the host
k_ = pk_preset(340 * sc, 13 * sc, 290 * sc, 32 * sc, pr_cur, #bb_pm);
k_ > 0 ? prs_dirty = k_;
ui_size_picker(1000 * sc, 20 * sc);
os_key(940, 16, 5, KRI2, 16);                             // OS: 1x / 2x / 4x, each click cycles

bv_r1 = 60; bv_c1 = bv_r1 + 96; bv_c2 = bv_r1 + 160;      // the upper modules and their two small-knob rows
// ---------- LOWPASS ----------
bv_x = 22;
bv_mod(0, bv_x, bv_r1, 340, "LOWPASS", "LADDER + ENVELOPE", 6, bv_x + 214);
kr_lamp(bv_x + 96, bv_r1 + 13, KRA, bba_lpe);
kr_kc = LK_YEL; kr_key(bv_x + 10, bv_r1 + 40, 70, 22, "4-POLE", slider10 >= 0.5) ? kr_tog(10);
kr_kc = LK_YEL; kr_key(bv_x + 84, bv_r1 + 40, 56, 22, "FAST", slider12 >= 0.5) ? kr_tog(12);
krv_lg = 1; kr_scale(bv_x + 62, bv_r1 + 132, 30, 4, 16, "20|||12k", 2);
bv_a = kr_knob(bv_x + 62, bv_r1 + 132, 30, 8, 20, 12000, 800); krv_lg = 0; kr_name(bv_x + 62, bv_r1 + 190, bv_a, "CUTOFF", kr_hz(slider8));
sprintf(#bvv, "%.1f", slider9); kr_small(bv_x + 145, bv_c1, 16, 9, 0, 10, 4, 11, "RES", #bvv);
kr_small(bv_x + 200, bv_c1, 16, 11, -10, 10, 5, 11, "ENV", bv_sgn(slider11));
sprintf(#bvv, "%.1f", slider7); kr_small(bv_x + 255, bv_c1, 16, 7, 0, 10, 2, 11, "DRIVE", #bvv);
kr_small(bv_x + 310, bv_c1, 16, 13, 0, 100, 100, 11, "MIX", bv_pct(slider13));
bv_off(22, bv_r1, 340, 6);
// ---------- RING ----------
bv_x = 368;
bv_mod(1, bv_x, bv_r1, 340, "RING", "RING MODULATOR + LFO", 14, bv_x + 214);
kr_lamp(bv_x + 66, bv_r1 + 13, KRA, 0.5 + 0.5 * rg_sl);
kr_kc = LK_YEL; kr_key(bv_x + 10, bv_r1 + 40, 76, 22, "SQUARE", slider19 >= 0.5) ? kr_tog(19);
krv_lg = 1; kr_scale(bv_x + 62, bv_r1 + 132, 30, 4, 16, "0.6|||4k", 2);
bv_a = kr_knob(bv_x + 62, bv_r1 + 132, 30, 16, 0.6, 4000, 200); kr_name(bv_x + 62, bv_r1 + 190, bv_a, "CARRIER", kr_hz(slider16));
sprintf(#bvv, "%.2f Hz", slider17); kr_small(bv_x + 145, bv_c1, 16, 17, 0.1, 25, 2, 11, "RATE", #bvv); krv_lg = 0;
kr_small(bv_x + 200, bv_c1, 16, 18, 0, 100, 0, 11, "AMOUNT", bv_pct(slider18));
sprintf(#bvv, "%.1f", slider15); kr_small(bv_x + 255, bv_c1, 16, 15, 0, 10, 2, 11, "DRIVE", #bvv);
kr_small(bv_x + 310, bv_c1, 16, 20, 0, 100, 100, 11, "MIX", bv_pct(slider20));
bv_off(368, bv_r1, 340, 14);
// ---------- PHASER ----------
bv_x = 714;
bv_mod(2, bv_x, bv_r1, 344, "PHASER", "6 / 12 STAGES + LFO", 21, bv_x + 218);
kr_lamp(bv_x + 86, bv_r1 + 13, KRA, 0.5 + 0.5 * ph_lfo);
kr_kc = LK_YEL; kr_key(bv_x + 10, bv_r1 + 40, 84, 22, "12-STAGE", slider25 >= 0.5) ? kr_tog(25);
krv_lg = 1; kr_scale(bv_x + 62, bv_r1 + 132, 30, 4, 16, "100|||8k", 2);
bv_a = kr_knob(bv_x + 62, bv_r1 + 132, 30, 23, 100, 8000, 800); kr_name(bv_x + 62, bv_r1 + 190, bv_a, "SWEEP", kr_hz(slider23));
sprintf(#bvv, "%.2f Hz", slider26); kr_small(bv_x + 200, bv_c1, 16, 26, 0.05, 20, 0.5, 11, "RATE", #bvv); krv_lg = 0;
sprintf(#bvv, "%.1f", slider24); kr_small(bv_x + 145, bv_c1, 16, 24, 0, 10, 4, 11, "RES", #bvv);
kr_small(bv_x + 255, bv_c1, 16, 27, 0, 100, 50, 11, "AMOUNT", bv_pct(slider27));
sprintf(#bvv, "%.1f", slider22); kr_small(bv_x + 314, bv_c1, 16, 22, 0, 10, 2, 11, "DRIVE", #bvv);
kr_small(bv_x + 255, bv_c2, 16, 28, 0, 100, 50, 11, "MIX", bv_pct(slider28));
bv_off(714, bv_r1, 344, 21);

// ---------- lower header: the CHAIN and STEREO ----------
ui_col(KRA); gfx_circle(40 * sc, 306 * sc, 14 * sc, 1, 1);
gfx_setfont(4); ui_col(KRKT); ui_text(40 * sc, 300.5 * sc, "GFX");
gfx_setfont(14); ui_col(KRI); kr_txt(64, 293, "BOB34"); gfx_setfont(11); ui_col(KRI2); kr_txt(64, 310, "THE CHAIN");
bv_o = floor(min(max(slider1, 0), 119) + 0.5); bv_k = 0;
loop(5,
  bv_m = PERM[bv_o * 5 + bv_k]; bv_x = 170 + bv_k * 136;
  bv_on = bv_m == 0 ? slider6 : bv_m == 1 ? slider14 : bv_m == 2 ? slider21 : bv_m == 3 ? slider29 : slider44;
  ui_col(0x050505); kr_r(bv_x, 293, 112, 26); ui_col(bv_on >= 0.5 ? ui_mix(KRF, KRA, 0.28) : KRF); kr_r(bv_x + 1, 294, 110, 24);
  gfx_setfont(12); ui_col(bv_on >= 0.5 ? KRI : ui_mix(KRI, KRF, 0.6));
  kr_ctxt(bv_x + 56, 306.5, bv_m == 0 ? "LOWPASS" : bv_m == 1 ? "RING" : bv_m == 2 ? "PHASER" : bv_m == 3 ? "MURF" : "SATURATOR");
  bv_k < 4 ? ( gfx_setfont(11); ui_col(KRA); kr_ctxt(bv_x + 124, 306.5, "►"); );
  bv_k += 1;
);
kr_kc = LK_RED; kr_key(866, 292, 92, 26, "STEREO", slider2 >= 0.5) ? kr_tog(2);

bv_r2 = 336; bv_c2b = bv_r2 + 160;
// ---------- MURF ----------
bv_x = 22;
bv_mod(3, bv_x, bv_r2, 520, "MURF", "RESONANT FILTER ARRAY", 29, bv_x + 374);
kr_kc = LK_YEL; kr_key(bv_x + 290, bv_r2 + 40, 70, 22, slider34 >= 0.5 ? "MID" : "BASS", slider34 >= 0.5) ? kr_tog(34);
// the eight faders, each lit by its filter's envelope
bv_i = 0;
loop(8,
  bv_fx = bv_x + 16 + bv_i * 31;
  kr_fader(bv_fx, bv_r2 + 52, 22, 118, 35 + bv_i, 0, 10, 8, slider29 >= 0.5 ? MFE[bv_i] : 0);
  gfx_setfont(10); ui_col(KRI2); sprintf(#bvv, "%d", bv_i + 1); kr_ctxt(bv_fx + 11, bv_r2 + 190, #bvv);
  bv_i += 1;
);
// the pattern: 16 steps x 8 filters, the current step lit
bv_gx = bv_x + 266; bv_gy = bv_r2 + 74; bv_pat = floor(min(max(slider33, 1), 12) + 0.5);
kr_window(bv_gx - 4, bv_gy - 4, 16 * 9 + 7, 8 * 7 + 7);
bv_s = 0;
loop(16,
  bv_m = MPAT[(bv_pat - 1) * 16 + bv_s]; bv_i = 0;
  loop(8,
    (bv_m & (1 << bv_i)) ? ui_col(bv_s == bba_step && slider29 >= 0.5 ? ui_mix(KRA, 0xFFFFFF, 0.4) : KRA) : ui_col(bv_s == bba_step && slider29 >= 0.5 ? ui_mix(KRGH, 0xFFFFFF, 0.12) : KRGH);
    kr_r(bv_gx + bv_s * 9, bv_gy + (7 - bv_i) * 7, 8, 6);
    bv_i += 1;
  );
  bv_s += 1;
);
krv_step = 1; sprintf(#bvv, "P %d", bv_pat); kr_small(bv_x + 444, bv_r2 + 98, 16, 33, 1, 12, 1, 12, "PATTERN", #bvv); krv_step = 0;
krv_lg = 1; sprintf(#bvv, "%.1f /s", slider31); kr_small(bv_x + 290, bv_c2b, 16, 31, 0.5, 20, 4, 11, "RATE", #bvv); krv_lg = 0;
sprintf(#bvv, "%.1f", slider32); kr_small(bv_x + 350, bv_c2b, 16, 32, 0, 10, 4, 11, "ENVELOPE", #bvv);
sprintf(#bvv, "%.1f", slider30); kr_small(bv_x + 410, bv_c2b, 16, 30, 0, 10, 2, 11, "DRIVE", #bvv);
kr_small(bv_x + 470, bv_c2b, 16, 43, 0, 100, 100, 11, "MIX", bv_pct(slider43));
bv_off(22, bv_r2, 520, 29);
// ---------- SATURATOR ----------
bv_x = 548;
bv_mod(4, bv_x, bv_r2, 340, "SATURATOR", "DRIVE + ENVELOPE + NOISE", 44, bv_x + 214);
kr_lamp(bv_x + 112, bv_r2 + 13, KRA, bba_ste);
kr_kc = LK_YEL; kr_key(bv_x + 10, bv_r2 + 40, 92, 22, "NOISE HP", slider49 >= 0.5) ? kr_tog(49);
kr_scale(bv_x + 62, bv_r2 + 132, 30, 6, 16, "0|2|4|6|8|10", 0);
bv_a = kr_knob(bv_x + 62, bv_r2 + 132, 30, 45, 0, 10, 3); sprintf(#bvv, "%.1f", slider45); kr_name(bv_x + 62, bv_r2 + 190, bv_a, "DRIVE", #bvv);
kr_small(bv_x + 145, bv_r2 + 96, 16, 46, -10, 10, 0, 11, "ENV", bv_sgn(slider46));
sprintf(#bvv, "%.1f", slider47); kr_small(bv_x + 200, bv_r2 + 96, 16, 47, 0, 10, 0, 11, "NOISE", #bvv);
krv_lg = 1; kr_small(bv_x + 255, bv_r2 + 96, 16, 48, 100, 12000, 3000, 11, "TONE", kr_hz(slider48)); krv_lg = 0;
kr_small(bv_x + 310, bv_r2 + 96, 16, 50, 0, 100, 100, 11, "MIX", bv_pct(slider50));
bv_off(548, bv_r2, 340, 44);
// ---------- OUTPUT ----------
bv_x = 894;
kr_sect(bv_x, bv_r2, 164, 206, "OUTPUT");
kr_scale(bv_x + 52, bv_r2 + 88, 26, 3, 15, "-12|0|+12", 4);
bv_a = kr_knob(bv_x + 52, bv_r2 + 88, 26, 3, -12, 12, 0); kr_name(bv_x + 52, bv_r2 + 136, bv_a, "LEVEL", kr_db(slider3));
sprintf(#bvv, "%d%%", floor(slider4 + 0.5)); kr_small(bv_x + 52, bv_r2 + 168, 14, 4, 0, 100, 50, 11, "DRIFT", #bvv);
kr_window(bv_x + 104, bv_r2 + 12, 52, 186);
function kr_fall(v, pk) ( pk = pk > 0.0000001 ? 20 * log10(pk) : -90; pk > v ? pk : max(v - 0.6, -90); );
bv_ml = kr_fall(bv_ml, bba_ol_d); bv_mr = kr_fall(bv_mr, bba_or_d); bba_ol_d = bba_or_d = 0;
bv_i = 0;
loop(24,
  bv_d = -48 + (bv_i + 0.5) / 24 * 48; bv_cy = bv_r2 + 190 - (bv_i + 1) * 7.4;
  kr_cell(bv_x + 111, bv_cy, 16, 5.4, bv_ml >= bv_d, bv_d > -3 ? 2 : bv_d > -12 ? 1 : 0);
  kr_cell(bv_x + 133, bv_cy, 16, 5.4, bv_mr >= bv_d, bv_d > -3 ? 2 : bv_d > -12 ? 1 : 0);
  bv_i += 1;
);

// ---------- the PATCH BAY ----------
ui_col(KRA); gfx_circle(40 * sc, 582 * sc, 14 * sc, 1, 1);
gfx_setfont(4); ui_col(KRKT); ui_text(40 * sc, 576.5 * sc, "GFX");
gfx_setfont(14); ui_col(KRI); kr_txt(64, 569, "BOB34"); gfx_setfont(11); ui_col(KRI2); kr_txt(64, 586, "PATCH BAY");
gfx_setfont(11); ui_col(KRI2); kr_txt(190, 577, "THREE CABLES: ANY MODULE'S ENVELOPE, AN LFO OR THE NOISE TO ANY OTHER MODULE");
bv_k = 0;
loop(3,
  bv_x = 22 + bv_k * 348; bv_y = 614; bv_n = 51 + 4 * bv_k;
  bv_src = floor(slider(bv_n) + 0.5); bv_dst = floor(slider(bv_n + 1) + 0.5); bv_c = bv_cord(bv_k);
  kr_sect(bv_x, bv_y, 340, 84, "");
  // TO, AMOUNT, LAG
  gfx_setfont(11); ui_col(KRI2); kr_rtxt(bv_x + 92, bv_y + 60, "TO");
  bv_box(bv_x + 98, bv_y + 46, 120, 28, bv_dsn(bv_dst)) ? (
    strcpy(#bvm, ""); bv_i = 0;
    loop(12, bv_i == bv_dst ? strcat(#bvm, "!"); strcat(#bvm, bv_dsm(bv_i)); bv_i < 11 ? strcat(#bvm, "|"); bv_i += 1; );
    gfx_x = mouse_x; gfx_y = mouse_y; bv_pick = gfx_showmenu(#bvm);
    bv_pick > 0 ? ( slider(bv_n + 1) = bv_pick - 1; ui_auto(bv_n + 1); );
  );
  kr_small(bv_x + 258, bv_y + 34, 15, bv_n + 2, -100, 100, 50, 5, "AMOUNT", bv_pct(slider(bv_n + 2)));
  kr_small(bv_x + 312, bv_y + 34, 15, bv_n + 3, 0, 100, 0, 5, "LAG", bv_pct(slider(bv_n + 3)));
  // an unpatched cable: greyed, its jack and FROM box drawn on top
  bv_src == 0 ? ( ui_cola(KRF, 0.55); kr_r(bv_x + 44, bv_y + 42, 294, 41); );
  // the jack: the cord's colour, glowing with what the cable carries
  bv_g = bv_src > 0 ? min(abs(CBV[bv_k]) * 1.6, 1) : 0;
  ui_cola(0x000000, 0.45); gfx_circle((bv_x + 25.5) * sc, (bv_y + 44.5) * sc, 14 * sc, 1, 1);
  ui_col(0x0A0A0A); gfx_circle((bv_x + 24) * sc, (bv_y + 42) * sc, 14 * sc, 1, 1);
  bv_src > 0 ? (
    ui_cola(bv_c, 0.18 + 0.25 * bv_g); gfx_circle((bv_x + 24) * sc, (bv_y + 42) * sc, 17 * sc, 1, 1);
    ui_col(ui_mix(bv_c, 0x000000, 0.35 - 0.35 * bv_g)); gfx_circle((bv_x + 24) * sc, (bv_y + 42) * sc, 11.5 * sc, 1, 1);
    ui_cola(0xFFF6E0, 0.35 * bv_g); gfx_circle((bv_x + 24) * sc, (bv_y + 42) * sc, 6 * sc, 1, 1);
    gfx_setfont(14); ui_col(0x16110C);
  ) : (
    ui_col(ui_mix(bv_c, KRF, 0.78)); gfx_circle((bv_x + 24) * sc, (bv_y + 42) * sc, 11.5 * sc, 1, 1);
    gfx_setfont(14); ui_col(ui_mix(KRI, KRF, 0.25));
  );
  kr_ctxt(bv_x + 24, bv_y + 42.5, bv_k == 0 ? "A" : bv_k == 1 ? "B" : "C");
  // FROM
  gfx_setfont(11); ui_col(KRI2); kr_rtxt(bv_x + 92, bv_y + 24, "FROM");
  bv_box(bv_x + 98, bv_y + 10, 120, 28, bv_srn(bv_src)) ? (
    strcpy(#bvm, ""); bv_i = 0;
    loop(10, bv_i == bv_src ? strcat(#bvm, "!"); strcat(#bvm, bv_srm(bv_i)); bv_i < 9 ? strcat(#bvm, "|"); bv_i += 1; );
    gfx_x = mouse_x; gfx_y = mouse_y; bv_pick = gfx_showmenu(#bvm);
    bv_pick > 0 ? ( slider(bv_n) = bv_pick - 1; ui_auto(bv_n); );
  );
  bv_k += 1;
);

pk_end();
ui_end();
