desc:GFX Polyvibe //tags: vibe chorus vibrato preamp modulation //author: JGWizrad / Garden FX (GFX) // // Requires gfx-drift, gfx-lfo, gfx-input, gfx-output, gfx-ui, gfx-panel, gfx-dyn and gfx-faceplate (.jsfx-inc) in the same folder. // // Four unequal phase-shift stages swept by a modelled light bulb + LDRs: // the bulb heats fast and cools slowly, the LDRs lag behind it -> the lopsided throb. // CHORUS - dry + shifted (the classic swirl) // VIBRATO - shifted only (pitch wobble) // PREAMP - vibe off, preamp colour only // PREAMP section is active in every mode: transistor-style asymmetric saturation, // low-mid lift and rounded top. 0% = clean. // 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-lfo.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{Chorus,Vibrato,Preamp}>-Mode slider2:0<0,2,1{Free,Sync,Flow}>-Rate mode slider3:2<0.1,12,0.01>-Free rate (Hz) slider4:6<0,15,1{4 bars,2 bars,1 bar,1/2,1/2 D,1/2 T,1/4,1/4 D,1/4 T,1/8,1/8 D,1/8 T,1/16,1/16 D,1/16 T,1/32}>-Sync rate slider5:70<0,100,1>-Intensity (%) slider6:70<0,100,1>-Throb (%) slider7:0<0,180,1>-Spread (deg) slider8:40<0,100,1>-Preamp (%) slider9:100<0,100,1>-Mix (%) slider10:5<0,50,1>-Rate drift (%) slider11:10<0,50,1>-Intensity drift (%) slider12:10<0,50,1>-Lamp drift (%) slider13:0.25<0.02,2,0.01>-Drift speed (Hz) slider14:0<-24,12,0.1>-Output (dB) slider15:0<0,100,1>-Band sat (%) slider16:30<0,100,1>-Bias (%) slider17:120<40,400,1>-Sat low band (Hz) slider18:6000<2000,16000,10>-Sat high band (Hz) slider19:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider20:1<0,2,1{Off,Clip,Limit}>-Output stage slider21:0<-24,12,0.1>-Input trim (dB) slider22:1<0,1,1{Off,On}>-Input guard 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-polyvibe"; // the ? pop-up links to this manual // ---------- preamp: low-mid lift -> asymmetric soft saturation -> rounded top ---------- function pre_proc(x) instance(lp1, lp2) local(y) ( lp1 += (x - lp1) * pre_lsc; y = x + pre_shk * lp1; y = y + pre_wet * (ost_curve(y, pre_d, pre_b) - y); lp2 += (y - lp2) * pre_tc; lp2; ); // ---------- bulb + LDR: fast heat / slow cool, then LDR lag ---------- function lamp(u) instance(bulb, ldr) ( bulb += (u - bulb) * (u > bulb ? lamp_heat : lamp_cool); ldr += (bulb - ldr) * (bulb > ldr ? ldr_fast : ldr_slow); ldr; ); // ---------- four unequal first-order allpass stages ---------- function vibe4(x, a1, a2, a3, a4) instance(s1x, s1y, s2x, s2y, s3x, s3y, s4x, s4y) local(y) ( y = a1 * x + s1x - a1 * s1y; s1x = x; s1y = y; x = y; y = a2 * x + s2x - a2 * s2y; s2x = x; s2y = y; x = y; y = a3 * x + s3x - a3 * s3y; s3x = x; s3y = y; x = y; y = a4 * x + s4x - a4 * s4y; s4x = x; s4y = y; y; ); function apc(f) local(t) ( t = tan($pi * min(max(f, 10), srate * 0.45) / srate); (t - 1) / (t + 1); ); function update() local(p, fc) ( mode = slider1; ratemode = slider2; frate = slider3; div = floor(slider4 + 0.5); int_t = slider5 / 100; thr_t = slider6 / 100; spread = slider7 / 360; mix_t = slider9 / 100; drift_macro(slider23); // DRIFT dial: 50% = the drift settings, 100% = twice, 0 = none rd = slider10 / 100 * dm_a; idr = min(slider11 / 100 * dm_a, 1); ldr_d = min(slider12 / 100 * dm_a, 1); dspd = slider13 * dm_s; out_t = 10 ^ (slider14 / 20); p = slider8 / 100; pre_on = p > 0.001; pre_shk = 10 ^ (3 * p / 20) - 1; pre_d = 1 + 3 * p; pre_b = 0.15 * p; pre_wet = min(p * 2, 1); fc = 18000 * (7000 / 18000) ^ p; pre_tc = pre_on ? 1 - exp(-2 * $pi * min(fc, srate * 0.45) / srate) : 1; lfoL.lfo_setup(0, 0.2); lfoR.lfo_setup(0, 0.2); in_setup(slider21, slider22); ost_setup(slider15 / 100, slider16 / 100, slider17, slider18, slider19, slider20); ); // ---------- presets (leave Drift speed, Output and the I/O rows alone) ---------- function setp(m, rm, fr, dv, it, th, sp, pa, mx, r_, i_, l_) ( slider1 = m; slider2 = rm; slider3 = fr; slider4 = dv; slider5 = it; slider6 = th; slider7 = sp; slider8 = pa; slider9 = mx; slider10 = r_; slider11 = i_; slider12 = l_; ); function load_preset(p) ( p == 1 ? setp(0, 0, 2.0, 6, 70, 70, 0, 40, 100, 5, 10, 10) : // Classic Chorus p == 2 ? setp(0, 0, 0.6, 6, 85, 90, 0, 45, 100, 8, 10, 15) : // Slow Throb p == 3 ? setp(0, 0, 6.5, 6, 60, 60, 0, 40, 100, 5, 5, 10) : // Fast Swirl p == 4 ? setp(1, 0, 4.5, 6, 50, 60, 0, 30, 100, 5, 5, 5) : // Vibrato p == 5 ? setp(0, 0, 1.5, 6, 75, 70, 90, 40, 100, 5, 10, 10) : // Stereo Vibe p == 6 ? setp(0, 1, 2.0, 9, 70, 75, 0, 40, 100, 0, 5, 5) : // Synced 1/8 p == 7 ? setp(2, 0, 2.0, 6, 70, 70, 0, 35, 100, 0, 0, 0) : // Preamp Only (Clean) p == 8 ? setp(2, 0, 2.0, 6, 70, 70, 0, 80, 100, 0, 0, 0) : // Preamp Only (Pushed) p == 9 ? setp(0, 0, 1.2, 6, 70, 85, 0, 50, 100, 15, 20, 40); // Ageing Lamp p >= 1 && p <= 9 ? slider_automate(4095); // sliders 1-12 p == 10 ? ( // Reset all to defaults slider1 = 0; slider2 = 0; slider3 = 2; slider4 = 6; slider5 = 70; slider6 = 70; slider7 = 0; slider8 = 40; slider9 = 100; slider10 = 5; slider11 = 10; slider12 = 10; slider13 = 0.25; slider14 = 0; slider15 = 0; slider16 = 30; slider17 = 120; slider18 = 6000; slider19 = -0.3; slider20 = 1; slider21 = 0; slider22 = 1; slider23 = 50; slider_automate(8388607); ); ); // ---------- state ---------- sm = 1 - exp(-1 / (0.03 * srate)); pre_lsc = 1 - exp(-2 * $pi * 250 / srate); lamp_heat = 1 - exp(-1 / (0.015 * srate)); lamp_cool = 1 - exp(-1 / (0.09 * srate)); ldr_fast = 1 - exp(-1 / (0.005 * srate)); ldr_slow = 1 - exp(-1 / (0.05 * srate)); lfoL.lfo_init(); lfoR.lfo_init(); drR.drift_init(0.8 + rand(0.5)); drI.drift_init(0.8 + rand(0.5)); drL.drift_init(0.8 + rand(0.5)); ost_init(1000); ostL.ost_ch_init(); ostR.ost_ch_init(); in_init(); vuc = 1 - exp(-1 / (0.01 * srate)); vu_ms = 0; !ui_dflt ? ui_thm = 11; !ui_dflt ? ui_hw = 1; // defaults for new instances: Velvet, hardware (saved projects keep their own) pkL = 0; pkR = 0; dispLight = 0.5; update(); int_s = int_t; thr_s = thr_t; 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(); lfoL.lfo_block(ratemode, div, frate); lfoR.lfo_block(ratemode, div, frate); @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; // drift dinc = dspd / srate; vR = drR.drift_tick(dinc); vI = drI.drift_tick(dinc); vL = drL.drift_tick(dinc); // smoothing int_s += (int_t - int_s) * sm; thr_s += (thr_t - thr_s) * sm; mix_s += (mix_t - mix_s) * sm; out_s += (out_t - out_s) * sm; // preamp (every mode) pL = preL.pre_proc(inL); pR = preR.pre_proc(inR); // lamp: LFO -> bulb + LDR (throb) -> lamp drift lvL = lfoL.lfo_tick(0, rd, vR); lvR = lfoR.lfo_tick(spread, rd, vR); uL = 0.5 + 0.5 * lvL; uR = 0.5 + 0.5 * lvR; lightL = (1 - thr_s) * uL + thr_s * lampL.lamp(uL); lightR = (1 - thr_s) * uR + thr_s * lampR.lamp(uR); dim = 1 - ldr_d * (0.5 + 0.5 * vL); lightL *= dim; lightR *= dim; mode == 2 ? ( yL = pL; yR = pR; ) : ( de = int_s * (1 - idr * (0.5 + 0.5 * vI)); fbL = 380 * 2 ^ (de * 1.75 * (2 * lightL - 1)); fbR = 380 * 2 ^ (de * 1.75 * (2 * lightR - 1)); // unequal stages (ratios from the original's capacitor values) a1L = apc(fbL * 0.313); a2L = apc(fbL * 0.0214); a3L = apc(fbL * 10); a4L = apc(fbL); a1R = apc(fbR * 0.313); a2R = apc(fbR * 0.0214); a3R = apc(fbR * 10); a4R = apc(fbR); wL = vibL.vibe4(pL, a1L, a2L, a3L, a4L); wR = vibR.vibe4(pR, a1R, a2R, a3R, a4R); mode == 0 ? ( yL = 0.5 * (pL + wL); yR = 0.5 * (pR + wR); ) : ( yL = wL; yR = wR; ); ); // mix with the untreated signal oL = inL * (1 - mix_s) + yL * mix_s; oR = inR * (1 - mix_s) + yR * mix_s; // output stage: band saturation -> output gain -> clipper / limiter oL = ostL.ost_band(oL) * in_comp * out_s; oR = ostR.ost_band(oR) * in_comp * out_s; ost_out(oL, oR); spl0 = ost_l; spl1 = ost_r; pkL = max(pkL, abs(spl0)); pkR = max(pkR, abs(spl1)); // for display dispLight = lightL; dispA1 = a1L; dispA2 = a2L; dispA3 = a3L; dispA4 = a4L; dispFb = fbL; @gfx 940 404 ui_begin(940, 404); // ---------- DRAFT C: early-2000s plug-in - violet gradient, a glowing orb in a glass display, glossy value-ring knobs ---------- gx_ink = 0xE8E0F6; gx_dim = 0xA596C8; gx_mag = 0xFF4FD0; gx_vio = 0xA478FF; gx_pod = 0x2B1B48; gx_glw = 0xD070FF; function setv(idx, v) ( slider(idx) = v; ui_auto(idx); ); function gx_ap(a, w) ( atan2(-sin(w), a + cos(w)) - atan2(-a * sin(w), 1 + a * cos(w)); ); gx_ra = 1; gx_rw = 0; gx_ga = 0.25; function gx_rsp(x, y, w, h, lc, gc) local(i, gf, w0, ph, mag, mdb, qx, qy, px, py) ( ui_cola(gc, gx_ga); gf = 100; loop(3, qx = (x + log(gf / 20) / log(1000) * w) * sc; gfx_line(qx, y * sc, qx, (y + h) * sc); gf *= 10; ); gfx_line(x * sc, (y + h * 6 / 36) * sc, (x + w) * sc, (y + h * 6 / 36) * sc); ui_cola(lc, gx_ra); i = 0; loop(121, gf = 20 * 1000 ^ (i / 120); w0 = 2 * $pi * gf / srate; slider1 == 0 ? ( ph = gx_ap(dispA1, w0) + gx_ap(dispA2, w0) + gx_ap(dispA3, w0) + gx_ap(dispA4, w0); mag = abs(cos(ph * 0.5)); mdb = mag > 0.00001 ? 20 * log10(mag) : -100; ) : mdb = 0; mdb = min(max(mdb, -30), 6); qx = (x + i / 120 * w) * sc; qy = (y + (6 - mdb) / 36 * h) * sc; i > 0 ? ( gfx_line(px, py, qx, qy, 1); gfx_line(px, py + gx_rw, qx, qy + gx_rw, 1); gfx_line(px, py - gx_rw, qx, qy - gx_rw, 1); ); px = qx; py = qy; i += 1; ); ); // rounded rect, unscaled in (rd_rr: no seams) function gx_rr(x, y, w, h, r) ( rd_rr(x * sc, y * sc, w * sc, h * sc, r * sc); ); // a rounded violet pod: light rim, darker floor, a gloss on the top half function gx_podf(x, y, w, h) ( ui_cola(0x000000, 0.35); gx_rr(x + 1, y + 3, w, h, 12); ui_col(0x5A3F8C); gx_rr(x, y, w, h, 12); ui_col(gx_pod); gx_rr(x + 1.5, y + 1.5, w - 3, h - 3, 11); gfx_gradrect(floor((x + 8) * sc), floor((y + 2) * sc), floor((w - 16) * sc), floor(h * 0.4 * sc), 1, 1, 1, 0.07, 0, 0, 0, 0, 0, 0, 0, -0.07 / (h * 0.4 * sc)); ); function gx_glass(x, y, w, h) ( ui_cola(0x000000, 0.4); gx_rr(x, y + 2, w, h, 10); ui_col(0x6A4FA0); gx_rr(x, y, w, h, 10); ui_col(0x07040F); gx_rr(x + 2, y + 2, w - 4, h - 4, 9); ui_col(0x120A26); gx_rr(x + 4, y + 4, w - 8, h - 8, 8); gfx_gradrect(floor((x + 4) * sc), floor((y + 4) * sc), floor((w - 8) * sc), floor((h - 8) * sc), 0.25, 0.1, 0.45, 0.35, 0, 0, 0, 0, 0, 0, 0, -0.35 / ((h - 8) * sc)); ); // ---------- background: deep violet gradient ---------- gfx_gradrect(0, 0, gfx_w, gfx_h, 0.10, 0.05, 0.19, 1, 0, 0, 0, 0, 0.10 / gfx_h, 0.04 / gfx_h, 0.17 / gfx_h, 0); ui_col(0x140A24); gfx_rect(0, 0, gfx_w, floor(50 * sc)); gfx_gradrect(0, 0, gfx_w, floor(50 * sc), 1, 1, 1, 0.10, 0, 0, 0, 0, 0, 0, 0, -0.10 / (50 * sc)); ui_col(gx_mag); gfx_rect(0, floor(50 * sc), gfx_w, max(1, floor(1.5 * sc))); // ---------- header ---------- rd_badge(32, 25, 14, gx_ink, 0x140A24); gfx_setfont(6, "Arial", 26 * sc, 0); ui_col(0xF4F0FA); gfx_x = 56 * sc; gfx_y = 11 * sc; gfx_drawstr("POLY"); gfx_setfont(6, "Arial", 26 * sc, 'b'); ui_col(gx_mag); gfx_drawstr("VIBE"); rd_sub(200, 22, "LAMP & PHOTOCELL MODULATOR", gx_dim, 1); rd_sq(400, 13, 70, 24, slider1 == 0, gx_mag, "CHORUS") ? setv(1, 0); rd_sq(474, 13, 70, 24, slider1 == 1, gx_mag, "VIBRATO") ? setv(1, 1); rd_sq(548, 13, 70, 24, slider1 == 2, gx_mag, "PREAMP") ? setv(1, 2); pk_pextra = "Reset all to defaults"; // the house preset display: PRESETS, prev / next, LCD (click it for the list) k_ = pk_preset(628 * sc, 10 * sc, 232 * sc, 28 * sc, pr_cur, "Classic Chorus|Slow Throb|Fast Swirl|Vibrato|Stereo Vibe|Synced 1/8|Preamp Only (Clean)|Preamp Only (Pushed)|Ageing Lamp"); k_ > 0 ? ( load_preset(k_); pr_cur = k_ > 9 ? 0 : k_; ); ui_size_picker(868 * sc, 16 * sc); // ---------- the glass display: the orb + the phase response ---------- gx_glass(18, 64, 596, 152); gx_lit = slider1 == 2 ? 0 : min(max(dispLight, 0), 1); gx_lr = slider1 == 2 ? 0 : min(max(lightR, 0), 1); // orb: halo rings that breathe with the lamp gx_k = 0; loop(4, ui_cola(gx_glw, (0.10 - gx_k * 0.02) * (0.3 + gx_lit)); gfx_circle(100 * sc, 140 * sc, (30 + gx_k * 7 + gx_lit * 5) * sc, 0, 1); gx_k += 1; ); rd_lamp(100, 140, 22, 0.3 + 0.7 * gx_lit, gx_glw); ui_cola(0xFFFFFF, 0.12); gfx_circle(100 * sc, 140 * sc, 22 * sc, 0, 1); // L / R light bars (the spread between the channels) gx_k = 0; loop(2, gx_t = gx_k ? gx_lr : gx_lit; ui_cola(gx_vio, 0.2); gfx_rect(floor((150 + gx_k * 12) * sc), floor(92 * sc), floor(6 * sc), floor(96 * sc)); ui_col(gx_mag); gfx_rect(floor((150 + gx_k * 12) * sc), floor((188 - 96 * gx_t) * sc), floor(6 * sc), floor(96 * gx_t * sc)); rd_lbl(153 + gx_k * 12, 192, gx_k ? "R" : "L", gx_dim, 1); gx_k += 1; ); rd_lbl(100, 192, slider1 == 2 ? "LAMP OFF" : "LAMP", gx_dim, 1); ui_cola(gx_vio, 0.3); gfx_rect(floor(180 * sc), floor(78 * sc), max(1, floor(sc)), floor(124 * sc)); gfx_setfont(4); ui_col(gx_mag); gfx_x = 194 * sc; gfx_y = 74 * sc; slider1 == 0 ? sprintf(#inf, "CHORUS NOTCH %.0f Hz", dispFb) : slider1 == 1 ? strcpy(#inf, "VIBRATO pitch wobble, flat response") : strcpy(#inf, "PREAMP ONLY"); gfx_drawstr(#inf); gx_ra = 0.2; gx_rw = 2; gx_rsp(194, 92, 406, 110, gx_mag, gx_vio); gx_ra = 1; gx_rw = 0; gx_ga = 0; gx_rsp(194, 92, 406, 110, 0xFFC8F2, gx_vio); gx_ga = 0.25; // ---------- right pod: rate mode + preamp ---------- gx_podf(628, 64, 294, 152); dy_ink = gx_ink; dy_bg = gx_pod; dy_kst = 31; rd_lbl(690, 72, "RATE MODE", gx_dim, 1); rd_sq(644, 90, 92, 24, slider2 == 0, gx_vio, "FREE") ? setv(2, 0); rd_sq(644, 120, 92, 24, slider2 == 1, gx_vio, "SYNC") ? setv(2, 1); rd_sq(644, 150, 92, 24, slider2 == 2, gx_vio, "FLOW") ? setv(2, 2); ui_cola(gx_ink, 0.12); gfx_rect(floor(752 * sc), floor(76 * sc), max(1, floor(sc)), floor(128 * sc)); rd_lbl(836, 72, "PREAMP", gx_dim, 1); sprintf(#v, "%.0f%%", slider8); dy_knob(800, 132, 22, 8, 0, 100, 40, 0, 0xFFB040, "AMOUNT", #v); gfx_setfont(1); ui_col(gx_ink); ui_text(876 * sc, 120 * sc, slider8 < 1 ? "clean" : slider8 < 50 ? "warm" : slider8 < 80 ? "pushed" : "driven"); rd_lbl(876, 138, "every mode", gx_dim, 1); // ---------- bottom pod: VIBE (drift + the four vibe dials in one row) | IN / OUT | MASTER ---------- gx_podf(18, 230, 904, 160); ui_cola(gx_ink, 0.1); gfx_rect(floor(444 * sc), floor(240 * sc), max(1, floor(sc)), floor(140 * sc)); gfx_rect(floor(700 * sc), floor(240 * sc), max(1, floor(sc)), floor(140 * sc)); rd_lbl(238, 238, "VIBE", gx_dim, 1); dy_nov = 1; sprintf(#v, "%.0f%%", slider23); dy_knob(62, 300, 15, 23, 0, 100, 50, 0, gx_vio, "DRIFT", #v); dy_nov = 0; ui_cola(gx_ink, 0.08); gfx_rect(floor(100 * sc), floor(262 * sc), max(1, floor(sc)), floor(84 * sc)); slider2 ? ( dy_step = 16; dy_steps = ""; dy_knob(146, 300, 19, 4, 0, 15, 6, 0, gx_mag, "SPEED", lfo_div_name(floor(slider4 + 0.5))); ) : ( sprintf(#v, "%.2f Hz", slider3); dy_knob(146, 300, 19, 3, 0.1, 12, 2, 1, gx_mag, "SPEED", #v); ); sprintf(#v, "%.0f%%", slider5); dy_knob(226, 300, 17, 5, 0, 100, 70, 0, gx_mag, "INTENSITY", #v); sprintf(#v, "%.0f%%", slider6); dy_knob(306, 300, 17, 6, 0, 100, 70, 0, gx_mag, "THROB", #v); sprintf(#v, "%.0f deg", slider7); dy_knob(386, 300, 17, 7, 0, 180, 0, 0, gx_vio, "SPREAD", #v); rd_lbl(572, 238, "IN / OUT", gx_dim, 1); rd_iok = 31; rd_ioc1 = 0x9D8CC8; rd_ioc2 = 0xFFB040; rd_ioc3 = 0xFF5060; dy_nov = 1; rd_iotx = 12; rd_io(476, 278, 21, 22, 15, 20); rd_iotx = 0; dy_nov = 0; rd_iok = 20; rd_lbl(811, 238, "MASTER", gx_dim, 1); sprintf(#v, "%.0f%%", slider9); dy_knob(760, 296, 19, 9, 0, 100, 100, 0, gx_mag, "MIX", #v); sprintf(#v, "%+.1f dB", slider14); dy_knob(862, 296, 19, 14, -24, 12, 0, 0, gx_mag, "OUTPUT", #v); mL.ui_ledm(722 * sc, 356 * sc, 178 * sc, 6 * sc, pkL); mR.ui_ledm(722 * sc, 365 * sc, 178 * sc, 6 * sc, pkR); pkL *= 0.85; pkR *= 0.85; pk_end(); ui_end();