desc:GFX PenguinDrive //tags: drive distortion envelope filter phaser vintage stereo //author: JGWizrad / Garden FX (GFX) // // Requires gfx-drift, gfx-input, gfx-output and gfx-ui (.jsfx-inc) in the same folder. // // Inspired by a 70s art-rock live guitar setup: // Integrated resonant envelope state-variable filter (classic envelope-filter style), 4-pole sweep phaser, // asymmetric valve drive stage, and end-of-chain optical dynamic compressor. 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:0<0,2,1{Envelope Drive,Fixed Filter,Bypass}>-Mode slider2:3<0,10,0.01>-Z-Drive slider3:50<0,100,1>-Filter Sensitivity (%) slider4:1000<200,5000,10>-Filter Peak (Hz) slider5:60<10,95,1>-Resonance / Q (%) slider6:40<0,100,1>-Phaser Mix (%) slider7:0.3<0.05,5,0.01>-Phaser Speed (Hz) slider8:50<0,100,1>-Squash Compressor (%) slider9:50<0,100,1>-Mix (%) slider10:10<0,50,1>-Filter Drift (%) slider11:15<0,50,1>-Drive Drift (%) slider12:0.25<0.02,2,0.01>-Drift Speed (Hz) slider13:0<-24,12,0.1>-Output (dB) slider14:0<0,100,1>-Band Sat (%) slider15:30<0,100,1>-Bias (%) slider16:120<40,400,1>-Sat Low Band (Hz) slider17:6000<2000,16000,10>-Sat High Band (Hz) slider18:-0.3<-24,0,0.1>-Ceiling / Limit Threshold (dB) slider19:1<0,2,1{Off,Clip,Limit}>-Output Stage slider20:0<-24,12,0.1>-Input Trim (dB) slider21:1<0,1,1{Off,On}>-Input Guard slider22:20<20,500,1>-Wet Low Cut (Hz, 20 = off) slider23: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-penguindrive"; // Initialize string variables to prevent execution halting #v_dr = ""; #v_se = ""; #v_pk = ""; #v_q = ""; #v_ph = ""; #v_sq = ""; #v_mix = ""; #v_df = ""; #v_lc = ""; #v_out = ""; // State Variable Filter implementation (classic envelope-filter style) function svf_run(in, f, q) instance(hp, bp, lp) local(g, R) ( g = tan($pi * min(f, srate * 0.45) / srate); R = 1 - q; hp = (in - (2 * R + g) * bp - lp) / (1 + 2 * R * g + g * g); bp = bp + g * hp; lp = lp + g * bp; bp; // Bandpass/Peaking output for classic vocal envelope sound ); // 4-pole Allpass Phaser function phaser_run(x, freq) instance(a1, a2, a3, a4) local(g, y) ( g = (1 - tan($pi * freq / srate)) / (1 + tan($pi * freq / srate)); a1 = g * (a1 + x) - x; a2 = g * (a2 + a1) - a1; a3 = g * (a3 + a2) - a2; a4 = g * (a4 + a3) - a3; a4; ); // End-of-Chain Compressor (Squash) function comp_run(x, thresh) instance(env) local(gain) ( env += (abs(x) - env) * (abs(x) > env ? 0.01 : 0.0005); gain = env > thresh ? thresh / env : 1.0; x * gain; ); function wlc(x) instance(x1, x2, y1, y2) local(y) ( y = wl_b0 * (x - 2 * x1 + x2) - wl_a1 * y1 - wl_a2 * y2; x2 = x1; x1 = x; y2 = y1; y1 = y; y; ); function wlc_set(f) local(w0, al, n) ( w0 = 2 * $pi * min(f, srate * 0.45) / srate; al = sin(w0) / (2 * 0.7071); n = 1 + al; wl_b0 = (1 + cos(w0)) * 0.5 / n; wl_a1 = -2 * cos(w0) / n; wl_a2 = (1 - al) / n; ); function update() ( wl_on = slider22 > 20.5; wl_on ? wlc_set(slider22); mode = slider1; drive_g = 10 ^ (slider2 * 2.5 / 20); sens = slider3 / 100; base_freq = slider4; res_q = slider5 / 100 * 0.95; phaser_mix = slider6 / 100; phaser_spd = slider7; squash = slider8 / 100; mix_t = slider9 / 100; drift_macro(slider23); fd = slider10 / 100 * dm_a; dd = slider11 / 100 * dm_a; dspd = slider12 * dm_s; out_t = 10 ^ (slider13 / 20); in_setup(slider20, slider21); ost_setup(slider14 / 100, slider15 / 100, slider16, slider17, slider18, slider19); ); function load_preset(p) ( p == 1 ? ( slider1 = 0; slider2 = 4.5; slider3 = 65; slider4 = 1200; slider5 = 75; slider6 = 45; slider7 = 0.35; slider8 = 60; slider9 = 85; ) : // Art-Rock Lead p == 2 ? ( slider1 = 0; slider2 = 2.0; slider3 = 80; slider4 = 800; slider5 = 85; slider6 = 20; slider7 = 0.20; slider8 = 80; slider9 = 75; ) : // Funk Envelope p == 3 ? ( slider1 = 1; slider2 = 6.0; slider3 = 0; slider4 = 1400; slider5 = 60; slider6 = 50; slider7 = 0.50; slider8 = 30; slider9 = 100; ) : // Cocked Wah p == 4 ? ( slider1 = 0; slider2 = 1.0; slider3 = 40; slider4 = 2000; slider5 = 40; slider6 = 0; slider7 = 0.10; slider8 = 20; slider9 = 50; ); // Mild Auto-Sweep p >= 1 && p <= 4 ? slider_automate(1023); ); // ---------- state ---------- sm = 1 - exp(-1 / (0.03 * srate)); vuc = 1 - exp(-1 / (0.01 * srate)); drF.drift_init(0.8 + rand(0.5)); drD.drift_init(0.8 + rand(0.5)); ost_init(20000); ostL.ost_ch_init(); ostR.ost_ch_init(); in_init(); pkL = 0; pkR = 0; vu_ms = 0; envL = 0; envR = 0; p_phase = 0; update(); mix_s = mix_t; out_s = out_t; !ui_dflt ? ui_thm = 0; !ui_dflt ? ui_hw = 0; 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_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 in_process(spl0, spl1); inL = in_l; inR = in_r; vu_ms += (0.5 * (inL * inL + inR * inR) - vu_ms) * vuc; // Drift Ticks dinc = dspd / srate; vF = drF.drift_tick(dinc); vD = drD.drift_tick(dinc); mix_s += (mix_t - mix_s) * sm; out_s += (out_t - out_s) * sm; mode == 2 ? ( oL = inL; oR = inR; ) : ( wL = inL; wR = inR; // 1. Envelope Follower (Enhanced Sensitivity Algorithm) mode == 0 ? ( envL += (abs(wL) - envL) * (abs(wL) > envL ? 0.015 : 0.00015); envR += (abs(wR) - envR) * (abs(wR) > envR ? 0.015 : 0.00015); envL_scaled = envL ^ 0.75; envR_scaled = envR ^ 0.75; dyn_freqL = base_freq + envL_scaled * sens * 12000; dyn_freqR = base_freq + envR_scaled * sens * 12000; ) : ( dyn_freqL = base_freq; dyn_freqR = base_freq; ); eff_fL = min(max(dyn_freqL * (1 + fd * vF), 40), 16000); eff_fR = min(max(dyn_freqR * (1 + fd * vF), 40), 16000); f_outL = fltL.svf_run(wL, eff_fL, res_q); f_outR = fltR.svf_run(wR, eff_fR, res_q); phaser_mix > 0 ? ( p_phase += phaser_spd / srate; p_phase -= floor(p_phase); mod_p = 500 + 1500 * (0.5 + 0.5 * sin(p_phase * 2 * $pi)); phL = phaserL.phaser_run(f_outL, mod_p); phR = phaserR.phaser_run(f_outR, mod_p); f_outL = (1 - phaser_mix) * f_outL + phaser_mix * phL; f_outR = (1 - phaser_mix) * f_outR + phaser_mix * phR; ); eff_drive = drive_g * (1 + dd * vD * 0.2); d_outL = ost_tanh(f_outL * eff_drive); d_outR = ost_tanh(f_outR * eff_drive); squash > 0 ? ( c_thresh = 1.0 - squash * 0.85; d_outL = compL.comp_run(d_outL, c_thresh); d_outR = compR.comp_run(d_outR, c_thresh); ); oL = wl_on ? inL + mix_s * (WLL.wlc(d_outL) - WDL.wlc(inL)) : (1 - mix_s) * inL + mix_s * d_outL; oR = wl_on ? inR + mix_s * (WLR.wlc(d_outR) - WDR.wlc(inR)) : (1 - mix_s) * inR + mix_s * d_outR; ); dispF = eff_fL; 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 900 340 ui_begin(900, 340); // --- Theme Colors --- BG_COLOR = 0x1E2022; FACE_BG = 0x2A2D30; CHARCOAL = 0x181A1B; YELLOW = 0xF3B02C; ORANGE = 0xE65C00; RED = 0xC02626; TEXT_LIGHT= 0xE5E0D8; KEYC = 0x1E2022; function setv(idx, v) ( slider(idx) = v; ui_auto(idx); ); ui_col(BG_COLOR); gfx_rect(0, 0, gfx_w, gfx_h); rd_plate(6, 4, 888, 332, FACE_BG, 12); // --- 70s RACING STRIPES --- stripe_y0 = 62 * sc; stripe_h = 4 * sc; ui_col(YELLOW); gfx_rect(40 * sc, stripe_y0, 820 * sc, stripe_h); ui_col(ORANGE); gfx_rect(40 * sc, stripe_y0 + stripe_h, 820 * sc, stripe_h); ui_col(RED); gfx_rect(40 * sc, stripe_y0 + stripe_h * 2, 820 * sc, stripe_h); ui_col(YELLOW); gfx_roundrect(6 * sc, 4 * sc, 888 * sc, 332 * sc, 12 * sc); rd_screw(22, 20); rd_screw(878, 20); rd_screw(22, 320); rd_screw(878, 320); // ---------- Header ---------- rd_badge(56, 36, 15, YELLOW, CHARCOAL); rd_titlei(81, 11, "PENGUIN DRIVE", 32, TEXT_LIGHT); rd_sub(82, 43, "DYNAMIC ENVELOPE SATURATOR", ORANGE, 1); rd_sq(450, 25, 50, 26, slider1 == 0, KEYC, "ENVELOPE") ? setv(1, 0); rd_sq(510, 25, 50, 26, slider1 == 1, KEYC, "FIXED") ? setv(1, 1); rd_sq(612, 25, 50, 26, slider1 == 2, 0x331111, "OFF") ? setv(1, 2); rd_led(475, 15, 2.5, slider1 == 0, YELLOW); rd_led(535, 15, 2.5, slider1 == 1, ORANGE); pk_pextra = ""; // the house preset display: PRESETS, prev / next, LCD (click it for the list) k_ = pk_preset(670 * sc, 20 * sc, 216 * sc, 28 * sc, pr_cur, "Art-Rock Lead ('73)|Funk Envelope|Cocked Wah Drive|Mild Auto-Sweep"); k_ > 0 ? ( load_preset(k_); pr_cur = k_ > 4 ? 0 : k_; ); ui_size_picker(370 * sc, 24 * sc); // ---------- Scope Window ---------- ui_col(CHARCOAL); rd_rr(40 * sc, 80 * sc, 300 * sc, 94 * sc, 6 * sc); ui_col(0x121415); gfx_rect(44 * sc, 84 * sc, 292 * sc, 86 * sc); ui_col(YELLOW); sx0 = 44; sw0 = 292; sy0 = 127; sa = 28; i = 0; loop(100, pp = i / 100; qx = (sx0 + pp * sw0) * sc; qy = (sy0 - sin(pp * $pi * 2 + (dispF / 1000)) * sa * (0.3 + 0.7 * (dispF / 4000))) * sc; i > 0 ? gfx_line(px, py, qx, qy, 1); px = qx; py = qy; i += 1; ); gfx_setfont(4); ui_cola(TEXT_LIGHT, 0.9); gfx_x = 52 * sc; gfx_y = 90 * sc; sprintf(#f_str, "FILTER PEAK: %.0f HZ", dispF); gfx_drawstr(#f_str); // ---------- Inline Hover Checks for Controls ---------- // Z-Drive (400, 120) sqrt((mouse_x - 400 * sc)^2 + (mouse_y - 120 * sc)^2) <= 31 * sc ? sprintf(#v_dr, "%.2f", slider2) : strcpy(#v_dr, ""); // Sens (480, 120) sqrt((mouse_x - 480 * sc)^2 + (mouse_y - 120 * sc)^2) <= 31 * sc ? sprintf(#v_se, "%.0f%%", slider3) : strcpy(#v_se, ""); // Peak (560, 120) sqrt((mouse_x - 560 * sc)^2 + (mouse_y - 120 * sc)^2) <= 31 * sc ? sprintf(#v_pk, "%.0f Hz", slider4) : strcpy(#v_pk, ""); // Q / Res (640, 120) sqrt((mouse_x - 640 * sc)^2 + (mouse_y - 120 * sc)^2) <= 31 * sc ? sprintf(#v_q, "%.0f%%", slider5) : strcpy(#v_q, ""); // Phaser (720, 120) sqrt((mouse_x - 720 * sc)^2 + (mouse_y - 120 * sc)^2) <= 31 * sc ? sprintf(#v_ph, "%.0f%%", slider6) : strcpy(#v_ph, ""); // Squash (800, 120) sqrt((mouse_x - 800 * sc)^2 + (mouse_y - 120 * sc)^2) <= 31 * sc ? sprintf(#v_sq, "%.0f%%", slider8) : strcpy(#v_sq, ""); // Drift (84, 272) sqrt((mouse_x - 84 * sc)^2 + (mouse_y - 272 * sc)^2) <= 26 * sc ? sprintf(#v_df, "%.0f%%", slider23) : strcpy(#v_df, ""); // Mix (450, 272) sqrt((mouse_x - 450 * sc)^2 + (mouse_y - 272 * sc)^2) <= 28 * sc ? sprintf(#v_mix, "%.0f%%", slider9) : strcpy(#v_mix, ""); // Low Cut (524, 272) sqrt((mouse_x - 524 * sc)^2 + (mouse_y - 272 * sc)^2) <= 25 * sc ? (slider22 <= 20.5 ? strcpy(#v_lc, "off") : sprintf(#v_lc, "%.0f Hz", slider22)) : strcpy(#v_lc, ""); // Output (598, 272) sqrt((mouse_x - 598 * sc)^2 + (mouse_y - 272 * sc)^2) <= 28 * sc ? sprintf(#v_out, "%+.1f dB", slider13) : strcpy(#v_out, ""); // ---------- Main Controls ---------- dy_kst = 0; dy_knob(400, 120, 21, 2, 0, 10, 3, 0, ORANGE, "Z-DRIVE", #v_dr); dy_knob(480, 120, 21, 3, 0, 100, 50, 0, YELLOW, "SENS", #v_se); dy_knob(560, 120, 21, 4, 200, 5000, 1000, 1, YELLOW, "PEAK", #v_pk); dy_knob(640, 120, 21, 5, 10, 95, 60, 0, YELLOW, "Q / RES", #v_q); dy_knob(720, 120, 21, 6, 0, 100, 40, 0, ORANGE, "PHASER", #v_ph); dy_knob(800, 120, 21, 8, 0, 100, 50, 0, RED, "SQUASH", #v_sq); // ---------- Lower Panel ---------- ui_cola(TEXT_LIGHT, 0.25); gfx_rect(40 * sc, 222 * sc, 820 * sc, 1 * sc); gfx_rect(128 * sc, 230 * sc, 1 * sc, 92 * sc); gfx_rect(400 * sc, 230 * sc, 1 * sc, 92 * sc); rd_lbl(84, 230, "CHAOS DRIFT", ORANGE, 1); rd_lbl(264, 230, "IN / OUT", ORANGE, 1); rd_lbl(630, 230, "MASTER", ORANGE, 1); dy_nov = 1; dy_knob(84, 272, 16, 23, 0, 100, 50, 0, YELLOW, "DRIFT", #v_df); rd_ioc1 = CHARCOAL; rd_ioc2 = YELLOW; rd_ioc3 = RED; rd_iok = 20; rd_iotx = 12; rd_io(166, 250, 20, 21, 14, 19); rd_iotx = 0; dy_nov = 0; dy_knob(450, 272, 18, 9, 0, 100, 50, 0, YELLOW, "MIX", #v_mix); dy_kf = 1; dy_knob(524, 272, 15, 22, 20, 500, 20, 1, 0, "LOW CUT", #v_lc); dy_knob(598, 272, 18, 13, -24, 12, 0, 0, YELLOW, "OUTPUT", #v_out); mL.ui_ledm(652 * sc, 258 * sc, 196 * sc, 8 * sc, pkL); mR.ui_ledm(652 * sc, 270 * sc, 196 * sc, 8 * sc, pkR); pkL *= 0.85; pkR *= 0.85; rd_lbl(750, 286, " LEVEL ", ORANGE, 0.8); pk_end(); ui_end();