desc:GFX Thrust //tags: compressor bus vca feedforward feedback stereo-link thrust //author: JGWizrad / Garden FX (GFX) // // Requires gfx-drift, gfx-input, gfx-output, gfx-ui, gfx-panel, gfx-instrument and gfx-dyn (.jsfx-inc) in the same folder. // // A stereo VCA bus compressor in the spirit of the famous American stereo bus compressor with the stepped // controls and the THRUST switch (personal use; the sound is designed from scratch). // THRESHOLD - where it starts working. // ATTACK - .03 / .1 / .3 / 1 / 3 / 10 / 30 ms. RATIO - 1.5 / 2 / 3 / 4 / 6 / 10 / INF : 1. // RELEASE - .05 / .1 / .2 / .5 / 1 / 2 s, or VAR (set by the VAR knob, 0.05 - 3 s). // KNEE - HARD / MED (6 dB) / SOFT (12 dB). // THRUST - what the detector hears: NORM (flat), MED (the lows count less), LOUD (the lows count much less // and the highs more) - the bass drives the compressor less, so mixes stay big and punchy. // TYPE - NEW: feed-forward (the detector hears the input: precise, firm). OLD: feedback (it hears its own // output: softer, more forgiving, the ratio eases off on big peaks - the vintage feel). // LINK - IND (each side on its own) to 100% (both sides share one gain). LINK FILTER shapes what is // shared: OFF, HP (the lows are left out of the link) or BP (only the mids are shared). // MAKEUP, MIX (parallel compression), IN. METER: the two VUs show gain reduction or output level. // PRESETS. 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 import gfx-dyn.jsfx-inc slider1:1<0,1,1{Out,In}>-In slider2:-12<-40,0,0.1>-Threshold (dB) slider3:3<0,6,1{.03 ms,.1 ms,.3 ms,1 ms,3 ms,10 ms,30 ms}>-Attack slider4:3<0,6,1{1.5:1,2:1,3:1,4:1,6:1,10:1,inf:1}>-Ratio slider5:3<0,6,1{.05 s,.1 s,.2 s,.5 s,1 s,2 s,Var}>-Release slider6:0.3<0.05,3,0.01>-Var release (s) slider7:1<0,2,1{Hard,Med,Soft}>-Knee slider8:0<0,2,1{Norm,Med,Loud}>-Thrust slider9:0<0,1,1{New (feed-forward),Old (feedback)}>-Type slider10:100<0,100,1>-Link (%) slider11:0<0,2,1{Off,HP,BP}>-Link filter slider12:0<-10,20,0.1>-Makeup (dB) slider13:100<0,100,1>-Mix (%) slider14:0<0,1,1{GR,Output}>-Meter slider15:0<-24,12,0.1>-Input trim (dB) slider16:1<0,1,1{Off,On}>-Input guard slider17:0<0,100,1>-Band sat (%) slider18:30<0,100,1>-Bias (%) slider19:120<40,400,1>-Sat low band (Hz) slider20:6000<2000,16000,10>-Sat high band (Hz) slider21:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider22:1<0,2,1{Off,Clip,Limit}>-Output stage in_pin:left input in_pin:right input out_pin:left output out_pin:right output @init #ui_man = "gfx-thrust"; // the ? pop-up links to this manual function att_ms(i) ( i == 0 ? 0.03 : i == 1 ? 0.1 : i == 2 ? 0.3 : i == 3 ? 1 : i == 4 ? 3 : i == 5 ? 10 : 30 ); function rat_v(i) ( i == 0 ? 1.5 : i == 1 ? 2 : i == 2 ? 3 : i == 3 ? 4 : i == 4 ? 6 : i == 5 ? 10 : 1000 ); function rel_s(i) ( i == 0 ? 0.05 : i == 1 ? 0.1 : i == 2 ? 0.2 : i == 3 ? 0.5 : i == 4 ? 1 : i == 5 ? 2 : slider6 ); // one channel's side-chain: THRUST shaping, the link-filter copy, and a fast peak detector function sc_set(th, lf) instance(t1, t2, lk) ( th == 0 ? ( t1.bq_set(4, 200, 0.7, 0); t2.bq_set(3, 3000, 0.7, 0); ) : th == 1 ? ( t1.bq_set(4, 220, 0.6, -6); t2.bq_set(3, 3000, 0.7, 1.5); ) : ( t1.bq_set(4, 260, 0.6, -12); t2.bq_set(3, 2500, 0.7, 4); ); lf == 1 ? lk.bq_set(1, 250, 0.7, 0) : lf == 2 ? lk.bq_set(5, 1000, 0.6, 0) : lk.bq_set(4, 200, 0.7, 0); ); function sc_run(x) instance(t1, t2, lk, env, envk) local(s, k) ( s = t2.bq_run(t1.bq_run(x)); k = lk.bq_run(s); env = max(abs(s), env * det_r); envk = max(abs(k), envk * det_r); 20 * log10(env + 0.0000001); ); function sc_lvk() instance(envk) ( 20 * log10(envk + 0.0000001) + (lfil > 0 ? 3 : 0) ); // gain: feed-forward or feedback ratio law, knee, attack / release in dB function gc(lv) instance(e) local(o, t, f) ( o = lv - thr; f = dy_curve(o, knee); t = typ ? max(-(min(rat, 30) - 1) * f, -40) : -(1 - 1 / rat) * f; e += (t - e) * (t < e ? ac : rc); e; ); function update() ( on = slider1; thr = slider2; ac = dy_ms(att_ms(slider3)); rat = rat_v(slider4); rc = dy_ms(1000 * rel_s(slider5)); knee = slider7 == 0 ? 0 : slider7 == 1 ? 6 : 12; typ = slider9; lnk = slider10 / 100; lfil = slider11; scL.sc_set(slider8, lfil); scR.sc_set(slider8, lfil); mk = 10 ^ (slider12 / 20); mix_t = slider13 / 100; in_setup(0, slider16); ost_setup(0, 0, 120, 6000, -0.3, 0); // no GFX output stage on this unit: its own character does that job ); // ---------- presets ---------- // threshold, attack, ratio, release, knee, thrust, type, link, link filter, makeup, mix function pp(t, a, r, rl, kn, th, ty, lk, lf, mu, mx) ( slider1 = 1; slider2 = t; slider3 = a; slider4 = r; slider5 = rl; slider7 = kn; slider8 = th; slider9 = ty; slider10 = lk; slider11 = lf; slider12 = mu; slider13 = mx; ); function load_preset(p) ( p == 1 ? pp(-14, 4, 1, 4, 2, 1, 0, 100, 1, 2, 100) : // Mix Bus Glue p == 2 ? pp(-18, 4, 3, 3, 1, 2, 0, 100, 1, 4, 100) : // Big Mix (Loud thrust) p == 3 ? pp(-22, 3, 4, 2, 0, 1, 0, 80, 0, 6, 100) : // Drum Bus Punch p == 4 ? pp(-30, 0, 5, 1, 0, 2, 0, 100, 0, 10, 40) : // Drum Smash (parallel) p == 5 ? pp(-16, 4, 2, 6, 2, 0, 1, 100, 0, 3, 100) : // Vintage Feedback p == 6 ? pp(-20, 2, 3, 3, 1, 0, 0, 100, 0, 4, 100) : // Vocal Bus p == 7 ? pp(-18, 5, 1, 4, 2, 1, 1, 0, 0, 2, 100) : // Wide Stereo (independent) p == 8 ? pp(-12, 1, 6, 1, 0, 0, 0, 100, 0, 2, 100) : // Bus Limiter p == 9 ? pp(-24, 5, 3, 4, 1, 0, 0, 100, 0, 5, 100) : // Bass Control p == 10 ? pp(-26, 3, 4, 2, 1, 2, 1, 50, 2, 6, 60); // Room Pump p >= 1 && p <= 10 ? slider_automate(8191); // sliders 1-13 p == 11 ? ( // Reset all to defaults pp(-12, 3, 3, 3, 1, 0, 0, 100, 0, 0, 100); slider6 = 0.3; slider14 = 0; slider15 = 0; slider16 = 1; slider17 = 0; slider18 = 30; slider19 = 120; slider20 = 6000; slider21 = -0.3; slider22 = 1; slider_automate(4194303); // sliders 1-22 ); ); in_init(); ost_init(20000); ostL.ost_ch_init(); ostR.ost_ch_init(); det_r = exp(-1 / (0.008 * srate)); // peak detector decay (8 ms) sm = 1 - exp(-1 / (0.03 * srate)); vuc = 1 - exp(-1 / (0.3 * srate)); !ui_dflt ? ui_thm = 5; !ui_dflt ? ui_hw = 1; // fixed faceplate PNL = 0x141416; INKW = 0xF2F2EE; YEL = 0xF2C21B; BLU = 0x2F7FE0; LGRY = 0xD2D4D8; DGRY = 0x6C6F76; // knob caps update(); mix_s = mix_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(); @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); dl = in_l; dr = in_r; // detector input: the input (NEW) or the last output (OLD, feedback) vL = scL.sc_run(typ ? yl : dl); vR = scR.sc_run(typ ? yr : dr); sh = max(scL.sc_lvk(), scR.sc_lvk()); lfil == 0 ? sh = max(vL, vR); vL += (sh - vL) * lnk; vR += (sh - vR) * lnk; on ? ( gL.gc(vL); gR.gc(vR); yl = dl * 10 ^ (gL.e / 20); yr = dr * 10 ^ (gR.e / 20); wl = yl * mk; wr = yr * mk; ) : ( gL.e = gR.e = 0; yl = dl; yr = dr; wl = dl; wr = dr; ); grmL = min(grmL, gL.e); grmR = min(grmR, gR.e); mix_s += (mix_t - mix_s) * sm; oL = dl + (wl - dl) * mix_s; oR = dr + (wr - dr) * mix_s; oL = ostL.ost_band(oL * in_comp); oR = ostR.ost_band(oR * in_comp); ost_out(oL, oR); spl0 = ost_l; spl1 = ost_r; vmL += (spl0 * spl0 - vmL) * vuc; vmR += (spl1 * spl1 - vmR) * vuc; @gfx 900 352 ui_begin(900, ins_show_out ? 476 : 352); dy_ink = INKW; dy_bg = PNL; dy_kst = 7; // coloured caps: light grey, dark grey, yellow, blue function sect(x, w, t, col) ( ui_col(col); gfx_rect(x * sc, 74 * sc, w * sc, 3 * sc); gfx_setfont(6, "Trebuchet MS", 13 * sc, 'b'); ui_col(col); gfx_x = (x + (x > 100 ? 6 : 0)) * sc; gfx_y = 82 * sc; gfx_drawstr(t); ); // ---------- panel ---------- fh = ins_show_out ? 468 : 344; ui_col(0x050506); gfx_rect(0, 0, gfx_w, gfx_h); pk_chassis(8 * sc, 4 * sc, 884 * sc, fh * sc, PNL); ui_col(INKW); gfx_circle(40 * sc, 34 * sc, 14 * sc, 1, 1); gfx_setfont(4); ui_col(PNL); ui_text(40 * sc, 29 * sc, "GFX"); gfx_setfont(6, "Trebuchet MS", 26 * sc, 'b'); ui_col(INKW); gfx_x = 62 * sc; gfx_y = 18 * sc; gfx_drawstr("THRUST"); gfx_setfont(4); ui_col(YEL); gfx_x = 200 * sc; gfx_y = 30 * sc; gfx_drawstr("STEREO BUS COMPRESSOR"); pk_pextra = "Reset all to defaults"; // the house preset display: PRESETS, prev / next, LCD (click it for the list) k_ = pk_preset(470 * sc, 14 * sc, 346 * sc, 36 * sc, pr_cur, "Mix Bus Glue|Big Mix (loud thrust)|Drum Bus Punch|Drum Smash (parallel)|Vintage Feedback|Vocal Bus|Wide Stereo (independent)|Bus Limiter|Bass Control|Room Pump"); k_ > 0 ? ( load_preset(k_); pr_cur = k_ > 10 ? 0 : k_; ); ui_size_picker(828 * sc, 22 * sc); ui_col(YEL); gfx_rect(14 * sc, 60 * sc, 520 * sc, 4 * sc); ui_col(BLU); gfx_rect(534 * sc, 60 * sc, 352 * sc, 4 * sc); // ---------- COMPRESSOR ---------- sect(24, 510, "COMPRESSOR", YEL); sprintf(#v, "%.1f dB", slider2); dy_knob(74, 150, 26, 2, -40, 0, -12, 0, YEL, "THRESHOLD", #v); dy_step = 7; dy_steps = ".03|.1|.3|1|3|10|30"; sprintf(#v, "%g ms", att_ms(slider3)); dy_knob(184, 150, 26, 3, 0, 6, 3, 0, LGRY, "ATTACK", #v); dy_step = 7; dy_steps = "1.5|2|3|4|6|10|INF"; slider4 == 6 ? strcpy(#v, "INF : 1") : sprintf(#v, "%g : 1", rat_v(slider4)); dy_knob(294, 150, 26, 4, 0, 6, 3, 0, LGRY, "RATIO", #v); dy_step = 7; dy_steps = ".05|.1|.2|.5|1|2|VAR"; slider5 == 6 ? sprintf(#v, "VAR %.2f s", slider6) : sprintf(#v, "%g s", rel_s(slider5)); dy_knob(404, 150, 26, 5, 0, 6, 3, 0, LGRY, "RELEASE", #v); sprintf(#v, "%.2f s", slider6); dy_knob(478, 140, 14, 6, 0.05, 3, 0.3, 1, slider5 == 6 ? YEL : DGRY, "VAR", #v); dy_step = 3; dy_steps = "HARD|MED|SOFT"; dy_knob(74, 266, 20, 7, 0, 2, 1, 0, DGRY, "KNEE", ""); dy_step = 3; dy_steps = "NORM|MED|LOUD"; dy_knob(184, 266, 20, 8, 0, 2, 0, 0, YEL, "THRUST", ""); dy_btn(252, 244, slider9 == 0, BLU, "NEW") ? ( slider9 = 0; ui_auto(9); ); dy_btn(300, 244, slider9 == 1, YEL, "OLD") ? ( slider9 = 1; ui_auto(9); ); dy_lbl(296, 290, slider9 ? "FEEDBACK" : "FEED-FORWARD", 0.55); sprintf(#v, "%+.1f dB", slider12); dy_knob(404, 266, 20, 12, -10, 20, 0, 0, BLU, "MAKEUP", #v); pk_ledbtn(462 * sc, 246 * sc, 40 * sc, 26 * sc, slider1, dy_grn) ? ( slider1 = 1 - slider1; ui_auto(1); ); dy_lbl(482, 276, "IN", 0.9); // ---------- STEREO ---------- sect(534, 346, "STEREO", BLU); grdL = min(grmL, grdL * 0.85 + 0); grdR = min(grmR, grdR * 0.85 + 0); grmL = 0; grmR = 0; slider14 ? ( vaL = 10 * log10(vmL + 0.0000000001) + 18; vaR = 10 * log10(vmR + 0.0000000001) + 18; ) : ( vaL = grdL; vaR = grdR; ); vuL.ui_vu(546 * sc, 100 * sc, 162 * sc, 98 * sc, vaL, slider14 ? "OUT L" : "GR L"); vuR.ui_vu(716 * sc, 100 * sc, 162 * sc, 98 * sc, vaR, slider14 ? "OUT R" : "GR R"); slider10 < 0.5 ? strcpy(#w, "IND") : sprintf(#w, "%.0f", slider10); dy_knob(580, 266, 20, 10, 0, 100, 100, 0, BLU, "LINK", #w); dy_step = 3; dy_steps = "OFF|HP|BP"; dy_knob(680, 266, 20, 11, 0, 2, 0, 0, DGRY, "LINK FILTER", ""); dy_btn(742, 238, slider14, YEL, slider14 ? "OUT" : "GR") ? ( slider14 = 1 - slider14; ui_auto(14); ); dy_lbl(762, 282, "METER", 0.55); sprintf(#v, "WET %.0f", slider13); dy_knob(838, 266, 20, 13, 0, 100, 100, 0, LGRY, "MIX", #v); pk_end(); ui_end();