desc:GFX Quad11 //tags: multiband compressor bus eq saturation dynamics //author: JGWizrad / Garden FX (GFX) // // Requires gfx-drift, gfx-input, gfx-output, gfx-ui, gfx-panel and gfx-instrument (.jsfx-inc) in the same folder // (gfx-instrument is used only for the shared OUTPUT tray and button). // // Four-band bus compressor with an EQ and a saturator in every band. // SPLIT - three Linkwitz-Riley 24 dB/oct crossovers (drag the lines in the display). The bands sum back // flat, and the dry signal for MIX goes through the same phase shift, so parallel mixing is clean. // EQ - a bell in each band: FREQ, WIDTH (Q), GAIN (drag the dots in the display; wheel = width). // COMP - THRESH: below zero compresses (the threshold, in dBFS), above zero expands (lifts the peaks // over -THRESH dBFS by up to 6 dB, for punch). RATIO sets how hard either works. ATTACK and // RELEASE are FAST / SLOW, with times suited to each band. Stereo-linked, 6 dB soft knee. // The level is made up automatically: three quarters of the average gain change, followed slowly // (about 1.5 s), so the band keeps its loudness while the peaks are still held down (or lifted). // SAT - TAPE (asymmetric tanh, even + odd harmonics), TUBE (one side saturates earlier: warmer, mostly // even) or CLIP (hard knee, odd harmonics). DRIVE is level-matched, 2x oversampled while any // band is driven (with no drive anywhere the plugin is fully transparent). // PRE: SAT > EQ > COMP (the compressor tames what the saturation adds) // POST: EQ > COMP > SAT (the compressor steadies the level going into the saturation) // SOLO / BYP - listen to a band on its own / let it through unprocessed. // MASTER EQ - HPF, low shelf, high shelf, LPF on the whole bus, ahead of the split. // PRESETS - starting points for common bus jobs (they set the split, the bands, the master EQ and MIX). // MIX - parallel compression: blends the processed bus with the dry one. // MASTER holds SAT and the OUT stage (OFF / CLIP / LIM; click its name to step it); INPUT has TRIM and GUARD. 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-instrument.jsfx-inc slider1:0<-24,12,0.1>-Input trim (dB) slider2:0<0,1,1{Off,On}>-Input guard slider3:100<0,100,1>-Mix (%) slider4:0<-24,12,0.1>-Output (dB) slider5:0<0,100,1>-Band sat (%) slider6:30<0,100,1>-Bias (%) slider7:120<40,400,1>-Sat low band (Hz) slider8:6000<2000,16000,10>-Sat high band (Hz) slider9:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider10:1<0,2,1{Off,Clip,Limit}>-Output stage slider11:120<30,16000,1>-Crossover 1 (Hz) slider12:1000<30,16000,1>-Crossover 2 (Hz) slider13:5000<30,16000,1>-Crossover 3 (Hz) slider14:20<20,500,1>-HPF (Hz, 20 = off) slider15:100<30,500,1>-Low shelf freq (Hz) slider16:0<-12,12,0.1>-Low shelf gain (dB) slider17:8000<2000,16000,10>-High shelf freq (Hz) slider18:0<-12,12,0.1>-High shelf gain (dB) slider19:20000<2000,20000,10>-LPF (Hz, 20000 = off) slider20:0<0,1,1{Pre,Post}>-Master EQ position (no control - kept for old projects) slider21:60<20,20000,1>-Low: EQ freq (Hz) slider22:0.8<0.3,6,0.01>-Low: EQ width (Q) slider23:0<-12,12,0.1>-Low: EQ gain (dB) slider24:0<-48,48,0.1>-Low: threshold (dB; - compress / + expand) slider25:2<1,20,0.1>-Low: ratio slider26:40<0.1,100,0.1>-Low: attack (ms) slider27:150<10,2000,1>-Low: release (ms) slider28:0<-12,24,0.1>-Low: (unused - was makeup) slider29:0<0,2,1{Tape,Tube,Clip}>-Low: sat type slider30:0<0,100,1>-Low: drive (%) slider31:1<0,1,1{Pre,Post}>-Low: sat position slider32:0<0,2,1{Active,Bypass,Solo}>-Low: state slider33:400<20,20000,1>-Lo-mid: EQ freq (Hz) slider34:0.8<0.3,6,0.01>-Lo-mid: EQ width (Q) slider35:0<-12,12,0.1>-Lo-mid: EQ gain (dB) slider36:0<-48,48,0.1>-Lo-mid: threshold (dB; - compress / + expand) slider37:2<1,20,0.1>-Lo-mid: ratio slider38:30<0.1,100,0.1>-Lo-mid: attack (ms) slider39:120<10,2000,1>-Lo-mid: release (ms) slider40:0<-12,24,0.1>-Lo-mid: (unused - was makeup) slider41:0<0,2,1{Tape,Tube,Clip}>-Lo-mid: sat type slider42:0<0,100,1>-Lo-mid: drive (%) slider43:1<0,1,1{Pre,Post}>-Lo-mid: sat position slider44:0<0,2,1{Active,Bypass,Solo}>-Lo-mid: state slider45:2500<20,20000,1>-Hi-mid: EQ freq (Hz) slider46:0.8<0.3,6,0.01>-Hi-mid: EQ width (Q) slider47:0<-12,12,0.1>-Hi-mid: EQ gain (dB) slider48:0<-48,48,0.1>-Hi-mid: threshold (dB; - compress / + expand) slider49:2<1,20,0.1>-Hi-mid: ratio slider50:20<0.1,100,0.1>-Hi-mid: attack (ms) slider51:90<10,2000,1>-Hi-mid: release (ms) slider52:0<-12,24,0.1>-Hi-mid: (unused - was makeup) slider53:0<0,2,1{Tape,Tube,Clip}>-Hi-mid: sat type slider54:0<0,100,1>-Hi-mid: drive (%) slider55:1<0,1,1{Pre,Post}>-Hi-mid: sat position slider56:0<0,2,1{Active,Bypass,Solo}>-Hi-mid: state slider57:10000<20,20000,1>-High: EQ freq (Hz) slider58:0.8<0.3,6,0.01>-High: EQ width (Q) slider59:0<-12,12,0.1>-High: EQ gain (dB) slider60:0<-48,48,0.1>-High: threshold (dB; - compress / + expand) slider61:2<1,20,0.1>-High: ratio slider62:10<0.1,100,0.1>-High: attack (ms) slider63:60<10,2000,1>-High: release (ms) slider64:0<-12,24,0.1>-High: (unused - was makeup) slider65:0<0,2,1{Tape,Tube,Clip}>-High: sat type slider66:0<0,100,1>-High: drive (%) slider67:1<0,1,1{Pre,Post}>-High: sat position slider68:0<0,2,1{Active,Bypass,Solo}>-High: state in_pin:left input in_pin:right input out_pin:left output out_pin:right output @init #ui_man = "gfx-quad11"; // the ? pop-up links to this manual // ---------- biquads (RBJ); fs = the rate the filter runs at ---------- // type: 0 low-pass, 1 high-pass, 2 peak, 3 high shelf, 4 low shelf, 5 all-pass function bq_set(type, f, q, gdb, fs) instance(b0, b1, b2, a1, a2) local(w0, cw, sw, al, amp, sa, n) ( w0 = 2 * $pi * min(f, fs * 0.45) / fs; cw = cos(w0); sw = sin(w0); al = sw / (2 * q); amp = 10 ^ (gdb / 40); type == 0 ? ( n = 1 + al; b0 = (1 - cw) * 0.5 / n; b1 = (1 - cw) / n; b2 = b0; a1 = -2 * cw / n; a2 = (1 - al) / n; ) : type == 1 ? ( n = 1 + al; b0 = (1 + cw) * 0.5 / n; b1 = -(1 + cw) / n; b2 = b0; a1 = -2 * cw / n; a2 = (1 - al) / n; ) : type == 2 ? ( n = 1 + al / amp; b0 = (1 + al * amp) / n; b1 = -2 * cw / n; b2 = (1 - al * amp) / n; a1 = b1; a2 = (1 - al / amp) / n; ) : type == 3 ? ( sa = 2 * sqrt(amp) * al; n = (amp + 1) - (amp - 1) * cw + sa; b0 = amp * ((amp + 1) + (amp - 1) * cw + sa) / n; b1 = -2 * amp * ((amp - 1) + (amp + 1) * cw) / n; b2 = amp * ((amp + 1) + (amp - 1) * cw - sa) / n; a1 = 2 * ((amp - 1) - (amp + 1) * cw) / n; a2 = ((amp + 1) - (amp - 1) * cw - sa) / n; ) : type == 4 ? ( sa = 2 * sqrt(amp) * al; n = (amp + 1) + (amp - 1) * cw + sa; b0 = amp * ((amp + 1) - (amp - 1) * cw + sa) / n; b1 = 2 * amp * ((amp - 1) - (amp + 1) * cw) / n; b2 = amp * ((amp + 1) - (amp - 1) * cw - sa) / n; a1 = -2 * ((amp - 1) + (amp + 1) * cw) / n; a2 = ((amp + 1) + (amp - 1) * cw - sa) / n; ) : ( n = 1 + al; b0 = (1 - al) / n; b1 = -2 * cw / n; b2 = 1; a1 = b1; a2 = b0; ); ); function bq_run(x) instance(b0, b1, b2, a1, a2, x1, x2, y1, y2) local(y) ( y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; x2 = x1; x1 = x; y2 = y1; y1 = y; y; ); // magnitude at w = 2 pi f / srate (for the display) function bq_mag(w) instance(b0, b1, b2, a1, a2) local(c1, c2, s1, s2, nr, ni, dr, di) ( c1 = cos(w); c2 = cos(2 * w); s1 = sin(w); s2 = sin(2 * w); nr = b0 + b1 * c1 + b2 * c2; ni = b1 * s1 + b2 * s2; dr = 1 + a1 * c1 + a2 * c2; di = a1 * s1 + a2 * s2; sqrt((nr * nr + ni * ni) / max(dr * dr + di * di, 0.000000000001)); ); // ---------- crossover (one channel) ---------- // split at f2, then the low half at f1 and the high half at f3. Each half gets the other split's all-pass, // so all four bands share the same phase and sum back flat; the dry signal gets all three all-passes. function xo_set(f1, f2, f3) instance(la1, la2, ha1, ha2, lb1, lb2, hb1, hb2, lc1, lc2, hc1, hc2, ap1, ap3, d1, d2, d3) ( la1.bq_set(0, f2, 0.70710678, 0, srate); la2.bq_set(0, f2, 0.70710678, 0, srate); ha1.bq_set(1, f2, 0.70710678, 0, srate); ha2.bq_set(1, f2, 0.70710678, 0, srate); lb1.bq_set(0, f1, 0.70710678, 0, srate); lb2.bq_set(0, f1, 0.70710678, 0, srate); hb1.bq_set(1, f1, 0.70710678, 0, srate); hb2.bq_set(1, f1, 0.70710678, 0, srate); lc1.bq_set(0, f3, 0.70710678, 0, srate); lc2.bq_set(0, f3, 0.70710678, 0, srate); hc1.bq_set(1, f3, 0.70710678, 0, srate); hc2.bq_set(1, f3, 0.70710678, 0, srate); ap1.bq_set(5, f1, 0.70710678, 0, srate); ap3.bq_set(5, f3, 0.70710678, 0, srate); d1.bq_set(5, f1, 0.70710678, 0, srate); d2.bq_set(5, f2, 0.70710678, 0, srate); d3.bq_set(5, f3, 0.70710678, 0, srate); ); function xo_run(x, xd) instance(la1, la2, ha1, ha2, lb1, lb2, hb1, hb2, lc1, lc2, hc1, hc2, ap1, ap3, d1, d2, d3, o1, o2, o3, o4, od) local(lo, hi) ( lo = ap3.bq_run(la2.bq_run(la1.bq_run(x))); hi = ap1.bq_run(ha2.bq_run(ha1.bq_run(x))); o1 = lb2.bq_run(lb1.bq_run(lo)); o2 = hb2.bq_run(hb1.bq_run(lo)); o3 = lc2.bq_run(lc1.bq_run(hi)); o4 = hc2.bq_run(hc1.bq_run(hi)); od = d3.bq_run(d2.bq_run(d1.bq_run(xd))); ); // ---------- master EQ (one channel); the filters keep running when off, so switching them on is clean ---------- function meq_set() instance(hp, ls, hs, lp) ( hp.bq_set(1, slider14, 0.70710678, 0, srate); ls.bq_set(4, slider15, 0.70710678, slider16, srate); hs.bq_set(3, slider17, 0.70710678, slider18, srate); lp.bq_set(0, slider19, 0.70710678, 0, srate); ); function meq_run(x) instance(hp, ls, hs, lp) local(y) ( y = hp.bq_run(x); x = meq_hp ? y : x; x = hs.bq_run(ls.bq_run(x)); y = lp.bq_run(x); meq_lp ? y : x; ); function meq_mag(w) instance(hp, ls, hs, lp) ( (meq_hp ? hp.bq_mag(w) : 1) * ls.bq_mag(w) * hs.bq_mag(w) * (meq_lp ? lp.bq_mag(w) : 1); ); // ---------- saturation ---------- TB15 = ost_tanh(0.15); function sat_curve(u, t) local(a) ( t == 0 ? ost_tanh(u + 0.15) - TB15 : // tape: biased tanh t == 1 ? ( u >= 0 ? ost_tanh(u) : ost_tanh(1.6 * u) / 1.6 ) : // tube: the negative side gives way first ( a = abs(u); a < 0.8 ? u : sign(u) * (0.8 + 0.2 * ost_tanh((a - 0.8) / 0.2)) ); // clip: hard knee ); // 4th-order Butterworth anti-alias filters either side of the curve, at twice the rate function sat_init() instance(u1, u2, d1, d2, dcx, dcy) local(fc, fs2) ( fs2 = 2 * srate; fc = 0.46 * srate; u1.bq_set(0, fc, 0.5411961, 0, fs2); u2.bq_set(0, fc, 1.3065630, 0, fs2); d1.bq_set(0, fc, 0.5411961, 0, fs2); d2.bq_set(0, fc, 1.3065630, 0, fs2); dcx = 0; dcy = 0; ); // g = drive into the curve, c = level match; with off set the curve is skipped (same filters, same timing). // While no band is driven (os_on = 0) the filters keep running but are left out, so the plugin is transparent. function sat_run(x, g, c, t, off) instance(u1, u2, d1, d2, dcx, dcy) local(z, y) ( z = u2.bq_run(u1.bq_run(x * 2)); d2.bq_run(d1.bq_run(off ? z : sat_curve(g * z, t) * c)); z = u2.bq_run(u1.bq_run(0)); y = d2.bq_run(d1.bq_run(off ? z : sat_curve(g * z, t) * c)); !os_on ? x : off ? y : ( dcy = y - dcx + 0.9995 * dcy; dcx = y; dcy; ); // the asymmetric curves add DC ); // ---------- one band (stereo) ---------- function band_init() instance(sL, sR, env, grm, amk) ( sL.sat_init(); sR.sat_init(); env = 0; grm = 0; amk = 0; ); function band_set(base) instance(eqL, eqR, thr, rat, atc, rlc, mkg, st, sg, smk, soff, pre, state) local(d) ( eqL.bq_set(2, slider(base), slider(base + 1), slider(base + 2), srate); eqR.bq_set(2, slider(base), slider(base + 1), slider(base + 2), srate); thr = slider(base + 3); rat = max(slider(base + 4), 1); atc = 1 - exp(-1 / (max(slider(base + 5), 0.05) * 0.001 * srate)); rlc = 1 - exp(-1 / (max(slider(base + 6), 1) * 0.001 * srate)); mkg = 1; st = slider(base + 8); d = slider(base + 9); sg = 0.25 * 10 ^ (d * 0.3 / 20); // drive: 0 .. +30 dB into the curve smk = 0.5 / (sat_curve(sg * 0.25, st) - sat_curve(-sg * 0.25, st)); // level-matched at -12 dBFS soff = d <= 0; pre = slider(base + 10) == 0; state = slider(base + 11); ); function band_run(l, r) instance(eqL, eqR, sL, sR, thr, rat, atc, rlc, mkg, st, sg, smk, soff, pre, state, env, grm, amk, ol, orr) local(lv, over, gr, g) ( // bypassed: through the oversampling filters only, so it stays in time with the other bands state == 1 ? ( ol = sL.sat_run(l, 1, 1, 0, 1); orr = sR.sat_run(r, 1, 1, 0, 1); env = 0; ) : ( pre ? ( l = sL.sat_run(l, sg, smk, st, soff); r = sR.sat_run(r, sg, smk, st, soff); ); l = eqL.bq_run(l); r = eqR.bq_run(r); // dynamics: stereo-linked peak level, 6 dB soft knee. THRESH < 0 compresses above THRESH dBFS; // THRESH > 0 expands upward above -THRESH dBFS (peaks lifted, at most 6 dB). Attack / release on the gain. lv = 20 * log10(max(max(abs(l), abs(r)), 0.000001)); over = lv - (thr < 0 ? thr : -thr); gr = over < -3 ? 0 : over > 3 ? (1 - 1 / rat) * over : (1 - 1 / rat) * (over + 3) * (over + 3) / 12; gr = thr < 0 ? -gr : thr > 0 ? min(gr, 6) : 0; env += (gr - env) * (abs(gr) > abs(env) ? atc : rlc); abs(env) > abs(grm) ? grm = env; amk += (env - amk) * amc; // auto makeup follows the average gain change g = 10 ^ ((env - 0.75 * amk) / 20); l *= g; r *= g; !pre ? ( l = sL.sat_run(l, sg, smk, st, soff); r = sR.sat_run(r, sg, smk, st, soff); ); ol = l * mkg; orr = r * mkg; ); ); function band_mag(w) instance(eqL) ( eqL.bq_mag(w); ); // ---------- presets ---------- // FAST / SLOW attack and release times per band (ms) function att_t(k, slow) ( slow ? (k == 0 ? 40 : k == 1 ? 30 : k == 2 ? 20 : 10) : (k == 0 ? 10 : k == 1 ? 6 : k == 2 ? 3 : 1) ); function rel_t(k, slow) ( slow ? (k == 0 ? 600 : k == 1 ? 450 : k == 2 ? 350 : 250) : (k == 0 ? 150 : k == 1 ? 120 : k == 2 ? 90 : 60) ); function is_slow_a(k, v) ( v > sqrt(att_t(k, 0) * att_t(k, 1)) ); function is_slow_r(k, v) ( v > sqrt(rel_t(k, 0) * rel_t(k, 1)) ); // one band: EQ freq / width / gain, thresh, ratio, attack + release (0 fast, 1 slow), sat type, drive, sat position function pb(k, f, q, g, t, rt, sa, sr, ty, dr, ps) local(b) ( b = 21 + k * 12; slider(b) = f; slider(b + 1) = q; slider(b + 2) = g; slider(b + 3) = t; slider(b + 4) = rt; slider(b + 5) = att_t(k, sa); slider(b + 6) = rel_t(k, sr); slider(b + 7) = 0; slider(b + 8) = ty; slider(b + 9) = dr; slider(b + 10) = ps; slider(b + 11) = 0; ); // split, master EQ (HPF, lo freq, lo gain, hi freq, hi gain, LPF), mix function pm(x1, x2, x3, hp, lf, lg, hf, hg, lp, mx) ( slider11 = x1; slider12 = x2; slider13 = x3; slider14 = hp; slider15 = lf; slider16 = lg; slider17 = hf; slider18 = hg; slider19 = lp; slider20 = 0; slider3 = mx; ); function load_preset(p) local(i) ( p == 1 ? ( // Gentle Glue pm(120, 1000, 5000, 20, 100, 0, 8000, 0, 20000, 100); pb(0, 60, 0.8, 0, -14, 1.5, 1, 1, 0, 0, 1); pb(1, 400, 0.8, 0, -14, 1.5, 1, 1, 0, 0, 1); pb(2, 2500, 0.8, 0, -14, 1.5, 1, 1, 0, 0, 1); pb(3, 10000, 0.8, 0, -14, 1.5, 1, 1, 0, 0, 1); ) : p == 2 ? ( // Mix Bus Punch pm(120, 1000, 5000, 25, 100, 0, 8000, 0, 20000, 100); pb(0, 60, 0.8, 0, -18, 3, 1, 0, 0, 20, 1); pb(1, 400, 0.8, 0, -16, 2.5, 1, 0, 0, 0, 1); pb(2, 2500, 0.8, 0, -16, 2.5, 1, 0, 0, 0, 1); pb(3, 10000, 0.8, 0, -14, 2, 1, 0, 0, 0, 1); ) : p == 3 ? ( // Drum Bus Smash (parallel) pm(100, 800, 6000, 30, 80, 1.5, 10000, 1.5, 20000, 45); pb(0, 60, 0.8, 0, -30, 6, 0, 0, 0, 30, 1); pb(1, 400, 0.8, 0, -30, 6, 0, 0, 2, 20, 1); pb(2, 2500, 0.8, 0, -30, 6, 0, 0, 2, 20, 1); pb(3, 10000, 0.8, 0, -28, 5, 0, 0, 0, 10, 1); ) : p == 4 ? ( // Transient Lift (expand) pm(120, 1000, 5000, 20, 100, 0, 8000, 0, 20000, 100); pb(0, 60, 0.8, 0, 18, 2, 0, 0, 0, 0, 1); pb(1, 400, 0.8, 0, 14, 2, 0, 0, 0, 0, 1); pb(2, 2500, 0.8, 0, 18, 2, 0, 0, 0, 0, 1); pb(3, 10000, 0.8, 0, 12, 1.5, 0, 0, 0, 0, 1); ) : p == 5 ? ( // Vocal Control (tames harshness and esses) pm(200, 2000, 7000, 80, 200, 0, 10000, 1, 20000, 100); pb(0, 150, 0.8, -1, -20, 2, 1, 1, 0, 0, 1); pb(1, 450, 1.2, 0, -20, 3, 1, 0, 0, 0, 1); pb(2, 3500, 1.5, 0, -24, 4, 0, 0, 0, 0, 1); pb(3, 8000, 1, 0, -28, 6, 0, 0, 0, 0, 1); ) : p == 6 ? ( // Bass Tighten pm(80, 250, 2000, 30, 60, 0, 8000, 0, 20000, 100); pb(0, 50, 0.8, 1, -20, 4, 1, 0, 1, 25, 0); pb(1, 160, 1, -1.5, -18, 3, 0, 0, 1, 15, 0); pb(2, 800, 0.8, 0, -16, 2, 0, 0, 0, 0, 1); pb(3, 4000, 0.8, 0, 0, 2, 0, 0, 0, 0, 1); ) : p == 7 ? ( // De-Mud & Air pm(150, 800, 6000, 25, 100, 0, 12000, 1.5, 20000, 100); pb(0, 80, 0.8, 0, -16, 2, 1, 1, 0, 0, 1); pb(1, 350, 1, -3, -18, 2, 1, 0, 0, 0, 1); pb(2, 2500, 0.8, 0, -16, 2, 1, 0, 0, 0, 1); pb(3, 12000, 0.7, 2, -20, 1.5, 0, 0, 0, 0, 1); ) : p == 8 ? ( // Tape Warmth (no compression) pm(120, 1000, 5000, 20, 100, 0, 8000, 0, 20000, 100); pb(0, 60, 0.8, 0, 0, 2, 1, 1, 0, 30, 1); pb(1, 400, 0.8, 0, 0, 2, 1, 1, 0, 20, 1); pb(2, 2500, 0.8, 0, 0, 2, 1, 1, 0, 15, 1); pb(3, 10000, 0.8, 0, 0, 2, 1, 1, 0, 10, 1); ) : p == 9 ? ( // Master Gentle pm(100, 900, 6000, 20, 100, 0, 8000, 0, 20000, 100); pb(0, 60, 0.8, 0, -10, 1.5, 1, 1, 0, 8, 1); pb(1, 400, 0.8, 0, -10, 1.4, 1, 1, 0, 0, 1); pb(2, 2500, 0.8, 0, -10, 1.4, 1, 1, 0, 0, 1); pb(3, 10000, 0.8, 0, -8, 1.3, 1, 1, 0, 0, 1); ) : p == 10 ? ( // Parallel Crush pm(120, 1000, 5000, 20, 100, 0, 8000, 0, 20000, 35); pb(0, 60, 0.8, 0, -36, 10, 0, 0, 2, 40, 1); pb(1, 400, 0.8, 0, -36, 10, 0, 0, 2, 40, 1); pb(2, 2500, 0.8, 0, -36, 10, 0, 0, 2, 40, 1); pb(3, 10000, 0.8, 0, -36, 10, 0, 0, 2, 30, 1); ) : p == 11 ? ( // Reset all to defaults pm(120, 1000, 5000, 20, 100, 0, 8000, 0, 20000, 100); pb(0, 60, 0.8, 0, 0, 2, 1, 0, 0, 0, 1); pb(1, 400, 0.8, 0, 0, 2, 1, 0, 0, 0, 1); pb(2, 2500, 0.8, 0, 0, 2, 1, 0, 0, 0, 1); pb(3, 10000, 0.8, 0, 0, 2, 1, 0, 0, 0, 1); slider1 = 0; slider2 = 0; slider4 = 0; slider5 = 0; slider6 = 30; slider7 = 120; slider8 = 6000; slider9 = -0.3; slider10 = 1; ); p >= 1 && p <= 11 ? ( i = 1; loop(68, ui_auto(i); i += 1; ); ); ); function update() ( xf1 = min(max(slider11, 30), srate * 0.4); xf2 = min(max(slider12, xf1 * 1.25), srate * 0.42); xf3 = min(max(slider13, xf2 * 1.25), srate * 0.44); xL.xo_set(xf1, xf2, xf3); xR.xo_set(xf1, xf2, xf3); meq_hp = slider14 > 20.5; meq_lp = slider19 < 19990; meq_post = slider20; // no control: new instances and the presets keep it at PRE meL.meq_set(); meR.meq_set(); bnd0.band_set(21); bnd1.band_set(33); bnd2.band_set(45); bnd3.band_set(57); os_on = slider30 > 0 || slider42 > 0 || slider54 > 0 || slider66 > 0; any_solo = bnd0.state == 2 || bnd1.state == 2 || bnd2.state == 2 || bnd3.state == 2; mix_t = any_solo ? 1 : slider3 / 100; // a soloed band is heard on its own, without the dry signal out_t = 10 ^ (slider4 / 20); in_setup(slider1, slider2); ins_output_setup(5); ); in_init(); ost_init(20000); ostL.ost_ch_init(); ostR.ost_ch_init(); bnd0.band_init(); bnd1.band_init(); bnd2.band_init(); bnd3.band_init(); dsL.sat_init(); dsR.sat_init(); sm = 1 - exp(-1 / (0.03 * srate)); amc = 1 - exp(-1 / (1.5 * srate)); pkI = 0; pkO = 0; !ui_dflt ? ui_thm = 5; !ui_dflt ? ui_hw = 1; // fixed charcoal faceplate !ui_dflt ? ui_kstyle = 0; PNL = 0x2A2B2F; INKL = 0xE8E6E0; ACC = 0xD9A441; REDL = 0xFF3A2A; AMBL = 0xFFB030; SH1 = 0x232428; SH2 = 0x202125; SH3 = 0x1C1D21; BC0 = 0xE0604A; BC1 = 0xE8B23A; BC2 = 0x5FBF7A; BC3 = 0x4A9AD8; // band colours update(); mix_s = mix_t; out_s = out_t; grd = 1000; memset(grd, 0, 4); // displayed gain reduction per band // display mapping: 20 Hz - 20 kHz across, +/-15 dB up and down function f2x(f) ( gx0 + gw * log(max(f, 20) / 20) / log(1000); ); function x2f(x) ( 20 * 1000 ^ (min(max((x - gx0) / gw, 0), 1)); ); function db2y(d) ( gmid - d * gh / 30; ); function qgroup(x, y, w, h, title, col, shade) ( pk_inset(x * sc, y * sc, w * sc, h * sc, shade); pk_box(x * sc, y * sc, w * sc, h * sc, col, title); C_INK = INKL; ); function fmt_hz(dst, f) ( f >= 1000 ? sprintf(dst, "%.1fk", f / 1000) : sprintf(dst, "%d", f); ); // one band strip (unscaled layout coords): SOLO / BYP and the band's range, a gain-reduction bar, // then EQ, COMPRESSOR and SATURATION, each under its own caption function cap(x, y, w, t, col) local(tw, th) ( gfx_setfont(4); gfx_measurestr(t, tw, th); ui_cola(col, 0.85); gfx_x = (x + 10) * sc; gfx_y = (y - 5) * sc; gfx_drawstr(t); ui_cola(col, 0.25); gfx_line((x + 10) * sc + tw + 6 * sc, y * sc, (x + w - 10) * sc, y * sc); ); function strip(x, y, k, base, col, name) local(st, gr, nm1, nm2, sa, sr) ( qgroup(x, y, 206, 362, name, col, k % 2 ? SH2 : SH1); st = slider(base + 11); pk_ledbtn((x + 10) * sc, (y + 12) * sc, 40 * sc, 26 * sc, st == 2, AMBL) ? ( slider(base + 11) = st == 2 ? 0 : 2; ui_auto(base + 11); ); pk_ledbtn((x + 56) * sc, (y + 12) * sc, 40 * sc, 26 * sc, st == 1, REDL) ? ( slider(base + 11) = st == 1 ? 0 : 1; ui_auto(base + 11); ); gfx_setfont(4); ui_col(INKL); ui_text((x + 30) * sc, (y + 40) * sc, "SOLO"); ui_text((x + 76) * sc, (y + 40) * sc, "BYP"); // the band's range k == 0 ? ( fmt_hz(#bq1, xf1); sprintf(#brg, "< %s Hz", #bq1); ) : k == 3 ? ( fmt_hz(#bq1, xf3); sprintf(#brg, "> %s Hz", #bq1); ) : ( fmt_hz(#bq1, k == 1 ? xf1 : xf2); fmt_hz(#bq2, k == 1 ? xf2 : xf3); sprintf(#brg, "%s-%s Hz", #bq1, #bq2); ); ui_cola(col, 0.9); gfx_measurestr(#brg, nm1, nm2); gfx_x = (x + 196) * sc - nm1; gfx_y = (y + 19) * sc; gfx_drawstr(#brg); // gain reduction: a bar that grows from the left (0 .. 24 dB) and its value gr = k == 0 ? bnd0.grm : k == 1 ? bnd1.grm : k == 2 ? bnd2.grm : bnd3.grm; k == 0 ? bnd0.grm = 0 : k == 1 ? bnd1.grm = 0 : k == 2 ? bnd2.grm = 0 : bnd3.grm = 0; grd[k] = abs(gr) > abs(grd[k] * 0.85) ? gr : grd[k] * 0.85; ui_col(0x0A0A0B); gfx_rect((x + 10) * sc, (y + 56) * sc, 150 * sc, 7 * sc); ui_cola(col, 0.9); gfx_rect((x + 10) * sc, (y + 56) * sc, min(abs(grd[k]) / (grd[k] > 0 ? 6 : 24), 1) * 150 * sc, 7 * sc); grd[k] > 0.05 ? sprintf(#grs, "EXP +%.1f", grd[k]) : sprintf(#grs, "GR %.1f", grd[k]); gfx_setfont(4); ui_cola(INKL, 0.8); gfx_measurestr(#grs, nm1, nm2); gfx_x = (x + 196) * sc - nm1; gfx_y = (y + 54) * sc; gfx_drawstr(#grs); // EQ cap(x, y + 80, 206, "EQ", col); ui_section(3); C_INK = INKL; ui_knob((x + 40) * sc, (y + 108) * sc, base, 20, 20000, k == 0 ? 60 : k == 1 ? 400 : k == 2 ? 2500 : 10000, 1, "FREQ", "%.0f Hz"); ui_knob((x + 103) * sc, (y + 108) * sc, base + 1, 0.3, 6, 0.8, 1, "WIDTH", "Q %.2f"); ui_knob((x + 166) * sc, (y + 108) * sc, base + 2, -12, 12, 0, 0, "GAIN", "%+.1f dB"); // COMPRESSOR cap(x, y + 154, 206, "COMPRESSOR", col); ui_section(4); C_INK = INKL; ui_knob((x + 56) * sc, (y + 190) * sc, base + 3, -48, 48, 0, 0, "THRESH", slider(base + 3) < -0.05 ? "COMP %.1f dB" : slider(base + 3) > 0.05 ? "EXP %.1f" : "OFF"); gfx_setfont(4); ui_cola(INKL, 0.55); ui_text((x + 18) * sc, (y + 206) * sc, "COMP"); ui_text((x + 94) * sc, (y + 206) * sc, "EXP"); ui_knob((x + 150) * sc, (y + 190) * sc, base + 4, 1, 20, 2, 1, "RATIO", "%.1f:1"); // ATTACK / RELEASE: FAST or SLOW (times per band) sa = is_slow_a(k, slider(base + 5)); sr = is_slow_r(k, slider(base + 6)); gfx_setfont(4); ui_col(INKL); ui_text((x + 56) * sc, (y + 236) * sc, "ATTACK"); ui_text((x + 150) * sc, (y + 236) * sc, "RELEASE"); pk_ledbtn((x + 14) * sc, (y + 246) * sc, 40 * sc, 22 * sc, !sa, col) ? ( slider(base + 5) = att_t(k, 0); ui_auto(base + 5); ); pk_ledbtn((x + 58) * sc, (y + 246) * sc, 40 * sc, 22 * sc, sa, col) ? ( slider(base + 5) = att_t(k, 1); ui_auto(base + 5); ); pk_ledbtn((x + 108) * sc, (y + 246) * sc, 40 * sc, 22 * sc, !sr, col) ? ( slider(base + 6) = rel_t(k, 0); ui_auto(base + 6); ); pk_ledbtn((x + 152) * sc, (y + 246) * sc, 40 * sc, 22 * sc, sr, col) ? ( slider(base + 6) = rel_t(k, 1); ui_auto(base + 6); ); gfx_setfont(4); ui_cola(INKL, 0.8); ui_text((x + 34) * sc, (y + 272) * sc, "FAST"); ui_text((x + 78) * sc, (y + 272) * sc, "SLOW"); ui_text((x + 128) * sc, (y + 272) * sc, "FAST"); ui_text((x + 172) * sc, (y + 272) * sc, "SLOW"); // SATURATION cap(x, y + 286, 206, "SATURATION", col); pk_ledbtn((x + 18) * sc, (y + 300) * sc, 44 * sc, 30 * sc, slider(base + 9) > 0, col) ? ( slider(base + 8) = (slider(base + 8) + 1) % 3; ui_auto(base + 8); ); gfx_setfont(4); ui_col(INKL); ui_text((x + 40) * sc, (y + 334) * sc, slider(base + 8) == 0 ? "TAPE" : slider(base + 8) == 1 ? "TUBE" : "CLIP"); ui_section(1); C_INK = INKL; ui_knob((x + 103) * sc, (y + 316) * sc, base + 9, 0, 100, 0, 0, "DRIVE", "%.0f%%"); pk_ledbtn((x + 144) * sc, (y + 300) * sc, 44 * sc, 30 * sc, slider(base + 10) == 0, AMBL) ? ( slider(base + 10) = 1 - slider(base + 10); ui_auto(base + 10); ); gfx_setfont(4); ui_col(INKL); ui_text((x + 166) * sc, (y + 334) * sc, slider(base + 10) == 0 ? "PRE" : "POST"); ); 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(); // knobs moved on the panel do not run @slider, so read them here @serialize file_var(0, ui_sz); file_avail(0) != 0 ? file_var(0, pr_cur); // the preset in use (older projects end before it) @sample in_process(spl0, spl1); pkI = max(pkI, max(abs(in_l), abs(in_r))); l = in_l; r = in_r; !meq_post ? ( l = meL.meq_run(l); r = meR.meq_run(r); ); xL.xo_run(l, in_l); xR.xo_run(r, in_r); bnd0.band_run(xL.o1, xR.o1); bnd1.band_run(xL.o2, xR.o2); bnd2.band_run(xL.o3, xR.o3); bnd3.band_run(xL.o4, xR.o4); // sum: a bypassed band passes as it came, and when any band is soloed only the soloed ones are heard wl = 0; wr = 0; (!any_solo || bnd0.state == 2) ? ( wl += bnd0.ol; wr += bnd0.orr; ); (!any_solo || bnd1.state == 2) ? ( wl += bnd1.ol; wr += bnd1.orr; ); (!any_solo || bnd2.state == 2) ? ( wl += bnd2.ol; wr += bnd2.orr; ); (!any_solo || bnd3.state == 2) ? ( wl += bnd3.ol; wr += bnd3.orr; ); meq_post ? ( wl = meL.meq_run(wl); wr = meR.meq_run(wr); ); // parallel mix: the dry signal has been through the crossover's all-passes and the same oversampling // filters as the bands, so the two line up exactly dl = dsL.sat_run(xL.od, 1, 1, 0, 1); dr = dsR.sat_run(xR.od, 1, 1, 0, 1); mix_s += (mix_t - mix_s) * sm; out_s += (out_t - out_s) * sm; oL = (dl + (wl - dl) * mix_s) * out_s; oR = (dr + (wr - dr) * mix_s) * out_s; oL = ostL.ost_band(oL); oR = ostR.ost_band(oR); ost_out(oL, oR); spl0 = ost_l; spl1 = ost_r; pkO = max(pkO, max(abs(spl0), abs(spl1))); @gfx 1100 624 ui_begin(1100, 624); // ---------- chassis ---------- fh = 616; ui_col(0x0B0C0D); gfx_rect(0, 0, gfx_w, gfx_h); pk_chassis(8 * sc, 8 * sc, 1084 * sc, fh * sc, PNL); C_INK = INKL; // header ui_col(INKL); gfx_circle(40 * sc, 38 * sc, 14 * sc, 1, 1); gfx_setfont(4); ui_col(PNL); ui_text(40 * sc, 33 * sc, "GFX"); gfx_setfont(6, "Trebuchet MS", 24 * sc, 'b'); ui_col(INKL); gfx_x = 62 * sc; gfx_y = 24 * sc; gfx_drawstr("QUAD11"); gfx_setfont(4); ui_cola(INKL, 0.6); gfx_x = 172 * sc; gfx_y = 34 * sc; gfx_drawstr("4-BAND BUS COMPRESSOR - EQ + SATURATION PER BAND"); pk_pextra = "Reset all to defaults"; // the house preset display: PRESETS, prev / next, LCD (click it for the list) k_ = pk_preset(560 * sc, 18 * sc, 300 * sc, 36 * sc, pr_cur, "Gentle Glue|Mix Bus Punch|Drum Bus Smash (parallel)|Transient Lift (expand)|Vocal Control|Bass Tighten|De-Mud & Air|Tape Warmth|Master Gentle|Parallel Crush"); k_ > 0 ? ( load_preset(k_); pr_cur = k_ > 10 ? 0 : k_; ); ui_size_picker(880 * sc, 31 * sc); ui_col(ACC); gfx_rect(14 * sc, 66 * sc, 1072 * sc, 3 * sc); // ---------- display: response, crossovers, bells and gain reduction ---------- gdx = 24 * sc; gdy = 82 * sc; gdw = 846 * sc; gdh = 140 * sc; ui_col(0x050505); ui_rrect(gdx, gdy, gdw, gdh, 4 * sc); ui_col(0x111215); gfx_rect(gdx + 3 * sc, gdy + 3 * sc, gdw - 6 * sc, gdh - 6 * sc); gx0 = 34 * sc; gw = 826 * sc; gy0 = 90 * sc; gh = 124 * sc; gmid = gy0 + gh * 0.5; // band areas, tinted, with their gain reduction hanging from the top gi = 0; loop(4, ga = gi == 0 ? gx0 : f2x(gi == 1 ? xf1 : gi == 2 ? xf2 : xf3); gb = gi == 3 ? gx0 + gw : f2x(gi == 0 ? xf1 : gi == 1 ? xf2 : xf3); gc = gi == 0 ? BC0 : gi == 1 ? BC1 : gi == 2 ? BC2 : BC3; ui_cola(gc, 0.07); gfx_rect(ga, gy0, gb - ga, gh); ui_cola(gc, 0.3); gfx_rect(ga, gy0, gb - ga, min(abs(grd[gi]) / (grd[gi] > 0 ? 6 : 24), 1) * gh * 0.5); gi += 1; ); // grid gfx_setfont(4); ui_cola(0xFFFFFF, 0.08); gfx_line(gx0, gmid, gx0 + gw, gmid); gi = 0; loop(9, gf = gi == 0 ? 50 : gi == 1 ? 100 : gi == 2 ? 200 : gi == 3 ? 500 : gi == 4 ? 1000 : gi == 5 ? 2000 : gi == 6 ? 5000 : gi == 7 ? 10000 : 20000; gx = f2x(gf); ui_cola(0xFFFFFF, 0.07); gfx_line(gx, gy0, gx, gy0 + gh); ui_cola(INKL, 0.45); gfx_x = gx + 2 * sc; gfx_y = gy0 + gh - 12 * sc; gfx_drawstr(gi == 0 ? "50" : gi == 1 ? "100" : gi == 2 ? "200" : gi == 3 ? "500" : gi == 4 ? "1k" : gi == 5 ? "2k" : gi == 6 ? "5k" : gi == 7 ? "10k" : ""); gi += 1; ); ui_cola(INKL, 0.45); gfx_x = gx0 + 2 * sc; gfx_y = db2y(12) - 5 * sc; gfx_drawstr("+12"); gfx_x = gx0 + 2 * sc; gfx_y = db2y(-12) - 5 * sc; gfx_drawstr("-12"); // response: master EQ times the band bells, each weighted by its share of the crossover ui_col(INKL); gi = 0; gpy = 0; loop(181, gf = 20 * 1000 ^ (gi / 180); gw_ = 2 * $pi * min(gf, srate * 0.49) / srate; gq1 = (gf / xf1) ^ 4; gq2 = (gf / xf2) ^ 4; gq3 = (gf / xf3) ^ 4; gl1 = 1 / (1 + gq1); gl2 = 1 / (1 + gq2); gl3 = 1 / (1 + gq3); gm = gl1 * gl2 * bnd0.band_mag(gw_) + (1 - gl1) * gl2 * bnd1.band_mag(gw_) + gl3 * (1 - gl2) * bnd2.band_mag(gw_) + (1 - gl3) * (1 - gl2) * bnd3.band_mag(gw_); gm *= meL.meq_mag(gw_); gy = db2y(min(max(20 * log10(max(gm, 0.00001)), -15), 15)); gx = gx0 + gw * gi / 180; gi > 0 ? ( gfx_line(gpx, gpy, gx, gy, 1); gfx_line(gpx, gpy + 1, gx, gy + 1, 1); ); gpx = gx; gpy = gy; gi += 1; ); // crossover lines and bell dots: drag them (wheel on a dot = width) gin = mouse_x >= gx0 && mouse_x < gx0 + gw && mouse_y >= gy0 && mouse_y < gy0 + gh; gi = 0; ghit = -1; gbest = 9 * sc; loop(4, gb = 21 + gi * 12; gx = f2x(slider(gb)); gy = db2y(slider(gb + 2)); gd = sqrt((mouse_x - gx) ^ 2 + (mouse_y - gy) ^ 2); gd < gbest ? ( gbest = gd; ghit = gi; ); gi += 1; ); gxh = -1; gi = 0; ghit < 0 ? loop(3, gx = f2x(gi == 0 ? xf1 : gi == 1 ? xf2 : xf3); abs(mouse_x - gx) < 5 * sc ? gxh = gi; gi += 1; ); pressed && gin && drag_id == 0 ? ( ghit >= 0 ? drag_id = 3000 + ghit : gxh >= 0 ? drag_id = 3100 + gxh; ); drag_id >= 3000 && drag_id < 3004 ? ( gb = 21 + (drag_id - 3000) * 12; slider(gb) = floor(x2f(mouse_x) + 0.5); slider(gb + 2) = min(max(floor((gmid - mouse_y) * 30 / gh * 10 + 0.5) / 10, -12), 12); ui_auto(gb); ui_auto(gb + 2); ); drag_id >= 3100 && drag_id < 3103 ? ( gi = drag_id - 3100; gf = floor(x2f(mouse_x) + 0.5); gi == 0 ? ( slider11 = min(max(gf, 30), slider12 / 1.25); ui_auto(11); ) : gi == 1 ? ( slider12 = min(max(gf, slider11 * 1.25), slider13 / 1.25); ui_auto(12); ) : ( slider13 = min(max(gf, slider12 * 1.25), 16000); ui_auto(13); ); ); ghit >= 0 && mouse_wheel != 0 && drag_id == 0 ? ( gb = 22 + ghit * 12; slider(gb) = min(max(slider(gb) * 1.12 ^ sign(mouse_wheel), 0.3), 6); ui_auto(gb); mouse_wheel = 0; ); gi = 0; loop(3, gx = f2x(gi == 0 ? xf1 : gi == 1 ? xf2 : xf3); ui_cola(INKL, gxh == gi || drag_id == 3100 + gi ? 0.9 : 0.45); gfx_line(gx, gy0, gx, gy0 + gh); gfx_line(gx + 1, gy0, gx + 1, gy0 + gh); ui_col(INKL); gfx_rect(gx - 3 * sc, gy0, 7 * sc, 5 * sc); gi += 1; ); gi = 0; loop(4, gb = 21 + gi * 12; gc = gi == 0 ? BC0 : gi == 1 ? BC1 : gi == 2 ? BC2 : BC3; gx = f2x(slider(gb)); gy = db2y(slider(gb + 2)); ui_col(0x000000); gfx_circle(gx, gy, 6 * sc, 1, 1); ui_col(gc); gfx_circle(gx, gy, (ghit == gi || drag_id == 3000 + gi ? 5.5 : 4.5) * sc, 1, 1); (ghit == gi || drag_id == 3000 + gi) ? ( sprintf(#bd, "%.0f Hz %+.1f dB Q %.2f", slider(gb), slider(gb + 2), slider(gb + 1)); gfx_measurestr(#bd, gtw, gth); ui_col(INKL); gfx_x = min(max(gx - gtw * 0.5, gx0), gx0 + gw - gtw); gfx_y = gy0 + 2 * sc; gfx_drawstr(#bd); ); gi += 1; ); // ---------- MASTER EQ ---------- qgroup(886, 82, 190, 140, "MASTER EQ", ACC, SH1); ui_section(3); C_INK = INKL; ui_kfilter = 1; strcpy(#kf_lo, "20"); strcpy(#kf_hi, "500"); ui_knob(922 * sc, 122 * sc, 14, 20, 500, 20, 1, "HPF", slider14 <= 20.5 ? "OFF" : "%.0f Hz"); ui_knob(982 * sc, 118 * sc, 15, 30, 500, 100, 1, "LO FREQ", "%.0f Hz"); ui_knob(1042 * sc, 118 * sc, 16, -12, 12, 0, 0, "LO SHELF", "%+.1f dB"); ui_kfilter = 2; strcpy(#kf_lo, "2k"); strcpy(#kf_hi, "20k"); ui_knob(922 * sc, 184 * sc, 19, 2000, 20000, 20000, 1, "LPF", slider19 >= 19990 ? "OFF" : "%.0f Hz"); ui_knob(982 * sc, 180 * sc, 17, 2000, 16000, 8000, 1, "HI FREQ", "%.0f Hz"); ui_knob(1042 * sc, 180 * sc, 18, -12, 12, 0, 0, "HI SHELF", "%+.1f dB"); // ---------- the four bands ---------- strip(24, 240, 0, 21, BC0, "LOW"); strip(238, 240, 1, 33, BC1, "LO-MID"); strip(452, 240, 2, 45, BC2, "HI-MID"); strip(666, 240, 3, 57, BC3, "HIGH"); // ---------- INPUT + MASTER ---------- qgroup(888, 240, 188, 92, "INPUT", ACC, SH1); ui_section(3); C_INK = INKL; knob_push = 2; // the GUARD LED button toggles the input guard ui_knob(930 * sc, 286 * sc, 1, -24, 12, 0, 0, "TRIM", "%.1f dB"); ui_guardclick(1, 2, 930, 286, 24); // click TRIM for GUARD knob_push = 0; mI.ui_ledm(1040 * sc, 254 * sc, 12 * sc, 64 * sc, pkI); gfx_setfont(4); ui_col(INKL); ui_text(1046 * sc, 322 * sc, "IN"); qgroup(888, 344, 188, 258, "MASTER", ACC, SH2); ui_section(2); C_INK = INKL; ui_knob(940 * sc, 396 * sc, 3, 0, 100, 100, 0, "MIX", "%.0f%%"); ui_knob(940 * sc, 474 * sc, 4, -24, 12, 0, 0, "OUTPUT", "%+.1f dB"); ins_io(898, 534, 5, INKL); // SAT and the OUT stage (OFF / CLIP / LIM) mO.ui_ledm(1040 * sc, 362 * sc, 12 * sc, 206 * sc, pkO); gfx_setfont(4); ui_col(INKL); ui_text(1046 * sc, 574 * sc, "OUT"); any_solo ? ( ui_col(AMBL); ui_text(940 * sc, 586 * sc, "SOLO ON"); ); pkI *= 0.85; pkO *= 0.85; pk_end(); ui_end();