desc:GFX Space //tags: reverb room chamber hall plate spring algorithmic //author: JGWizrad / Garden FX (GFX) // // Requires gfx-drift, gfx-input, gfx-output, gfx-ui, gfx-panel, gfx-dyn and gfx-faceplate (.jsfx-inc) in the same folder. // // Algorithmic reverb: // ROOM / CHAMBER / HALL - early reflections + an 8-line feedback delay network, sized per type // PLATE - no early reflections, dense from the start, bright // SPRING - a separate spring-tank model: dispersion (the "boing" chirp) inside a // feedback loop, two springs for left / right // PRE-DELAY, DECAY (seconds to fade 60 dB), SIZE, DAMPING (highs die away sooner), // MOD (slow chorusing of the tail), EARLY (reflection level), DIFFUSION (how smeared the onset is), // LO CUT / HI CUT on the reverb only, DUCK (reverb dips while you play), WIDTH, FREEZE (infinite). // Drift: WANDER - the tail's modulation wanders randomly. // IN / OUT: TRIM (with the GUARD key), SAT and the OUT stage (OFF / CLIP / LIM) sit on the panel. import gfx-drift.jsfx-inc import gfx-input.jsfx-inc import gfx-output.jsfx-inc import gfx-ui.jsfx-inc import gfx-panel.jsfx-inc import gfx-dyn.jsfx-inc import gfx-faceplate.jsfx-inc import gfx-instrument.jsfx-inc slider1:2<0,4,1{Room,Chamber,Hall,Plate,Spring}>-Type slider2:20<0,250,1>-Pre-delay (ms) slider3:2.4<0.2,20,0.01>-Decay (s) slider4:100<50,150,1>-Size (%) slider5:50<0,100,1>-Damping (%) slider6:30<0,100,1>-Modulation (%) slider7:50<0,100,1>-Early reflections (%) slider8:75<0,100,1>-Diffusion (%) slider9:120<20,1000,1>-Lo cut (Hz) slider10:9000<1000,20000,10>-Hi cut (Hz) slider11:0<0,100,1>-Duck (%) slider12:100<0,100,1>-Width (%) slider13:0<0,1,1{Off,On}>-Freeze slider14:20<0,100,1>-Wander (%) slider15:0.2<0.02,2,0.01>-Drift speed (Hz) slider16:30<0,100,1>-Mix (%) slider17:0<-24,12,0.1>-Output (dB) slider18:0<0,100,1>-Band sat (%) slider19:30<0,100,1>-Bias (%) slider20:120<40,400,1>-Sat low band (Hz) slider21:6000<2000,16000,10>-Sat high band (Hz) slider22:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider23:1<0,2,1{Off,Clip,Limit}>-Output stage slider24:0<-24,12,0.1>-Input trim (dB) slider25:0<0,1,1{Off,On}>-Input guard slider26:0<0,3,1{Off,-3 dB,-6 dB,-9 dB}>-Vintage EQ (500 Hz cut) slider27:50<0,100,1>-Drift (%) in_pin:left input in_pin:right input out_pin:left output out_pin:right output @init #ui_man = "gfx-space"; // the ? pop-up links to this manual // ---------- memory: every delay line gets its own block ---------- SR48 = srate / 48000; BLK = ceil(0.25 * srate) + 16; // up to 250 ms per line pre_b = 100000; pre_len = 2 * ceil(0.26 * srate) + 16; // interleaved stereo er_b = pre_b + pre_len; er_len = 2 * ceil(0.12 * srate) + 16; ap_b = er_b + er_len; // 4 input diffusers, stereo-interleaved rings of 4096 ln_b = ap_b + 4 * 8192; // 8 network lines sp_b = ln_b + 8 * BLK; // 2 spring delays memset(pre_b, 0, sp_b + 2 * BLK - pre_b); pre_w = 0; er_w = 0; lw = 400; lz = 420; lph = 440; lrt = 460; llen = 480; lg_a = 500; lg_b = 520; apw = 540; apz = 560; memset(400, 0, 200); // line lengths (ms at SIZE 100% in a hall) - mutually prime-ish bl = 600; bl[0] = 31.7; bl[1] = 37.1; bl[2] = 41.3; bl[3] = 47.9; bl[4] = 53.3; bl[5] = 61.1; bl[6] = 70.7; bl[7] = 79.3; i = 0; loop(8, lrt[i] = 0.31 + 0.11 * i; lph[i] = rand(1); i += 1; ); // modulation rates (Hz) // input diffuser lengths (samples at 48 kHz) apl = 620; apl[0] = 142; apl[1] = 107; apl[2] = 379; apl[3] = 277; // early reflections: 8 taps per side (ms, gain); patterns scaled per type ert = 640; erg = 660; ert[0] = 4.3; ert[1] = 7.9; ert[2] = 11.2; ert[3] = 15.8; ert[4] = 21.4; ert[5] = 27.9; ert[6] = 34.6; ert[7] = 43.1; erg[0] = 0.85; erg[1] = -0.7; erg[2] = 0.62; erg[3] = 0.5; erg[4] = -0.42; erg[5] = 0.34; erg[6] = -0.27; erg[7] = 0.2; function tanh_(x) local(e) ( x = min(max(x, -20), 20); e = exp(2 * x); (e - 1) / (e + 1) ); // spring: first-order allpass chain (dispersion) function sp_chain(x) instance(z0, z1, z2, z3, z4, z5, z6, z7, z8, z9, z10, z11) local(y) ( y = -0.62 * x + z0; z0 = x + 0.62 * y; x = y; y = -0.62 * x + z1; z1 = x + 0.62 * y; x = y; y = -0.62 * x + z2; z2 = x + 0.62 * y; x = y; y = -0.62 * x + z3; z3 = x + 0.62 * y; x = y; y = -0.62 * x + z4; z4 = x + 0.62 * y; x = y; y = -0.62 * x + z5; z5 = x + 0.62 * y; x = y; y = -0.62 * x + z6; z6 = x + 0.62 * y; x = y; y = -0.62 * x + z7; z7 = x + 0.62 * y; x = y; y = -0.62 * x + z8; z8 = x + 0.62 * y; x = y; y = -0.62 * x + z9; z9 = x + 0.62 * y; x = y; y = -0.62 * x + z10; z10 = x + 0.62 * y; x = y; y = -0.62 * x + z11; z11 = x + 0.62 * y; y; ); // ---------- vintage wet EQ: HPF 140 Hz, 500 Hz cut, LPF 5 kHz (the classic studio reverb-send trick) ---------- // vt_set(m): m = 0 off, 1/2/3 = -3/-6/-9 dB at 500 Hz. Shared coefficients, per-channel state. function vt_bq(t, f, q, gdb) local(w, cw, al, A, a0) ( w = 2 * $pi * min(f, srate * 0.45) / srate; cw = cos(w); al = sin(w) / (2 * q); t == 0 ? ( // high-pass a0 = 1 + al; vtp[0] = (1 + cw) * 0.5 / a0; vtp[1] = -(1 + cw) / a0; vtp[2] = vtp[0]; ) : t == 1 ? ( // low-pass a0 = 1 + al; vtp[0] = (1 - cw) * 0.5 / a0; vtp[1] = (1 - cw) / a0; vtp[2] = vtp[0]; ) : ( // peaking A = 10 ^ (gdb / 40); a0 = 1 + al / A; vtp[0] = (1 + al * A) / a0; vtp[1] = -2 * cw / a0; vtp[2] = (1 - al * A) / a0; ); vtp[3] = -2 * cw / a0; vtp[4] = (1 - (t == 2 ? al / A : al)) / a0; ); VTC = 700; // 15 coefficients function vt_set(m) ( vt_on = m > 0; vtp = VTC; vt_bq(0, 140, 0.7071, 0); vtp = VTC + 5; vt_bq(2, 500, 0.9, -3 * m); vtp = VTC + 10; vt_bq(1, 5000, 0.7071, 0); ); function vt_proc(x) instance(a1, a2, b1, b2, c1, c2) local(y) ( y = VTC[0] * x + a1; a1 = VTC[1] * x - VTC[3] * y + a2; a2 = VTC[2] * x - VTC[4] * y; x = y; y = VTC[5] * x + b1; b1 = VTC[6] * x - VTC[8] * y + b2; b2 = VTC[7] * x - VTC[9] * y; x = y; y = VTC[10] * x + c1; c1 = VTC[11] * x - VTC[13] * y + c2; c2 = VTC[12] * x - VTC[14] * y; y; ); function update() local(i, sz, L, gl, gh, a) ( rtype = slider1; pre_s = slider2 * 0.001 * srate; t60 = slider3 * 1.11; // calibrated: the measured fade matches DECAY sz = slider4 / 100 * (rtype == 0 ? 0.36 : rtype == 1 ? 0.6 : rtype == 2 ? 1 : rtype == 3 ? 0.55 : 1); damp = slider5 / 100; modd = slider6 / 100 * (rtype == 3 ? 0.5 : 1); erl = rtype >= 3 ? 0 : slider7 / 100 * (rtype == 0 ? 1 : rtype == 1 ? 0.8 : 0.6); er_sc = rtype == 0 ? 0.55 : rtype == 1 ? 0.9 : 1.5; diff = slider8 / 100 * (rtype == 3 ? 1 : 0.85); frz = slider13; i = 0; loop(8, L = min(bl[i] * sz * 0.001 * srate, BLK - 64); llen[i] = L; // two-band loss per pass: lows fade over DECAY, highs sooner (DAMPING) gl = frz ? 1 : 10 ^ (-3 * L / (t60 * srate)); gh = frz ? 1 : 10 ^ (-3 * L / (t60 * (1 - 0.9 * damp) * srate * (rtype == 3 ? 1.4 : 1))); a = (gl - gh) / (gl + gh); lg_a[i] = a; lg_b[i] = gl * (1 - a); i += 1; ); // spring tank sp_lenL = min(0.034 * slider4 / 100 * srate, BLK - 64); sp_lenR = min(0.041 * slider4 / 100 * srate, BLK - 64); sp_g = frz ? 0.999 : 10 ^ (-3 * 0.037 / max(t60, 0.2)); sp_lp = 1 - exp(-2 * $pi * (5200 - 2600 * damp) / srate); lo_c = 1 - exp(-2 * $pi * slider9 / srate); hi_c = 1 - exp(-2 * $pi * min(slider10, srate * 0.45) / srate); duck = slider11 / 100; wid = slider12 / 100; drift_macro(slider27); // DRIFT dial: 50% = the wander setting, 100% = twice, 0 = none wan = slider14 / 100 * dm_a; dspd = slider15 * dm_s; mix_t = slider16 / 100; out_t = 10 ^ (slider17 / 20); in_setup(slider24, slider25); slider26 != vt_m ? ( vt_m = slider26; vt_set(vt_m); ); ost_setup(slider18 / 100, slider19 / 100, slider20, slider21, slider22, slider23); ); // ---------- presets (leave drift speed, output and the I/O rows alone) ---------- function setp(t, pd, dc, sz, dm, md, erp, df, lc, hc, dk, mx) ( slider1 = t; slider2 = pd; slider3 = dc; slider4 = sz; slider5 = dm; slider6 = md; slider7 = erp; slider8 = df; slider9 = lc; slider10 = hc; slider11 = dk; slider16 = mx; slider13 = 0; ); function load_preset(p) ( p == 1 ? setp(0, 5, 0.6, 90, 45, 15, 70, 70, 150, 9000, 0, 25) : // Small Room p == 2 ? setp(0, 10, 1.1, 120, 55, 20, 60, 75, 120, 7000, 0, 25) : // Live Room p == 3 ? setp(1, 15, 1.6, 100, 50, 25, 55, 80, 120, 8000, 0, 25) : // Vocal Chamber p == 4 ? setp(2, 25, 2.4, 100, 50, 30, 45, 75, 120, 9000, 0, 30) : // Concert Hall p == 5 ? setp(2, 40, 5.5, 140, 60, 45, 35, 85, 200, 7000, 30, 35) : // Cathedral (ducked) p == 6 ? setp(3, 10, 2.0, 100, 30, 25, 0, 95, 150, 12000, 0, 25) : // Bright Plate p == 7 ? setp(3, 0, 1.2, 80, 45, 20, 0, 90, 200, 10000, 0, 20) : // Snare Plate p == 8 ? setp(4, 0, 2.0, 100, 50, 10, 0, 50, 300, 6000, 0, 30) : // Surf Spring p == 9 ? setp(4, 10, 3.0, 120, 40, 15, 0, 50, 200, 5000, 0, 25) : // Dub Spring p == 10 ? setp(2, 60, 14, 150, 40, 60, 20, 90, 250, 8000, 0, 40); // Ambient Wash p >= 1 && p <= 10 ? slider_automate(36863); // sliders 1-13, 16 p == 11 ? ( // Reset all to defaults setp(2, 20, 2.4, 100, 50, 30, 50, 75, 120, 9000, 0, 30); slider12 = 100; slider14 = 20; slider15 = 0.2; slider17 = 0; slider18 = 0; slider19 = 30; slider20 = 120; slider21 = 6000; slider22 = -0.3; slider23 = 1; slider24 = 0; slider25 = 0; slider26 = 0; slider27 = 50; slider_automate(134217727); // sliders 1-27 ); ); // ---------- state ---------- drW.drift_init(0.8 + rand(0.5)); ost_init(20000); ostL.ost_ch_init(); ostR.ost_ch_init(); in_init(); sm = 1 - exp(-1 / (0.03 * srate)); vuc = 1 - exp(-1 / (0.01 * srate)); dk_a = 1 - exp(-1 / (0.01 * srate)); dk_r = 1 - exp(-1 / (0.4 * srate)); pkL = 0; pkR = 0; vu_ms = 0; denv = 0; spwL = 0; spwR = 0; spyL = 0; spyR = 0; vt_m = -1; !ui_dflt ? ui_thm = 18; !ui_dflt ? ui_hw = 1; // defaults for new instances: Blossom, hardware (saved projects keep their own) update(); mix_s = mix_t; out_s = out_t; ui_dflt = 1; // theme defaults above apply once: @init re-runs (playback start, device reset) must not undo the saved theme @slider update(); @block update(); vW = drW.drift_tick(dspd * samplesblock / srate); @serialize file_var(0, ui_thm); file_var(0, ui_sz); file_var(0, ui_hw); file_avail(0) != 0 ? file_var(0, pr_cur); // the preset in use (older projects end before it) @sample // input stage: DC block, trim, guard in_process(spl0, spl1); inL = in_l; inR = in_r; vu_ms += (0.5 * (inL * inL + inR * inR) - vu_ms) * vuc; mix_s += (mix_t - mix_s) * sm; out_s += (out_t - out_s) * sm; xL = frz ? 0 : inL; xR = frz ? 0 : inR; // pre-delay (stereo, interleaved) pre_b[pre_w * 2] = xL; pre_b[pre_w * 2 + 1] = xR; prd = pre_w - floor(pre_s); prd < 0 ? prd += floor(pre_len / 2); pL = pre_b[prd * 2]; pR = pre_b[prd * 2 + 1]; pre_w += 1; pre_w >= floor(pre_len / 2) ? pre_w = 0; rtype == 4 ? ( // ---------- spring tank: two springs ---------- sL = sp_b[spwL]; sR = sp_b[BLK + spwR]; spyL += (sL - spyL) * sp_lp; spyR += (sR - spyR) * sp_lp; sp_b[spwL] = 0.6 * pL + sp_g * spcL.sp_chain(spyL); sp_b[BLK + spwR] = 0.6 * pR + sp_g * spcR.sp_chain(spyR); spwL += 1; spwL >= sp_lenL ? spwL = 0; spwR += 1; spwR >= sp_lenR ? spwR = 0; rL = sL * 1.4; rR = sR * 1.4; ) : ( // ---------- early reflections ---------- er_b[er_w * 2] = pL; er_b[er_w * 2 + 1] = pR; eL = 0; eR = 0; erl > 0 ? ( ak = 0; loop(8, t_ = floor(ert[ak] * er_sc * 0.001 * srate); r_ = er_w - t_; r_ < 0 ? r_ += floor(er_len / 2); eL += erg[ak] * er_b[r_ * 2 + (ak & 1)]; t_ = floor(ert[ak] * er_sc * 1.13 * 0.001 * srate); r_ = er_w - t_; r_ < 0 ? r_ += floor(er_len / 2); eR += erg[ak] * er_b[r_ * 2 + 1 - (ak & 1)]; ak += 1; ); ); er_w += 1; er_w >= floor(er_len / 2) ? er_w = 0; // ---------- input diffusion (4 allpasses per side) ---------- dL = pL + 0.3 * eL; dR = pR + 0.3 * eR; ak = 0; loop(4, n_ = floor(apl[ak] * SR48 * (0.6 + 0.4 * diff)); o_ = ap_b + ak * 8192; wq = apw[ak]; rq = wq - n_; rq < 0 ? rq += 4096; bv = o_[rq * 2]; yv = -diff * 0.75 * dL + bv; o_[wq * 2] = dL + diff * 0.75 * yv; dL = yv; bv = o_[rq * 2 + 1]; yv = -diff * 0.75 * dR + bv; o_[wq * 2 + 1] = dR + diff * 0.75 * yv; dR = yv; apw[ak] = (wq + 1) % 4096; ak += 1; ); // ---------- 8-line feedback delay network ---------- ak = 0; loop(8, lph[ak] += lrt[ak] * (1 + 0.5 * wan * vW) / srate; lph[ak] >= 1 ? lph[ak] -= 1; md_ = (modd * 0.0009 + wan * 0.0004 * vW) * srate * sin(2 * $pi * lph[ak]); rp = lw[ak] - llen[ak] - md_; rp < 0 ? rp += BLK; ri = floor(rp); rf = rp - ri; o_ = ln_b + ak * BLK; v_ = o_[ri % BLK] * (1 - rf) + o_[(ri + 1) % BLK] * rf; // two-band loss lz[ak] = lg_b[ak] * v_ + lg_a[ak] * lz[ak]; 1000[ak] = lz[ak]; ak += 1; ); // Hadamard mixing (8 x 8, energy preserving) a0 = 1000[0]; a1 = 1000[1]; a2 = 1000[2]; a3 = 1000[3]; a4 = 1000[4]; a5 = 1000[5]; a6 = 1000[6]; a7 = 1000[7]; b0 = a0 + a1; b1 = a0 - a1; b2 = a2 + a3; b3 = a2 - a3; b4 = a4 + a5; b5 = a4 - a5; b6 = a6 + a7; b7 = a6 - a7; c0 = b0 + b2; c1 = b1 + b3; c2 = b0 - b2; c3 = b1 - b3; c4 = b4 + b6; c5 = b5 + b7; c6 = b4 - b6; c7 = b5 - b7; hs = 0.35355339; 1010[0] = (c0 + c4) * hs; 1010[1] = (c1 + c5) * hs; 1010[2] = (c2 + c6) * hs; 1010[3] = (c3 + c7) * hs; 1010[4] = (c0 - c4) * hs; 1010[5] = (c1 - c5) * hs; 1010[6] = (c2 - c6) * hs; 1010[7] = (c3 - c7) * hs; ak = 0; loop(8, o_ = ln_b + ak * BLK; o_[lw[ak]] = 1010[ak] + (ak < 4 ? dL : dR) * (ak & 1 ? -0.5 : 0.5); lw[ak] += 1; lw[ak] >= BLK ? lw[ak] = 0; ak += 1; ); // decorrelated outputs rL = 1000[0] - 1000[2] + 1000[4] - 1000[6] + 0.5 * (1000[1] + 1000[5]); rR = 1000[1] - 1000[3] + 1000[5] - 1000[7] + 0.5 * (1000[2] - 1000[6]); rL = rL * 0.5 + erl * eL * 0.6; rR = rR * 0.5 + erl * eR * 0.6; ); // reverb tone: lo cut, hi cut hzL += (rL - hzL) * lo_c; rL -= hzL; hzR += (rR - hzR) * lo_c; rR -= hzR; lzL += (rL - lzL) * hi_c; rL = lzL; lzR += (rR - lzR) * hi_c; rR = lzR; vt_on ? ( rL = vtL.vt_proc(rL); rR = vtR.vt_proc(rR); ); // ducking lvl = max(abs(inL), abs(inR)); denv += (lvl - denv) * (lvl > denv ? dk_a : dk_r); dg = 1 - duck * min(denv * 4, 1); // width and mix em = 0.5 * (rL + rR); es = 0.5 * (rL - rR) * wid; wL = (em + es) * dg; wR = (em - es) * dg; dry_g = min(2 * (1 - mix_s), 1); wet_g = min(2 * mix_s, 1); oL = inL * dry_g + wL * wet_g; oR = inR * dry_g + wR * wet_g; oL = ostL.ost_band(oL) * out_s; oR = ostR.ost_band(oR) * out_s; ost_out(oL, oR); spl0 = ost_l; spl1 = ost_r; pkL = max(pkL, abs(spl0)); pkR = max(pkR, abs(spl1)); @gfx 980 412 ui_begin(980, 412); // ---------- DRAFT B: 90s desktop stereo reverb - lilac powder-coat, a wide amber LCD, pastel-coded knob caps ---------- LIL = 0xC7B6D9; INK = 0x2E2340; VIO = 0x5B3F86; MINT = 0x8FD8B8; PEACH = 0xF2B08A; SKY = 0x8EC2F0; ROSE = 0xE59AB8; function setv(idx, v) ( slider(idx) = v; ui_auto(idx); ); function tname(t) ( t == 0 ? "ROOM" : t == 1 ? "CHAMBER" : t == 2 ? "HALL" : t == 3 ? "PLATE" : "SPRING" ); // the decay shape (x, y, w, h unscaled): pre-delay gap, early reflections, the tail to -60 dB function dcy(x, y, w, h, ca, cb, cg) local(span, i, px0, qx, qy, hh, tt, te, db, px, py, k) ( span = max(slider2 / 1000 + slider3 * 1.15, 0.5); ui_cola(cg, 0.3); i = 0; loop(4, qy = y + i / 3 * h; gfx_line(x * sc, qy * sc, (x + w) * sc, qy * sc); i += 1; ); px0 = x + slider2 / 1000 / span * w; ui_cola(cg, 0.6); gfx_line(px0 * sc, y * sc, px0 * sc, (y + h) * sc); slider1 < 3 && slider7 > 0 ? ( ui_col(cb); k = 0; loop(8, qx = px0 + ert[k] * (slider1 == 0 ? 0.55 : slider1 == 1 ? 0.9 : 1.5) / 1000 / span * w; hh = abs(erg[k]) * slider7 / 100 * h * 0.8; gfx_rect(qx * sc, (y + h - hh) * sc, 2 * sc, hh * sc); k += 1; ); ); ui_col(ca); i = 0; loop(121, tt = i / 120 * span; te = tt - slider2 / 1000 - (slider1 == 3 ? 0 : 0.01); db = te < 0 ? -80 : slider13 ? -6 : -6 - 60 * te / slider3; qx = (x + tt / span * w) * sc; qy = (y + min(-db / 66, 1) * h) * sc; i > 0 && db > -66 ? ( gfx_line(px, py, qx, qy, 1); gfx_line(px, py + 1, qx, qy + 1, 1); ); px = qx; py = qy; i += 1; ); ); ui_col(0x141018); gfx_rect(0, 0, gfx_w, gfx_h); rd_plate(6, 4, 968, 404, LIL, 16); ui_col(ui_mix(LIL, 0x000000, 0.12)); rd_rr(14 * sc, 12 * sc, 952 * sc, 40 * sc, 10 * sc); rd_badge(40, 32, 13, VIO, LIL); gfx_setfont(6, "Trebuchet MS", 26 * sc, 'b'); ui_col(VIO); gfx_x = 62 * sc; gfx_y = 17 * sc; gfx_drawstr("space"); rd_sub(150, 28, "STEREO REVERBERATOR", INK, 0.7); pk_pextra = "Reset all to defaults"; // the house preset display: PRESETS, prev / next, LCD (click it for the list) k_ = pk_preset(520 * sc, 16 * sc, 300 * sc, 32 * sc, pr_cur, "Small Room|Live Room|Vocal Chamber|Concert Hall|Cathedral (ducked)|Bright Plate|Snare Plate|Surf Spring|Dub Spring|Ambient Wash"); k_ > 0 ? ( load_preset(k_); pr_cur = k_ > 10 ? 0 : k_; ); ui_size_picker(830 * sc, 22 * sc); // ---------- the window (what is ringing) + the decay LCD ---------- function win(x, y, w, h, col) local(cx, cy, k, i, yy, x1, x2, w1, w2, rr, rw) ( cx = x + w * 0.5; cy = y + h * 0.5; slider1 == 4 ? ( k = 0; loop(2, yy = y + h * (0.36 + k * 0.34); ui_col(0x6A5A80); gfx_rect((x + 8) * sc, (yy - 6) * sc, 8 * sc, 12 * sc); gfx_rect((x + w - 16) * sc, (yy - 6) * sc, 8 * sc, 12 * sc); ui_col(col); i = 0; loop(90, x1 = x + 16 + i / 90 * (w - 32); x2 = x + 16 + (i + 1) / 90 * (w - 32); w1 = sin(i * 1.2) * 4.5 + sin(i * 0.15 + sp_ph * (0.3 + k * 0.07)) * 4 * lv_; w2 = sin((i + 1) * 1.2) * 4.5 + sin((i + 1) * 0.15 + sp_ph * (0.3 + k * 0.07)) * 4 * lv_; gfx_line(x1 * sc, (yy + w1) * sc, x2 * sc, (yy + w2) * sc, 1); i += 1; ); k += 1; ); ) : slider1 == 3 ? ( ui_cola(col, 0.35); gfx_rect((x + 28) * sc, (y + 16) * sc, (w - 56) * sc, (h - 30) * sc); i = 0; loop(8, rr = ((sp_ph * 1.6 + i * 12) % (h * 0.42)); ui_cola(0xFFFFFF, 0.3 * lv_ * (1 - rr / (h * 0.42)) + 0.04); gfx_circle(cx * sc, (cy + 2) * sc, rr * sc, 0, 1); i += 1; ); ui_col(0x16121C); gfx_rect(x * sc, y * sc, 28 * sc, h * sc); gfx_rect((x + w - 28) * sc, y * sc, 28 * sc, h * sc); k = 0; loop(3, ui_cola(col, 0.6); gfx_line((x + 50 + k * (w - 100) / 2) * sc, y * sc, (x + 50 + k * (w - 100) / 2) * sc, (y + 16) * sc); k += 1; ); ) : ( rw = (slider1 == 0 ? 0.45 : slider1 == 1 ? 0.7 : 1) * slider4 / 150; ui_cola(col, 0.7); gfx_rect((cx - (w * 0.45) * rw) * sc, (cy - (h * 0.4) * rw) * sc, w * 0.9 * rw * sc, h * 0.8 * rw * sc); ui_col(0x16121C); gfx_rect((cx - (w * 0.45) * rw + 1) * sc, (cy - (h * 0.4) * rw + 1) * sc, (w * 0.9 * rw - 2) * sc, (h * 0.8 * rw - 2) * sc); i = 0; loop(6, rr = ((sp_ph * 1.2 + i * 16) % (h * 0.4)) * rw; ui_cola(col, 0.5 * (1 - rr / (h * 0.4 * rw + 0.01)) * (0.3 + lv_)); gfx_circle((cx - 20 * rw) * sc, cy * sc, rr * sc, 0, 1); i += 1; ); ui_col(0xFFF4D8); gfx_circle((cx - 20 * rw) * sc, cy * sc, 2.5 * sc, 1, 1); ); ); lv_ = min(sqrt(max(pkL, pkR)) * 1.4, 1); sp_ph += 0.25 + lv_; ui_col(0x3A2E4C); rd_rr(22 * sc, 64 * sc, 200 * sc, 92 * sc, 6 * sc); ui_col(0x16121C); rd_rr(25 * sc, 67 * sc, 194 * sc, 86 * sc, 5 * sc); win(25, 67, 194, 86, 0xD8C0FF); gfx_setfont(4); ui_col(0xD8C0FF); gfx_x = 32 * sc; gfx_y = 70 * sc; gfx_drawstr(tname(slider1)); pk_lcd(230 * sc, 64 * sc, 292 * sc, 92 * sc, 0); dcy(240, 82, 272, 66, pk_lcdc, pk_lcdc, pk_lcdc); gfx_setfont(4); ui_col(pk_lcdc); gfx_x = 240 * sc; gfx_y = 68 * sc; sprintf(#ts, "%.2f s pre %.0f ms%s", slider3, slider2, slider13 ? " FROZEN" : ""); gfx_drawstr(#ts); // ---------- type keys + freeze ---------- rd_sqbg = 0x3A2C52; rd_lbl(744, 62, "REVERB TYPE", INK, 0.7); k = 0; loop(5, rd_sq(540 + k * 82, 76, 76, 26, slider1 == k, k == 0 ? MINT : k == 1 ? SKY : k == 2 ? 0xB8A0F0 : k == 3 ? PEACH : ROSE, tname(k)) ? setv(1, k); k += 1; ); rd_sq(540, 112, 158, 30, slider13, 0x60C0FF, "FREEZE") ? setv(13, 1 - slider13); rd_lbl(619, 148, "holds the tail forever", INK, 0.6); rd_sqbg = 0x101012; mL.ui_ledm(718 * sc, 128 * sc, 236 * sc, 7 * sc, pkL); mR.ui_ledm(718 * sc, 138 * sc, 236 * sc, 7 * sc, pkR); pkL *= 0.85; pkR *= 0.85; // ---------- the reverb knobs, colour-coded ---------- dy_ink = INK; dy_bg = LIL; dy_kst = 7; ui_cola(INK, 0.1); gfx_rect(22 * sc, 168 * sc, 932 * sc, 1 * sc); sprintf(#v, "%.0f ms", slider2); dy_knob(64, 214, 22, 2, 0, 250, 20, 0, MINT, "PRE-DELAY", #v); sprintf(#v, "%.2f s", slider3); dy_knob(166, 214, 26, 3, 0.2, 20, 2.4, 1, MINT, "DECAY", #v); dy_nov = 1; // the % knobs show their value only while touched sprintf(#v, "%.0f%%", slider4); dy_knob(268, 214, 22, 4, 50, 150, 100, 0, MINT, "SIZE", #v); sprintf(#v, "%.0f%%", slider5); dy_knob(370, 214, 22, 5, 0, 100, 50, 0, PEACH, "DAMPING", #v); sprintf(#v, "%.0f%%", slider6); dy_knob(472, 214, 22, 6, 0, 100, 30, 0, PEACH, "MOD", #v); slider1 >= 3 ? strcpy(#v, "n/a") : sprintf(#v, "%.0f%%", slider7); dy_knob(574, 214, 22, 7, 0, 100, 50, 0, SKY, "EARLY", #v); slider1 == 4 ? strcpy(#v, "n/a") : sprintf(#v, "%.0f%%", slider8); dy_knob(676, 214, 22, 8, 0, 100, 75, 0, SKY, "DIFFUSION", #v); sprintf(#v, "%.0f%%", slider11); dy_knob(778, 214, 22, 11, 0, 100, 0, 0, ROSE, "DUCK", #v); sprintf(#v, "%.0f%%", slider12); dy_knob(880, 214, 22, 12, 0, 100, 100, 0, ROSE, "WIDTH", #v); dy_nov = 0; // ---------- bottom row ---------- ui_cola(INK, 0.1); gfx_rect(22 * sc, 290 * sc, 932 * sc, 1 * sc); dy_kst = 20; dy_nov = 0; rd_sub(26, 298, "TONE", VIO, 1); sprintf(#v, "%.0f Hz", slider9); dy_kf = 1; dy_knob(52, 340, 14, 9, 20, 1000, 120, 1, 0, "LO CUT", #v); sprintf(#v, "%.0f Hz", slider10); dy_kf = 2; dy_knob(112, 340, 14, 10, 1000, 20000, 9000, 1, 0, "HI CUT", #v); k = 0; loop(4, rd_sq(150 + k * 26, 324, 24, 20, slider26 == k, VIO, k == 0 ? "0" : k == 1 ? "-3" : k == 2 ? "-6" : "-9") ? setv(26, k); k += 1; ); rd_lbl(201, 352, "500 Hz CUT", INK, 0.8); rd_lbl(201, 366, "HP 140 / LP 5k", INK, 0.5); ui_cola(INK, 0.12); gfx_rect(266 * sc, 300 * sc, 1 * sc, 92 * sc); gfx_rect(390 * sc, 300 * sc, 1 * sc, 92 * sc); gfx_rect(560 * sc, 300 * sc, 1 * sc, 92 * sc); rd_sub(274, 298, "DRIFT", VIO, 1); sprintf(#v, "%.0f%%", slider27); dy_nov = 1; dy_knob(328, 344, 15, 27, 0, 100, 50, 0, 0x9C8AB4, "DRIFT", #v); rd_sub(398, 298, "MASTER", VIO, 1); dy_kst = 7; sprintf(#v, "%.0f%%", slider16); dy_knob(440, 344, 16, 16, 0, 100, 30, 0, 0xF4EEF8, "MIX", #v); sprintf(#v, "%+.1f dB", slider17); dy_knob(510, 344, 16, 17, -24, 12, 0, 0, 0xF4EEF8, "OUTPUT", #v); rd_sub(568, 298, "IN / OUT", VIO, 1); rd_ioc1 = 0x9C8AB4; rd_ioc2 = PEACH; rd_ioc3 = ROSE; rd_io(640, 322, 24, 25, 18, 23); dy_nov = 0; pk_end(); ui_end();