desc:GFX Benthick 3B //tags: bus processor saturation transformer transistor compressor limiter width mastering //author: JGWizrad / Garden FX (GFX) // // Requires gfx-drift, gfx-input, gfx-output, gfx-ui, gfx-panel, gfx-dyn, gfx-faceplate and gfx-instrument (.jsfx-inc) in the same folder. // // A solid-state iron and transistor bus processor: tight low-end weight, fast transient clamping and // dense glue, without woolly tube artefacts. 3B lays the stages out as columns, left to right in signal // order, with the big TILT knob straddling the line between COMP 1 and COMP 2: // 1 FRONT END - IRON: an asymmetric transformer core that works hardest on the lows (even harmonics, // weight, a little magnetic hysteresis), or CLASS-A: a symmetric discrete transistor // clip (odd harmonics, a dense edge focused in the upper mids). DRIVE pushes either; // small signals pass at unity. M/S drives the Sides harder (S DRIVE) for natural width. // A 5 Hz DC blocker and a gain clamp sit straight after the core. // SC HPF keeps the lows out of both compressors' detectors (no sub-bass pumping). // 2 COMP 1 - the slow window: a slow attack lets the early transients through untouched. HYST is a // dual-threshold gate on the detector: once over THRESH the release holds back until the // level falls HYST dB below it, so it never chatters round the threshold. // TILT - a seesaw around 700 Hz, after COMP 1: it shapes what COMP 2 hears and what you hear. // 3 COMP 2 - the fast FET grabber: microsecond attack, 4 / 8 / 12 / 20:1 or ALL (every ratio key in: // hard, aggressive, a little grit). MAKEUP sets its level. // BALANCE - the summing node: left = COMP 1 (its untouched transients, after TILT), right = COMP 2 // (the clamped signal) - the same signal through two paths, so it sums phase-coherently. // 4 BUS COMP - console-style VCA glue that pulls COMP 1 and COMP 2 together: 2 / 4 / 10:1, stepped // ATTACK (0.1 - 30 ms) and RELEASE (0.1 - 1.2 s or AUTO: quick after a transient, slow when // it has been working a while), THRESH, MAKEUP. Its detector shares the SC HPF. // 5 WIDTH - M/S width; S SAT drives the Sides into a second discrete saturation stage. // MIX - wet / dry, OUTPUT, then the house OUT stage: CLIP (the final soft-clip protection), or // the lookahead brickwall LIMIT with the LIMIT key. (3B has no IN / OUT strip: input trim, guard, // band sat and the OUT stage stay at their defaults.) // Every stage has an IN key. No drift: Benthick 3 is clean and repeatable. 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:4<0,18,0.1>-Drive (dB) slider2:0<0,1,1{Iron,Class-A}>-Front end slider3:0<0,1,1{Off,On}>-M/S drive slider4:4<0,12,0.1>-Side drive (dB) slider5:90<30,300,1>-Sidechain HPF (Hz) slider6:-18<-40,0,0.1>-Comp 1 threshold (dB) slider7:2<1,10,0.1>-Comp 1 ratio slider8:40<10,80,1>-Comp 1 attack (ms) slider9:300<50,1000,1>-Comp 1 release (ms) slider10:3<0,12,0.1>-Comp 1 hysteresis (dB) slider11:0<-6,6,0.1>-Tilt (dB) slider12:-14<-40,0,0.1>-Comp 2 threshold (dB) slider13:0<0,4,1{4:1,8:1,12:1,20:1,All}>-Comp 2 ratio slider14:400<20,800,1>-Comp 2 attack (us) slider15:300<50,1100,1>-Comp 2 release (ms) slider16:2<0,24,0.1>-Comp 2 makeup (dB) slider17:60<0,100,1>-Balance (Comp 1 <-> Comp 2, %) slider18:-18<-30,0,0.1>-Bus comp threshold (dB) slider19:0<0,2,1{2:1,4:1,10:1}>-Bus comp ratio slider20:4<0,5,1{0.1 ms,0.3 ms,1 ms,3 ms,10 ms,30 ms}>-Bus comp attack slider21:100<0,200,1>-Width (%) slider22:0<0,100,1>-Side sat (%) slider23:100<0,100,1>-Mix (%) slider24:0<-24,12,0.1>-Output (dB) slider25:0<-24,12,0.1>-Input trim (dB) slider26:1<0,1,1{Off,On}>-Input guard slider27:0<0,100,1>-Band sat (%) slider28:30<0,100,1>-Bias (%) slider29:120<40,400,1>-Sat low band (Hz) slider30:6000<2000,16000,10>-Sat high band (Hz) slider31:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider32:1<0,2,1{Off,Clip,Limit}>-Output stage slider33:1<0,1,1{Out,In}>-Front end in slider34:1<0,1,1{Out,In}>-Comp 1 in slider35:1<0,1,1{Out,In}>-Comp 2 in slider36:1<0,1,1{Out,In}>-Bus comp in slider37:4<0,4,1{0.1 s,0.3 s,0.6 s,1.2 s,Auto}>-Bus comp release slider38:5<0,18,0.1>-Bus comp makeup (dB) in_pin:left input in_pin:right input out_pin:left output out_pin:right output @init #ui_man = "gfx-benthick-3b"; // ---------- 1 FRONT END ---------- // IRON: the lows (below ~160 Hz) are driven harder into an asymmetric core (even harmonics, weight); // a touch of the field's rate of change is added before the core, so it lags rising and falling // signals differently (a small hysteresis loop). The rest of the band sees a gentler, symmetric core. function fe_iron(x, g) instance(lo, um) local(hi, u, d, yl, yh) ( lo += (x - lo) * ir_lpc; hi = x - lo; u = lo * g * 1.6; d = u - um; um = u; u += ir_hy * d; yl = (ost_tanh(u + ir_b) - ir_tb) / (ir_sl * g * 1.6); yh = ost_tanh(hi * g * 0.45) / (g * 0.45); yl + yh; ); // CLASS-A: symmetric, firmer knee than tanh (odd harmonics, a dense edge); the upper mids are lifted // into the clip and put back after it, so small signals stay flat and the harmonics land up there. function fe_classa(x, g) instance(pz1, pz2, px1, px2, dz1, dz2, dx1, dx2) local(e, y, a) ( e = ca_b0 * x + ca_b1 * px1 + ca_b2 * px2 - ca_a1 * pz1 - ca_a2 * pz2; px2 = px1; px1 = x; pz2 = pz1; pz1 = e; e *= g; a = abs(e); y = e / (1 + a ^ 2.5) ^ 0.4; y /= g; e = cd_b0 * y + cd_b1 * dx1 + cd_b2 * dx2 - cd_a1 * dz1 - cd_a2 * dz2; dx2 = dx1; dx1 = y; dz2 = dz1; dz1 = e; e; ); // the DC safeguard straight after the core: 1-pole high-pass at 5 Hz, then a gain clamp function fe_dc(x) instance(xm, ym) local(y) ( y = x - xm + dc_c * ym; xm = x; ym = y; min(max(y, -4), 4); ); // a peaking biquad's coefficients (the CLASS-A pre / de-emphasis) function pk_coefs(f, q, gdb) local(w0, cw, al, A, n) ( w0 = 2 * $pi * min(f, srate * 0.45) / srate; cw = cos(w0); al = sin(w0) / (2 * q); A = 10 ^ (gdb / 40); n = 1 + al / A; pk_b0 = (1 + al * A) / n; pk_b1 = -2 * cw / n; pk_b2 = (1 - al * A) / n; pk_a1 = pk_b1; pk_a2 = (1 - al / A) / n; ); ir_lpc = 1 - exp(-2 * $pi * 160 / srate); ir_b = 0.28; ir_tb = ost_tanh(ir_b); ir_sl = 1 - ir_tb * ir_tb; dc_c = 1 - 2 * $pi * 5 / srate; pk_coefs(2800, 0.8, 4); ca_b0 = pk_b0; ca_b1 = pk_b1; ca_b2 = pk_b2; ca_a1 = pk_a1; ca_a2 = pk_a2; pk_coefs(2800, 0.8, -4); cd_b0 = pk_b0; cd_b1 = pk_b1; cd_b2 = pk_b2; cd_a1 = pk_a1; cd_a2 = pk_a2; tl_c = 1 - exp(-2 * $pi * 700 / srate); sm = 1 - exp(-1 / (0.03 * srate)); c1_da = dy_ms(1); c1_dr = dy_ms(30); b3_fr = dy_ms(100); b3_sa = dy_ms(500); b3_sr = dy_ms(1200); // AUTO release: fast and slow halves function update() local(r) ( fe_on = slider33; c1_on = slider34; c2_on = slider35; li_on = slider36; drv_t = 10 ^ (slider1 / 20); topo_t = slider2; ms_on = slider3; sdrv_t = ms_on ? 10 ^ (slider4 / 20) : 1; ir_hy = 0.06 * srate / 1000 * (0.4 + 0.6 * slider1 / 18); slider5 != sc_last ? ( sc_last = slider5; h3L.bq_set(1, slider5, 0.707, 0); h3R.bq_set(1, slider5, 0.707, 0); h1L.bq_set(1, slider5, 0.707, 0); h1R.bq_set(1, slider5, 0.707, 0); h2L.bq_set(1, slider5, 0.707, 0); h2R.bq_set(1, slider5, 0.707, 0); ); c1_th = slider6; c1_ra = slider7; c1_at = dy_ms(slider8); c1_rl = dy_ms(slider9); c1_hy = slider10; tgl_t = 10 ^ (-slider11 / 40); tgh_t = 10 ^ (slider11 / 40); c2_th = slider12; r = floor(slider13 + 0.5); c2_all = r == 4; c2_ra = r == 0 ? 4 : r == 1 ? 8 : r == 2 ? 12 : 20; c2_kn = c2_all ? 0 : r == 0 ? 6 : r == 1 ? 4 : 2; c2_at = 1 - exp(-1 / (max(slider14, 1) * (c2_all ? 0.7 : 1) * 0.000001 * srate)); c2_rl = dy_ms(slider15 * (c2_all ? 0.6 : 1)); c2_mk_t = 10 ^ (slider16 / 20); c2_col = c2_all ? 0.45 : 0.2; pu_t = 1 - slider17 / 100; // the share of the COMP 1 path r = floor(slider19 + 0.5); b3_ra = r == 0 ? 2 : r == 1 ? 4 : 10; r = floor(slider20 + 0.5); b3_at = dy_ms(r == 0 ? 0.1 : r == 1 ? 0.3 : r == 2 ? 1 : r == 3 ? 3 : r == 4 ? 10 : 30); r = floor(slider37 + 0.5); b3_auto = r == 4; b3_rl = dy_ms(r == 0 ? 100 : r == 1 ? 300 : r == 2 ? 600 : 1200); b3_th = slider18; b3_mk_t = 10 ^ (slider38 / 20); wid_t = slider21 / 100; ss_k = 1 + 11 * slider22 / 100; ss_on = slider22 > 0; mix_t = slider23 / 100; out_t = 10 ^ (slider24 / 20); in_setup(slider25, slider26); ost_setup(slider27 / 100, slider28 / 100, slider29, slider30, slider31, slider32); ); // ---------- presets ---------- // drive, front end, M/S, side drive, SC HPF | C1 thresh, ratio, attack, release, hyst | tilt | // C2 thresh, ratio key, attack, release, makeup | balance | bus thresh, ratio key, attack step | width, side sat, mix | // bus release step, bus makeup function setp(dv, fe, ms, sd, hp, t1, r1, a1, l1, hy, ti, t2, r2, a2, l2, mk, pu, ld, lc, lr, wd, ss, mx, br, bm) ( slider1 = dv; slider2 = fe; slider3 = ms; slider4 = sd; slider5 = hp; slider6 = t1; slider7 = r1; slider8 = a1; slider9 = l1; slider10 = hy; slider11 = ti; slider12 = t2; slider13 = r2; slider14 = a2; slider15 = l2; slider16 = mk; slider17 = pu; slider18 = ld; slider19 = lc; slider20 = lr; slider21 = wd; slider22 = ss; slider23 = mx; slider37 = br; slider38 = bm; ); PRESET_N = 10; function pname(p) ( p == 1 ? "Bus Glue" : p == 2 ? "Iron Low End" : p == 3 ? "Drum Smash" : p == 4 ? "Vocal Forward" : p == 5 ? "Wide Master" : p == 6 ? "Parallel Crush" : p == 7 ? "Mix Bus Polish" : p == 8 ? "Bass DI Weight" : p == 9 ? "Room Mic Grit" : p == 10 ? "Gentle Master" : p == 11 ? "Reset all to defaults" : "-"; ); function load_preset(p) ( p == 1 ? setp(4, 0, 0, 4, 90, -18, 2, 40, 300, 3, 0, -14, 0, 400, 300, 2, 60, -18, 0, 4, 100, 0, 100, 4, 5) : p == 2 ? setp(8, 0, 0, 0, 120, -20, 2.5, 50, 400, 4, -1.5, -16, 0, 600, 400, 3, 50, -20, 0, 4, 100, 0, 100, 4, 7) : p == 3 ? setp(6, 1, 0, 0, 80, -22, 3, 30, 200, 2, 2, -24, 4, 20, 100, 8, 65, -22, 1, 2, 110, 10, 100, 1, 10.8) : p == 4 ? setp(3, 1, 0, 0, 150, -20, 2, 25, 250, 3, 1.5, -18, 1, 300, 200, 4, 70, -20, 0, 3, 100, 0, 100, 2, 8.5) : p == 5 ? setp(3, 0, 1, 6, 100, -16, 1.5, 50, 500, 4, 0.5, -10, 0, 800, 600, 1, 50, -16, 0, 5, 130, 25, 100, 4, 3.2) : p == 6 ? setp(10, 1, 0, 0, 90, -24, 4, 30, 200, 2, 0, -30, 4, 20, 80, 12, 80, -22, 2, 0, 100, 0, 60, 0, 9) : p == 7 ? setp(2, 0, 1, 3, 70, -14, 1.5, 40, 400, 3, 0.5, -8, 0, 600, 500, 1, 40, -15, 0, 4, 115, 15, 100, 4, 2.7) : p == 8 ? setp(10, 0, 0, 0, 60, -20, 3, 40, 300, 3, -2, -18, 1, 200, 150, 4, 70, -20, 1, 3, 100, 0, 100, 2, 7.4) : p == 9 ? setp(12, 1, 1, 6, 150, -26, 4, 15, 150, 1, 3, -30, 4, 20, 60, 10, 75, -24, 2, 0, 140, 40, 70, 1, 11.5) : p == 10 ? setp(1, 0, 0, 0, 50, -12, 1.3, 50, 600, 4, 0, -6, 0, 800, 800, 0, 30, -12, 0, 5, 105, 0, 100, 4, 1.3); p >= 1 && p <= 10 ? ( slider33 = 1; slider34 = 1; slider35 = 1; slider36 = 1; slider_automate(8388607 + 2 ^ 32 + 2 ^ 33 + 2 ^ 34 + 2 ^ 35 + 2 ^ 36 + 2 ^ 37); ); // sliders 1-23, 33-38 p == 11 ? ( // Reset all to defaults setp(4, 0, 0, 4, 90, -18, 2, 40, 300, 3, 0, -14, 0, 400, 300, 2, 60, -18, 0, 4, 100, 0, 100, 4, 5); slider24 = 0; slider25 = 0; slider26 = 1; slider27 = 0; slider28 = 30; slider29 = 120; slider30 = 6000; slider31 = -0.3; slider32 = 1; slider33 = 1; slider34 = 1; slider35 = 1; slider36 = 1; slider_automate(2 ^ 38 - 1); ); update(); ); // ---------- state ---------- ost_init(40000); ostL.ost_ch_init(); ostR.ost_ch_init(); in_init(); !ui_dflt ? ui_thm = 9; !ui_dflt ? ui_hw = 1; // default for new instances: saved projects keep their own pr_cur = 1; sc_last = -1; update(); drv_s = drv_t; topo_s = topo_t; sdrv_s = sdrv_t; tgl_s = tgl_t; tgh_s = tgh_t; c2_mk = c2_mk_t; pu_s = pu_t; b3_mk = b3_mk_t; wid_s = wid_t; mix_s = mix_t; out_s = out_t; g1 = 0; g2 = 0; g3 = 0; g3s = 0; e1 = 0; c1_eng = 0; gr1 = 0; gr2 = 0; gr3 = 0; pkI = 0; pkL = 0; pkR = 0; ui_dflt = 1; @slider update(); @block update(); @serialize file_var(0, ui_thm); file_var(0, ui_sz); file_var(0, ui_hw); file_var(0, pr_cur); @sample // input stage: DC block, trim, guard in_process(spl0, spl1); xL = in_l; xR = in_r; pkI = max(pkI, max(abs(xL), abs(xR))); yL = xL; yR = xR; // the dry, for MIX drv_s += (drv_t - drv_s) * sm; topo_s += (topo_t - topo_s) * sm; sdrv_s += (sdrv_t - sdrv_s) * sm; tgl_s += (tgl_t - tgl_s) * sm; tgh_s += (tgh_t - tgh_s) * sm; c2_mk += (c2_mk_t - c2_mk) * sm; pu_s += (pu_t - pu_s) * sm; b3_mk += (b3_mk_t - b3_mk) * sm; wid_s += (wid_t - wid_s) * sm; mix_s += (mix_t - mix_s) * sm; out_s += (out_t - out_s) * sm; // ---------- 1 FRONT END ---------- fe_on ? ( ms_on ? ( fa = (xL + xR) * 0.5; fb = (xL - xR) * 0.5; ) : ( fa = xL; fb = xR; ); gb = drv_s * sdrv_s; // IRON <-> CLASS-A: both run while the key's crossfade is moving, so the switch never clicks topo_s < 0.999 ? ( ia = feIA.fe_iron(fa, drv_s); ib = feIB.fe_iron(fb, gb); ); topo_s > 0.001 ? ( ca = feCA.fe_classa(fa, drv_s); cb = feCB.fe_classa(fb, gb); ); fa = topo_s < 0.001 ? ia : topo_s > 0.999 ? ca : ia + (ca - ia) * topo_s; fb = topo_s < 0.001 ? ib : topo_s > 0.999 ? cb : ib + (cb - ib) * topo_s; ms_on ? ( xL = fa + fb; xR = fa - fb; ) : ( xL = fa; xR = fb; ); xL = dcL.fe_dc(xL); xR = dcR.fe_dc(xR); ); // ---------- 2 COMP 1: the slow window, with the hysteresis gate on its detector ---------- s1 = max(abs(h1L.bq_run(xL)), abs(h1R.bq_run(xR))); c1_on ? ( e1 += (s1 - e1) * (s1 > e1 ? c1_da : c1_dr); l1 = 20 * log10(max(e1, 0.0000001)); l1 > c1_th ? c1_eng = 1 : l1 < c1_th - c1_hy ? c1_eng = 0; t1 = dy_curve(l1 - c1_th, 6) * (1 - 1 / c1_ra); g1 += (t1 - g1) * (t1 > g1 ? c1_at : (c1_eng ? c1_rl * 0.2 : c1_rl)); k1 = 10 ^ (-g1 / 20); xL *= k1; xR *= k1; gr1 = max(gr1, g1); ) : ( g1 = 0; e1 = 0; c1_eng = 0; ); // ---------- TILT: a seesaw round 700 Hz ---------- tlL += (xL - tlL) * tl_c; tlR += (xR - tlR) * tl_c; xL = tlL * tgl_s + (xL - tlL) * tgh_s; xR = tlR * tgl_s + (xR - tlR) * tgh_s; // ---------- 3 COMP 2: the fast FET grabber, then BALANCE sums it with the untouched COMP 1 path ---------- s2 = max(abs(h2L.bq_run(xL)), abs(h2R.bq_run(xR))); c2_on ? ( l2 = 20 * log10(max(s2, 0.0000001)); t2 = dy_curve(l2 - c2_th, c2_kn) * (1 - 1 / c2_ra); g2 += (t2 - g2) * (t2 > g2 ? c2_at : c2_rl); k2 = 10 ^ (-g2 / 20) * c2_mk; cL = xL * k2; cR = xR * k2; // the gain cell's own colour: a little odd-order rounding as it works fc = min(g2 / 20, 1) * c2_col; cL += fc * (ost_tanh(cL) - cL); cR += fc * (ost_tanh(cR) - cR); gr2 = max(gr2, g2); xL = cL + (xL - cL) * pu_s; xR = cR + (xR - cR) * pu_s; // the fast gain cell can leave a little DC on lopsided waveforms: block it xL = dc2L.fe_dc(xL); xR = dc2R.fe_dc(xR); ) : g2 = 0; // ---------- 4 BUS COMP: VCA glue over the summed COMP 1 / COMP 2 signal ---------- s3 = max(abs(h3L.bq_run(xL)), abs(h3R.bq_run(xR))); li_on ? ( l3 = 20 * log10(max(s3, 0.0000001)); t3 = dy_curve(l3 - b3_th, 4) * (1 - 1 / b3_ra); b3_auto ? ( // AUTO: a fast release after a transient, a slow one once it has been working a while g3 += (t3 - g3) * (t3 > g3 ? b3_at : b3_fr); g3s += (g3 - g3s) * (g3 > g3s ? b3_sa : b3_sr); g3e = max(g3, g3s); ) : ( g3 += (t3 - g3) * (t3 > g3 ? b3_at : b3_rl); g3e = g3; ); k3 = 10 ^ (-g3e / 20) * b3_mk; xL *= k3; xR *= k3; gr3 = max(gr3, g3e); ) : ( g3 = 0; g3s = 0; ); dL = xL; dR = xR; // ---------- 5 WIDTH: M/S, the Sides into a second discrete stage ---------- wm = (dL + dR) * 0.5; ws = (dL - dR) * 0.5 * wid_s; ss_on ? ws = ost_tanh(ws * ss_k) / ss_k; dL = wm + ws; dR = wm - ws; // MIX against the (delayed) dry, OUTPUT, then the house OUT stage oL = (yL + (dL - yL) * mix_s) * out_s; oR = (yR + (dR - yR) * mix_s) * out_s; oL = ostL.ost_band(oL); oR = ostR.ost_band(oR); ost_out(oL, oR); spl0 = ost_l; spl1 = ost_r; pkL = max(pkL, abs(spl0)); pkR = max(pkR, abs(spl1)); @gfx 1180 570 ui_begin(1180, 570); FACE = 0x123A40; FACE2 = 0x0E2F34; INK = 0xE4EEEE; DIM = 0x8DB0B2; COP = 0xD07A40; TEAL = 0x46C2BA; SAND = 0xD9CBA8; KEYC = 0x6A8C90; dy_ink = INK; dy_bg = FACE; dy_kst = 7; function setv(idx, v) ( slider(idx) = v; ui_auto(idx); update(); ); function bt_title(x, y, s, a) ( gfx_setfont(6, "Trebuchet MS", 13 * sc, 'b'); ui_cola(COP, a); gfx_x = x * sc; gfx_y = y * sc; gfx_drawstr(s); ); // a stage's IN key: lit copper while the stage is in function bt_in(x, y, idx) ( rd_sq(x, y, 40, 22, slider(idx) > 0.5, COP, "IN") ? setv(idx, slider(idx) > 0.5 ? 0 : 1); ); // a vertical gain-reduction meter, 1 - 20 dB, lit downwards function bt_gr(x, y, gr) local(i, d, c, yy) ( ui_col(0x061416); gfx_rect((x - 3) * sc, (y - 3) * sc, 22 * sc, 84 * sc); i = 0; loop(10, d = i == 0 ? 1 : i == 1 ? 2 : i == 2 ? 3 : i == 3 ? 4 : i == 4 ? 6 : i == 5 ? 8 : i == 6 ? 10 : i == 7 ? 12 : i == 8 ? 15 : 20; yy = y + i * 8; c = d <= 4 ? 0x4AE08A : d <= 10 ? 0xF0B040 : 0xF0503A; ui_col(gr >= d ? c : ui_mix(c, 0x000000, 0.8)); gfx_rect(x * sc, yy * sc, 16 * sc, 6 * sc); i += 1; ); rd_lbl(x + 8, y + 86, "GR", INK, 0.8); ); // a horizontal gain-reduction meter, 1 - 20 dB, lit left to right function bt_grh(x, y, gr) local(i, d, c, xx) ( rd_lbl(x - 14, y + 1, "GR", INK, 0.8); ui_col(0x061416); gfx_rect((x - 3) * sc, (y - 3) * sc, 175 * sc, 16 * sc); i = 0; loop(10, d = i == 0 ? 1 : i == 1 ? 2 : i == 2 ? 3 : i == 3 ? 4 : i == 4 ? 6 : i == 5 ? 8 : i == 6 ? 10 : i == 7 ? 12 : i == 8 ? 15 : 20; xx = x + i * 17; c = d <= 4 ? 0x4AE08A : d <= 10 ? 0xF0B040 : 0xF0503A; ui_col(gr >= d ? c : ui_mix(c, 0x000000, 0.8)); gfx_rect(xx * sc, y * sc, 14 * sc, 10 * sc); sprintf(#bt_gl, "%d", d); rd_lbl(xx + 7, y + 16, #bt_gl, INK, 0.55); i += 1; ); ); // a section panel: a slightly darker inset with its title function bt_sect(x, y, w, h, title, on) ( ui_col(FACE2); rd_rr(x * sc, y * sc, w * sc, h * sc, 6 * sc); ui_cola(0xFFFFFF, 0.07); gfx_line((x + 6) * sc, y * sc, (x + w - 6) * sc, y * sc); bt_title(x + 12, y + 8, title, on ? 1 : 0.45); ); // grey the controls of a stage that is switched out function bt_dim(x, y, w, h) ( ui_cola(FACE2, 0.55); gfx_rect(x * sc, y * sc, w * sc, h * sc); ); // the LIMIT key: a small square grey key with a red LED; in = the OUT stage's lookahead limiter, out = its soft clip function bt_limkey(x, y) local(on, hov) ( on = slider32 > 1.5; hov = mouse_x >= x * sc && mouse_x < (x + 40) * sc && mouse_y >= y * sc && mouse_y < (y + 40) * sc; pressed && hov && drag_id == 0 ? ( drag_id = -1; slider32 = on ? 1 : 2; ui_auto(32); update(); on = !on; ); ui_cola(0x000000, 0.5); gfx_rect((x + 1) * sc, (y + 2) * sc, 40 * sc, 40 * sc); ui_col(0x0A0A0B); gfx_rect(x * sc, y * sc, 40 * sc, 40 * sc); ui_col(hov ? 0x8E9496 : 0x7E8486); gfx_rect((x + 1.5) * sc, (y + 1.5) * sc, 37 * sc, 37 * sc); gfx_gradrect((x + 1.5) * sc, (y + 1.5) * sc, 37 * sc, 18 * sc, 1, 1, 1, 0.18, 0, 0, 0, 0, 0, 0, 0, -0.18 / (18 * sc)); rd_led(x + 20, y + 12, 4, on, 0xFF3A2A); gfx_setfont(6, "Trebuchet MS", 10 * sc, 'b'); ui_col(0x16191A); ui_text((x + 20) * sc, (y + 22) * sc, "LIMIT"); ); ui_col(0x050C0D); gfx_rect(0, 0, gfx_w, gfx_h); rd_plate(26, 0, 1128, 570, FACE, 0); rd_rack(0, 0, 1180, 570, 0x26383B); ui_col(COP); gfx_rect(26 * sc, 66 * sc, 1128 * sc, 2 * sc); // ---------- header: badge, title, PRESETS, prev / next, the LCD (click either for every preset), size ---------- rd_badge(60, 32, 15, COP, FACE); rd_title(84, 14, "BENTHICK 3B", 26, INK); rd_sub(282, 30, "IRON · TRANSISTOR · BUS PROCESSOR", DIM, 1); pm_ = rd_sq(676, 20, 78, 30, 0, KEYC, "PRESETS"); rd_sq(760, 20, 30, 30, 0, KEYC, "◄") ? ( pr_cur = pr_cur <= 1 ? PRESET_N : pr_cur - 1; load_preset(pr_cur); ); rd_sq(794, 20, 30, 30, 0, KEYC, "►") ? ( pr_cur = pr_cur >= PRESET_N ? 1 : pr_cur + 1; load_preset(pr_cur); ); pk_lcd(832 * sc, 12 * sc, 232 * sc, 46 * sc, 0); pk_lcdfont(9, 10 * sc); ui_col(pk_lcdc); pr_cur >= 1 && pr_cur <= PRESET_N ? sprintf(#plcd, "PRESET %02d/%02d", pr_cur, PRESET_N) : sprintf(#plcd, "PRESET --/%02d", PRESET_N); gfx_x = 844 * sc; gfx_y = 18 * sc; gfx_drawstr(#plcd); pk_lcdfont(9, 15 * sc); gfx_x = 844 * sc; gfx_y = 33 * sc; gfx_drawstr(pr_cur >= 1 && pr_cur <= PRESET_N ? pname(pr_cur) : "Custom"); pm_ || (pressed && drag_id == 0 && mouse_x >= 832 * sc && mouse_x < 1064 * sc && mouse_y >= 12 * sc && mouse_y < 58 * sc) ? ( drag_id = -1; strcpy(#pmenu, ""); k_ = 1; loop(11, k_ == pr_cur ? strcat(#pmenu, "!"); strcat(#pmenu, pname(k_)); k_ < 11 ? strcat(#pmenu, "|"); k_ += 1; ); gfx_x = 676 * sc; gfx_y = 58 * sc; k_ = gfx_showmenu(#pmenu); k_ > 0 ? ( load_preset(k_); pr_cur = k_ <= PRESET_N ? k_ : 0; ); ); ui_size_picker(1080 * sc, 26 * sc); // ---------- the columns, in signal order ---------- CY = 76; CH = 482; // 1 FRONT END bt_sect(40, CY, 210, CH, "1 FRONT END", fe_on); bt_in(198, CY + 6, 33); rd_sq(54, 120, 88, 30, topo_t < 0.5, COP, "IRON") ? setv(2, 0); rd_sq(148, 120, 88, 30, topo_t > 0.5, 0xE8E2D0, "CLASS-A") ? setv(2, 1); rd_lbl(145, 162, topo_t < 0.5 ? "ASYMMETRIC CORE · WEIGHT" : "SYMMETRIC CLIP · EDGE", DIM, 0.9); sprintf(#v, "+%.1f dB", slider1); dy_knob(145, 238, 32, 1, 0, 18, 4, 0, COP, "DRIVE", #v); ui_cola(INK, 0.15); gfx_rect(56 * sc, 318 * sc, 178 * sc, 1 * sc); rd_sq(54, 336, 88, 30, ms_on, TEAL, "M/S") ? setv(3, ms_on ? 0 : 1); rd_lbl(98, 378, ms_on ? "SIDES DRIVEN" : "L / R", DIM, 0.9); sprintf(#v, "+%.1f dB", slider4); dy_knob(196, 352, 22, 4, 0, 12, 4, 0, TEAL, "S DRIVE", #v); !ms_on ? ( ui_cola(FACE2, 0.6); gfx_circle(196 * sc, 352 * sc, 28 * sc, 1, 1); ); !fe_on ? bt_dim(48, CY + 34, 194, CH - 40); // 2 COMP 1 bt_sect(258, CY, 210, CH, "2 COMP 1", c1_on); rd_sub(272, CY + 28, "SLOW WINDOW", DIM, 1); bt_in(416, CY + 6, 34); sprintf(#v, "%.0f Hz", slider5); dy_kf = 1; dy_knob(302, 368, 22, 5, 30, 300, 90, 1, 0, "SC HPF", #v); sprintf(#v, "%.1f dB", slider6); dy_knob(302, 168, 22, 6, -40, 0, -18, 0, TEAL, "THRESH", #v); sprintf(#v, "%.1f:1", slider7); dy_knob(386, 168, 22, 7, 1, 10, 2, 1, TEAL, "RATIO", #v); sprintf(#v, "%.0f ms", slider8); dy_knob(302, 268, 22, 8, 10, 80, 40, 0, SAND, "ATTACK", #v); sprintf(#v, "%.0f ms", slider9); dy_knob(386, 268, 22, 9, 50, 1000, 300, 1, SAND, "RELEASE", #v); sprintf(#v, "%.1f dB", slider10); dy_knob(386, 368, 22, 10, 0, 12, 3, 0, SAND, "HYST", #v); bt_grh(284, 470, d1_gr); !c1_on ? bt_dim(266, CY + 50, 194, CH - 56); // 3 COMP 2 bt_sect(484, CY, 210, CH, "3 COMP 2", c2_on); rd_sub(498, CY + 28, "FET", DIM, 1); bt_in(642, CY + 6, 35); c2r = floor(slider13 + 0.5); rd_sq(498, 124, 44, 30, c2r == 0, TEAL, "4") ? setv(13, 0); rd_sq(545, 124, 44, 30, c2r == 1, TEAL, "8") ? setv(13, 1); rd_sq(592, 124, 44, 30, c2r == 2, TEAL, "12") ? setv(13, 2); rd_sq(639, 124, 44, 30, c2r == 3, TEAL, "20") ? setv(13, 3); rd_sq(498, 160, 185, 30, c2r == 4, COP, "ALL") ? setv(13, c2r == 4 ? 0 : 4); sprintf(#v, "%.1f dB", slider12); dy_knob(566, 368, 22, 12, -40, 0, -14, 0, TEAL, "THRESH", #v); sprintf(#v, "%.0f us", slider14); dy_knob(566, 268, 22, 14, 20, 800, 400, 1, SAND, "ATTACK", #v); sprintf(#v, "%.0f ms", slider15); dy_knob(650, 268, 22, 15, 50, 1100, 300, 1, SAND, "RELEASE", #v); sprintf(#v, "+%.1f dB", slider16); dy_knob(650, 368, 22, 16, 0, 24, 2, 0, TEAL, "MAKEUP", #v); bt_grh(510, 470, d2_gr); !c2_on ? bt_dim(492, CY + 50, 194, CH - 56); // TILT: the big knurled knob on the line between COMP 1 and COMP 2 (where it sits in the signal) ui_cola(0x000000, 0.35); gfx_circle(477 * sc, 302 * sc, 51 * sc, 1, 1); ui_col(FACE); gfx_circle(476 * sc, 300 * sc, 50 * sc, 1, 1); ui_cola(COP, 0.6); gfx_circle(476 * sc, 300 * sc, 50 * sc, 0, 1); dy_kst = 1; dy_ntick = 13; sprintf(#v, "%+.1f dB", slider11); dy_knob(476, 300, 36, 11, -6, 6, 0, 0, COP, "", ""); dy_kst = 7; dy_ntick = 11; // its name on a small dark plate under the ring (the value shows there while the knob is touched) ui_col(0x000000); rd_rr(439 * sc, 359 * sc, 76 * sc, 28 * sc, 5 * sc); ui_col(0x071A1D); rd_rr(440 * sc, 360 * sc, 74 * sc, 26 * sc, 4 * sc); gfx_setfont(6, "Trebuchet MS", (dy_act ? 13 : 17) * sc, 'b'); ui_col(dy_act ? COP : INK); ui_text(476 * sc, (dy_act ? 366 : 363) * sc, dy_act ? #v : "TILT"); // 4 PUNCH + LIMITER bt_sect(702, CY, 210, CH, "SUM", c2_on); sprintf(#v, "%.0f / %.0f", 100 - slider17, slider17); dy_knob(807, 168, 28, 17, 0, 100, 60, 0, COP, "BALANCE", #v); gfx_setfont(6, "Trebuchet MS", 15 * sc, 'b'); ui_col(INK); ui_text(760 * sc, 186 * sc, "1"); ui_text(854 * sc, 186 * sc, "2"); !c2_on ? bt_dim(710, CY + 30, 194, 150); ui_cola(INK, 0.15); gfx_rect(716 * sc, 250 * sc, 182 * sc, 1 * sc); bt_title(714, 262, "4 BUS COMP", li_on ? 1 : 0.45); bt_in(860, 258, 36); sprintf(#v, "%.1f dB", slider18); dy_knob(762, 450, 22, 18, -30, 0, -18, 0, TEAL, "THRESH", #v); sprintf(#v, "+%.1f dB", slider38); dy_knob(852, 450, 22, 38, 0, 18, 5, 0, TEAL, "MAKEUP", #v); dy_step = 6; dy_steps = ".1|.3|1|3|10|30"; dy_knob(762, 366, 22, 20, 0, 5, 4, 0, SAND, "ATTACK", "ms"); dy_step = 5; dy_steps = ".1|.3|.6|1.2|A"; dy_knob(852, 366, 22, 37, 0, 4, 4, 0, SAND, "RELEASE", floor(slider37 + 0.5) == 4 ? "AUTO" : "s"); b3r = floor(slider19 + 0.5); rd_sq(716, 290, 58, 26, b3r == 0, TEAL, "2") ? setv(19, 0); rd_sq(778, 290, 58, 26, b3r == 1, TEAL, "4") ? setv(19, 1); rd_sq(840, 290, 58, 26, b3r == 2, TEAL, "10") ? setv(19, 2); bt_grh(730, 518, d3_gr); !li_on ? bt_dim(710, 288, 194, CH - 216); // 5 WIDTH / MIX / OUTPUT + meters bt_sect(920, CY, 220, CH, "5 OUTPUT", 1); sprintf(#v, "%.0f%%", slider21); dy_knob(980, 168, 24, 21, 0, 200, 100, 0, TEAL, "WIDTH", #v); sprintf(#v, "%.0f%%", slider22); dy_knob(1080, 168, 24, 22, 0, 100, 0, 0, COP, "S SAT", #v); sprintf(#v, "%.0f%%", slider23); dy_knob(980, 278, 24, 23, 0, 100, 100, 0, SAND, "MIX", #v); sprintf(#v, "%+.1f dB", slider24); dy_knob(1080, 278, 28, 24, -24, 12, 0, 0, 0x2A3436, "OUTPUT", #v); pk_lcd(934 * sc, 352 * sc, 192 * sc, 64 * sc, 0); pk_lcdfont(9, 11 * sc); ui_col(pk_lcdc); sprintf(#gl, "COMP 1 %4.1f dB", d1_gr); gfx_x = 946 * sc; gfx_y = 358 * sc; gfx_drawstr(#gl); sprintf(#gl, "COMP 2 %4.1f dB", d2_gr); gfx_x = 946 * sc; gfx_y = 376 * sc; gfx_drawstr(#gl); sprintf(#gl, "BUS %4.1f dB", d3_gr); gfx_x = 946 * sc; gfx_y = 394 * sc; gfx_drawstr(#gl); ui_col(0x061416); gfx_rect(934 * sc, 428 * sc, 128 * sc, 110 * sc); mI.ui_ledm(942 * sc, 434 * sc, 26 * sc, 86 * sc, pkI); mL.ui_ledm(988 * sc, 434 * sc, 26 * sc, 86 * sc, pkL); mR.ui_ledm(1026 * sc, 434 * sc, 26 * sc, 86 * sc, pkR); rd_lbl(955, 524, "IN", INK, 0.8); rd_lbl(1020, 524, "OUT", INK, 0.8); bt_limkey(1084, 498); pkI *= 0.85; pkL *= 0.85; pkR *= 0.85; // the meters fall back smoothly between redraws d1_gr = max(gr1, d1_gr * 0.85); d2_gr = max(gr2, d2_gr * 0.85); d3_gr = max(gr3, d3_gr * 0.85); gr1 = 0; gr2 = 0; gr3 = 0; pk_end(); ui_end();