desc:GFX Echofish //tags: filter envelope auto-wah sample-and-hold step funk //author: JGWizrad / Garden FX (GFX) // // Echofish: GFX Starfish (the envelope / step filter) with a tape, bucket-brigade or digital echo (the Echo engine) added below. // ORDER: FILTER > ECHO (the echoes repeat the filtered sound) or ECHO > FILTER (the filter moves over the echoes too). // Echo: TAPE / BUCKET / DIGITAL, SYNC, PING-PONG, FREEZE, VINTAGE, TIME, FEEDBACK, DRIVE, LO / HI CUT, DUCK, WOW, // FLUTTER, WIDTH, MIX. // // Requires gfx-drift, gfx-input, gfx-output, gfx-ui, gfx-panel, gfx-instrument and gfx-dyn (.jsfx-inc) in the same folder. // // Starfish: an automatic filter in the spirit of the classic 70s envelope-filter pedals and the modern sample-and-hold filter // machines (personal use; the sound is designed from scratch). A resonant two-pole state-variable filter, swept by // your playing or stepped in time with the project. // MODE - ENV: the envelope follower sweeps the filter (the classic auto-wah / quack). S&H: random steps in time // with the project. ENV+S&H: the steps, scaled by how hard you play. // FILTER - LP / BP / HP / N (notch). // FREQ - where the sweep starts. DEPTH - how far it goes (0 - 6 octaves). PEAK - resonance. // UP / DOWN - the sweep direction (click the arrows). RANGE - LOW or HIGH (an octave up). // SENS, RESPONSE - how strongly and how fast the envelope follows your playing (RESPONSE: fast and snappy to slow // and vocal). // RATE x MULT - the step length (1/2 - 1/32 of a note, x1 / x2 / x4). GLIDE - smooths between steps. // PATTERN - FREE: a new random step every time. LOOP: a one-bar random pattern that repeats, so it grooves. // DRIVE - pushes the filter for a fatter, grittier sound. MIX, OUTPUT. // HOLD freezes the filter where it is; ON switches the effect in or out (both in the header). // PRESETS. IN / OUT (bottom band): TRIM with the GUARD key, SAT and the OUT stage. DRIFT: one dial for wow / flutter. 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 import gfx-faceplate.jsfx-inc import gfx-lfo.jsfx-inc slider1:0<0,2,1{Env,S&H,Env+S&H}>-Mode slider2:1<0,3,1{LP,BP,HP,Notch}>-Filter slider3:250<60,3000,1>-Freq (Hz) slider4:3.5<0,6,0.01>-Depth (octaves) slider5:4<0.5,15,0.01>-Peak slider6:1<0,1,1{Down,Up}>-Direction slider7:0<0,1,1{Low,High}>-Range slider8:60<0,100,1>-Sens (%) slider9:40<0,100,1>-Response (%) slider10:2<0,4,1{1/2,1/4,1/8,1/16,1/32}>-Rate slider11:0<0,2,1{x1,x2,x4}>-Mult slider12:15<0,100,1>-Glide (%) slider13:1<0,1,1{Free,Loop}>-Pattern slider14:20<0,100,1>-Drive (%) slider15:100<0,100,1>-Mix (%) slider16:0<-12,12,0.1>-Output (dB) slider17:1<0,1,1{Bypass,On}>-Effect slider18:0<0,1,1{Off,On}>-Hold slider19:0<-24,12,0.1>-Input trim (dB) slider20:1<0,1,1{Off,On}>-Input guard slider21:0<0,100,1>-Band sat (%) slider22:30<0,100,1>-Bias (%) slider23:120<40,400,1>-Sat low band (Hz) slider24:6000<2000,16000,10>-Sat high band (Hz) slider25:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider26:1<0,2,1{Off,Clip,Limit}>-Output stage slider27:0<0,2,1{Tape,Bucket,Digital}>-EQ: Echo type slider28:1<0,1,1{Off,On}>-EQ: Sync to tempo slider29:375<20,2000,1>-EQ: Time (ms) slider30:10<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}>-EQ: Time (sync) slider31:40<0,110,1>-EQ: Feedback (%) slider32:30<0,100,1>-EQ: Drive (%) slider33:120<20,1000,1>-EQ: Lo cut (Hz) slider34:6000<1000,20000,10>-EQ: Hi cut (Hz) slider35:0<0,100,1>-EQ: Duck (%) slider36:0<0,1,1{Off,On}>-EQ: Ping-pong slider37:0<0,1,1{Off,On}>-EQ: Freeze slider38:20<0,100,1>-EQ: Wow (%) slider39:15<0,100,1>-EQ: Flutter (%) slider40:0.3<0.05,2,0.01>-EQ: Drift speed (Hz) slider41:35<0,100,1>-EQ: Mix (%) slider42:100<0,100,1>-EQ: Width (%) slider43:0<-24,12,0.1>-EQ: Output (dB) slider44:0<0,3,1{Off,-3 dB,-6 dB,-9 dB}>-EQ: Vintage EQ (500 Hz cut) slider45:0<0,1,1{Filter then Echo,Echo then Filter}>-Order slider46: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-echofish"; // the ? pop-up links to this manual HN = 240; HFC = 30000; HST = 30300; SPX = 30700; SPY = 30720; // display history: cutoff, step markers memset(30000, 0, 760); function th(x) ( x = min(max(x, -3), 3); x * (27 + x * x) / (27 + 9 * x * x); ); // two-pole state-variable filter (trapezoidal), coefficients shared by both sides function svf(x) instance(s1, s2) local(v1, v2, v3) ( v3 = x - s2; v1 = fa1 * s1 + fa2 * v3; v2 = s2 + fa2 * s1 + fa3 * v3; s1 = 2 * v1 - s1; s2 = 2 * v2 - s2; s1 = th(s1 * 0.5) * 2; // the integrator saturates gently when pushed ftype == 0 ? v2 : ftype == 1 ? v1 * fk : ftype == 2 ? x - fk * v1 - v2 : x - fk * v1; ); function rnd(i) ( i = sin(i * 12.9898 + 4.1) * 43758.5453; i - floor(i); ); function update() ( mode = slider1; ftype = slider2; fbase = slider3 * (slider7 ? 2 : 1); depth = slider4; fk = 1 / slider5; dir = slider6 ? 1 : -1; sensg = 10 ^ ((slider8 - 50) / 25) * 4; env_a = dy_ms(1 + slider9 / 100 * 19); env_r = dy_ms(30 + slider9 / 100 * 770); spb = (slider10 == 0 ? 0.5 : slider10 == 1 ? 1 : slider10 == 2 ? 2 : slider10 == 3 ? 4 : 8) * (slider11 == 0 ? 1 : slider11 == 1 ? 2 : 4); // steps per beat glide = slider12 / 100; loopp = slider13; dg = 1 + slider14 / 100 * 4; mix_t = slider15 / 100; og = 10 ^ (slider16 / 20); fx_on = slider17; hold = slider18; in_setup(slider19, slider20); ins_output_setup(21); ); // ---------- presets (leave INPUT and OUTPUT alone) ---------- function ps(md, ft, f, d, pk, dr, rg, sn, rs, rt, ml, gl, lp, dv) ( slider1 = md; slider2 = ft; slider3 = f; slider4 = d; slider5 = pk; slider6 = dr; slider7 = rg; slider8 = sn; slider9 = rs; slider10 = rt; slider11 = ml; slider12 = gl; slider13 = lp; slider14 = dv; slider15 = 100; slider16 = 0; slider17 = 1; slider18 = 0; ); function load_preset(p) ( p == 1 ? ps(0, 1, 250, 3.5, 4, 1, 0, 60, 40, 2, 0, 15, 1, 20) : // Init (the defaults) p == 2 ? ps(0, 1, 250, 3.5, 5, 1, 0, 60, 30, 2, 0, 15, 1, 20) : // Funk Up p == 3 ? ps(0, 0, 900, 3, 6, 0, 0, 65, 35, 2, 0, 15, 1, 30) : // Bass Quack (down) p == 4 ? ps(1, 1, 300, 4, 7, 1, 0, 60, 40, 3, 0, 5, 1, 25) : // Ship Steps (1/16 loop) p == 5 ? ps(1, 0, 200, 4.5, 8, 1, 0, 60, 40, 2, 1, 45, 0, 15) : // Random Gurgle p == 6 ? ps(2, 1, 250, 4, 6, 1, 0, 70, 30, 3, 0, 10, 1, 20) : // Envelope Steps p == 7 ? ps(0, 3, 300, 4, 3, 1, 0, 55, 60, 2, 0, 15, 1, 0) : // Notch Sweep p == 8 ? ps(0, 1, 180, 3.5, 9, 1, 0, 60, 90, 2, 0, 15, 1, 10) : // Slow Talker p == 9 ? ps(0, 1, 400, 3, 6, 1, 1, 70, 15, 2, 0, 15, 1, 35) : // Clav Wah p == 10 ? ps(1, 1, 500, 3.5, 10, 1, 1, 60, 40, 4, 0, 0, 1, 10) : // Synth Blips (1/32) p == 11 ? ps(0, 0, 120, 5, 2, 1, 0, 50, 70, 2, 0, 15, 1, 0); // Lowpass Bloom p >= 1 && p <= 11 ? slider_automate(262143); // sliders 1-18 p == 12 ? ( // Reset all to defaults ps(0, 1, 250, 3.5, 4, 1, 0, 60, 40, 2, 0, 15, 1, 20); slider19 = 0; slider20 = 1; slider21 = 0; slider22 = 30; slider23 = 120; slider24 = 6000; slider25 = -0.3; slider26 = 1; slider_automate(67108863); ); ); in_init(); ost_init(20000); ostL.ost_ch_init(); ostR.ost_ch_init(); sm = 1 - exp(-1 / (0.03 * srate)); !ui_dflt ? ui_thm = 5; !ui_dflt ? ui_hw = 1; BLU = 0x1D3BC4; BLUD = 0x142A8C; SKYB = 0x46B2EE; REDP = 0xD43A2C; WHT = 0xF2F4FA; CHR = 0xC9CDD2; update(); mix_s = mix_t; fc = fbase; stv = 0; stg = 0; lstep = -1; bpos = 0; // ================= EQ (the Echo engine, names q_..., sliders 27 - 44) ================= // a preset mask of the EQ unit, one slider at a time function cb_auto(m, off) local(k) ( k = 1; loop(63, m - 2 * floor(m / 2) ? ui_auto(k + off); m = floor(m / 2); k += 1; ); ); #ui_man = "gfx-echofish"; // the ? pop-up links to this manual // ---------- delay memory: two lines of 4.2 s ---------- q_dlen = ceil(4.2 * srate) + 8; q_bufL = 100000; q_bufR = q_bufL + q_dlen; memset(q_bufL, 0, q_dlen * 2); q_wp = 0; function q_tanh_(x) local(e) ( x = min(max(x, -20), 20); e = exp(2 * x); (e - 1) / (e + 1) ); // cubic read, d samples behind the write position function q_rdl(q_buf, d) local(p, i, f, q_x0, x1, x2, q_x3) ( p = q_wp - d; p < 0 ? p += q_dlen; i = floor(p); f = p - i; q_x0 = q_buf[(i - 1 + q_dlen) % q_dlen]; x1 = q_buf[i % q_dlen]; x2 = q_buf[(i + 1) % q_dlen]; q_x3 = q_buf[(i + 2) % q_dlen]; (((0.5 * (q_x3 - q_x0) + 1.5 * (x1 - x2)) * f + (q_x0 - 2.5 * x1 + 2 * x2 - 0.5 * q_x3)) * f + 0.5 * (x2 - q_x0)) * f + x1; ); // the feedback path: cuts, then the echo type's colour function q_loopproc(x) instance(q_hz, q_lz, lz2, q_bz) ( q_hz += (x - q_hz) * q_lo_c; x -= q_hz; // lo cut q_lz += (x - q_lz) * q_hi_c; x = q_lz; // hi cut q_etype == 0 ? ( // tape: soft saturation, a little extra roll-off lz2 += (x - lz2) * q_tape_c; x = lz2; x = q_tanh_(x * q_drv_g) / q_drv_g; ) : q_etype == 1 ? ( // bucket: darker, firmer clip lz2 += (x - lz2) * q_bbd_c; q_bz += (lz2 - q_bz) * q_bbd_c; x = q_bz; x = x / (1 + abs(x) * (q_drv_g - 1) * 0.6 + 0.08 * x * x); ) : ( // digital: clean (a safety limit only) x = abs(x) > 1.5 ? q_tanh_(x / 1.5) * 1.5 : x; ); x; ); // ---------- vintage wet EQ: HPF 140 Hz, 500 Hz cut, LPF 5 kHz (the classic studio reverb-send trick) ---------- // vt_set(m): m = 0 off, 1/2/3 = -3/-6/-9 dB at 500 Hz. Shared coefficients, per-channel state. function q_vt_bq(t, f, q, gdb) local(w, cw, al, A, q_a0) ( w = 2 * $pi * min(f, srate * 0.45) / srate; cw = cos(w); al = sin(w) / (2 * q); t == 0 ? ( // high-pass q_a0 = 1 + al; q_vtp[0] = (1 + cw) * 0.5 / q_a0; q_vtp[1] = -(1 + cw) / q_a0; q_vtp[2] = q_vtp[0]; ) : t == 1 ? ( // low-pass q_a0 = 1 + al; q_vtp[0] = (1 - cw) * 0.5 / q_a0; q_vtp[1] = (1 - cw) / q_a0; q_vtp[2] = q_vtp[0]; ) : ( // peaking A = 10 ^ (gdb / 40); q_a0 = 1 + al / A; q_vtp[0] = (1 + al * A) / q_a0; q_vtp[1] = -2 * cw / q_a0; q_vtp[2] = (1 - al * A) / q_a0; ); q_vtp[3] = -2 * cw / q_a0; q_vtp[4] = (1 - (t == 2 ? al / A : al)) / q_a0; ); q_VTC = 32000; // 15 coefficients function q_vt_set(m) ( q_vt_on = m > 0; q_vtp = q_VTC; q_vt_bq(0, 140, 0.7071, 0); q_vtp = q_VTC + 5; q_vt_bq(2, 500, 0.9, -3 * m); q_vtp = q_VTC + 10; q_vt_bq(1, 5000, 0.7071, 0); ); function q_vt_proc(x) instance(a1, a2, b1, b2, c1, c2) local(y) ( y = q_VTC[0] * x + a1; a1 = q_VTC[1] * x - q_VTC[3] * y + a2; a2 = q_VTC[2] * x - q_VTC[4] * y; x = y; y = q_VTC[5] * x + b1; b1 = q_VTC[6] * x - q_VTC[8] * y + b2; b2 = q_VTC[7] * x - q_VTC[9] * y; x = y; y = q_VTC[10] * x + c1; c1 = q_VTC[11] * x - q_VTC[13] * y + c2; c2 = q_VTC[12] * x - q_VTC[14] * y; y; ); function q_update() local(q) ( q_etype = slider27; sync = slider28; q = lfo_div_q(slider30); q_t_ms = sync ? q * 60000 / max(tempo, 20) : slider29; q_t_ms = min(max(q_t_ms, 1), 4000); fb = min(slider31 / 100, 1.1); q_drv_g = 1 + 3 * slider32 / 100; q_lo_c = 1 - exp(-2 * $pi * slider33 / srate); q_hi_c = 1 - exp(-2 * $pi * min(slider34, srate * 0.45) / srate); q_tape_c = 1 - exp(-2 * $pi * 9000 / srate); q_bbd_c = 1 - exp(-2 * $pi * 4200 / srate); q_duck = slider35 / 100; q_ping = slider36; q_frz = slider37; drift_macro(slider46); // DRIFT dial: 50% = the wow / flutter settings, 100% = twice, 0 = none q_wow = slider38 / 100 * dm_a; q_flt = slider39 / 100 * dm_a; q_dspd = slider40 * dm_s; q_mix_t = slider41 / 100; q_wid = slider42 / 100; q_out_t = 10 ^ (slider43 / 20); // time glides: tape slowly (pitch bend), bucket a bit quicker, digital fast q_t_glide = 1 - exp(-1 / ((q_etype == 0 ? 0.35 : q_etype == 1 ? 0.15 : 0.04) * srate)); slider44 != q_vt_m ? ( q_vt_m = slider44; q_vt_set(q_vt_m); ); ); // ---------- presets (leave drift speed, output and the I/O rows alone) ---------- function q_setp(m, sy, q_tm, dv, f, q_drp, lc, q_hc, q_dk, q_pp, w, q_flp, mx) ( slider27 = m; slider28 = sy; slider29 = q_tm; slider30 = dv; slider31 = f; slider32 = q_drp; slider33 = lc; slider34 = q_hc; slider35 = q_dk; slider36 = q_pp; slider38 = w; slider39 = q_flp; slider41 = mx; slider37 = 0; ); function q_load_preset(p) ( p == 1 ? q_setp(0, 1, 375, 10, 40, 30, 120, 6000, 0, 0, 20, 15, 35) : // Tape Dotted Eighth p == 2 ? q_setp(0, 0, 110, 10, 20, 40, 150, 5000, 0, 0, 15, 20, 30) : // Slapback p == 3 ? q_setp(0, 1, 500, 6, 85, 60, 200, 3500, 0, 0, 45, 30, 40) : // Dub Siren p == 4 ? q_setp(1, 1, 375, 9, 45, 40, 150, 3500, 30, 1, 10, 5, 35) : // Bucket Ping-Pong p == 5 ? q_setp(1, 0, 650, 10, 60, 50, 100, 3000, 0, 0, 25, 10, 45) : // Memory Lane p == 6 ? q_setp(2, 1, 375, 6, 30, 0, 80, 12000, 50, 0, 0, 0, 25) : // Vocal Quarter (ducked) p == 7 ? q_setp(2, 1, 375, 11, 35, 0, 100, 9000, 0, 1, 0, 0, 30) : // Digital Triplet Ping p == 8 ? q_setp(0, 0, 1200, 10, 70, 45, 250, 4000, 40, 0, 35, 20, 45) : // Ambient Wash p == 9 ? q_setp(0, 0, 20, 10, 105, 70, 300, 2500, 0, 0, 60, 40, 50); // Runaway Tape p >= 1 && p <= 9 ? cb_auto(24575, 26); // sliders 1-13, 15 p == 10 ? ( // Reset all to defaults q_setp(0, 1, 375, 10, 40, 30, 120, 6000, 0, 0, 20, 15, 35); slider40 = 0.3; slider42 = 100; slider43 = 0; slider21 = 0; slider22 = 30; slider23 = 120; slider24 = 6000; slider25 = -0.3; slider26 = 1; slider19 = 0; slider20 = 0; slider44 = 0; slider46 = 50; cb_auto(67108863, 26); // sliders 1-26 ); ); // ---------- state ---------- q_drW.drift_init(0.8 + rand(0.5)); q_drF.drift_init(0.8 + rand(0.5)); sm = 1 - exp(-1 / (0.03 * srate)); q_vuc = 1 - exp(-1 / (0.01 * srate)); q_dk_a = 1 - exp(-1 / (0.005 * srate)); q_dk_r = 1 - exp(-1 / (0.25 * srate)); q_pkL = 0; q_pkR = 0; q_vu_ms = 0; q_denv = 0; q_fph = 0; q_cph = 0; q_vt_m = -1; // defaults for new instances: Tolex, hardware (saved projects keep their own) q_update(); q_t_s = q_t_ms * 0.001 * srate; q_mix_s = q_mix_t; q_out_s = q_out_t; function q_proc(cb_inl, cb_inr) ( q_inL = cb_inl; q_inR = cb_inr; q_vu_ms += (0.5 * (q_inL * q_inL + q_inR * q_inR) - q_vu_ms) * q_vuc; q_mix_s += (q_mix_t - q_mix_s) * sm; q_out_s += (q_out_t - q_out_s) * sm; // delay time: glide + tape wow / flutter (+ the bucket's chorus on the tails) q_t_s += (q_t_ms * 0.001 * srate - q_t_s) * q_t_glide; q_fph += (6.5 + 1.5 * vF) / srate; q_fph >= 1 ? q_fph -= 1; q_cph += 0.6 / srate; q_cph >= 1 ? q_cph -= 1; q_mod = (q_wow * 0.0025 * q_vW + q_flt * 0.00022 * sin(2 * $pi * q_fph)) * srate; q_etype == 1 ? q_mod += 0.0004 * srate * sin(2 * $pi * q_cph); dL = min(max(q_t_s + q_mod, 3), q_dlen - 4); q_dR = q_ping ? dL : min(max(q_t_s * (1 + 0.03 * q_wid) + q_mod, 3), q_dlen - 4); // a touch wider when not ping-ponging q_eL = q_rdl(q_bufL, dL); q_eR = q_rdl(q_bufR, q_dR); // feedback path q_frz ? ( q_fL = q_eL; q_fR = q_eR; // freeze: hold the loop exactly, no new input q_bufL[q_wp] = q_ping ? q_fR : q_fL; q_bufR[q_wp] = q_ping ? q_fL : q_fR; ) : ( q_fL = q_lpL.q_loopproc(q_eL) * fb; q_fR = q_lpR.q_loopproc(q_eR) * fb; q_ping ? ( q_bufL[q_wp] = 0.5 * (q_inL + q_inR) + q_fR; // in on the left, then bounce q_bufR[q_wp] = q_fL; ) : ( q_bufL[q_wp] = q_inL + q_fL; q_bufR[q_wp] = q_inR + q_fR; ); ); q_wp += 1; q_wp >= q_dlen ? q_wp = 0; // ducking: the echoes dip while the input is playing lvl = max(abs(q_inL), abs(q_inR)); q_denv += (lvl - q_denv) * (lvl > q_denv ? q_dk_a : q_dk_r); q_dg = 1 - q_duck * min(q_denv * 4, 1); // vintage EQ on the echoes heard (the loop's own tone stays LO / HI CUT) q_vt_on ? ( q_eL = q_vtL.q_vt_proc(q_eL); q_eR = q_vtR.q_vt_proc(q_eR); ); // width of the echoes, then mix q_em = 0.5 * (q_eL + q_eR); q_es = 0.5 * (q_eL - q_eR) * q_wid; q_wL = (q_em + q_es) * q_dg; q_wR = (q_em - q_es) * q_dg; q_dry_g = min(2 * (1 - q_mix_s), 1); q_wet_g = min(2 * q_mix_s, 1); // MIX: dry stays full up to 50%, then fades q_oL = q_inL * q_dry_g + q_wL * q_wet_g; q_oR = q_inR * q_dry_g + q_wR * q_wet_g; // output stage q_oL = (q_oL) * q_out_s; q_oR = (q_oR) * q_out_s; q_pkL = max(q_pkL, abs(q_oL)); q_pkR = max(q_pkR, abs(q_oR)); q_out_l = q_oL; q_out_r = q_oR; ); q_ord = slider45; !ui_dflt ? pr_cur = 1; // a new instance opens on Init ui_dflt = 1; // theme defaults above apply once: @init re-runs (playback start, device reset) must not undo the saved theme @slider q_update(); q_ord = slider45; update(); @block q_update(); q_dinc = q_dspd * samplesblock / srate; q_vW = q_drW.drift_tick(q_dinc); vF = q_drF.drift_tick(q_dinc * 3); q_ord = slider45; update(); playing = play_state & 1; playing ? bpos = beat_position; bpm = tempo > 1 ? tempo : 120; // (no host tempo: 120) binc = bpm / 60 / srate; @serialize file_avail(0) != 0 ? ( // the preset in use (older projects end before it) file_var(0, pr_cur); pr_ver = 2; file_avail(0) != 0 ? file_var(0, pr_ver) : pr_cur >= 1 ? pr_cur += 1; // saved before INIT became preset 1 ) : pr_cur = 0; @sample in_process(spl0, spl1); !q_ord ? ( q_proc(in_l, in_r); in_l = q_out_l; in_r = q_out_r; ); // EQ first dl = in_l; dr = in_r; // envelope follower (on the mono input) det = abs(dl + dr) * 0.5 * sensg; env += (det - env) * (det > env ? env_a : env_r); e = min(env, 1) ^ 0.7; // sample and hold: a step at each division of the beat bpos += binc; sidx = floor(bpos * spb); sidx != lstep ? ( lstep = sidx; stv = loopp ? rnd(sidx % max(1, floor(spb * 4)) + 1) : rand(1); steph = 1; ); glide > 0.005 ? stg += (stv - stg) / (1 + glide * 0.35 * srate * 60 / (bpm * spb)) : stg = stv; // GLIDE: a share of the step length amt = mode == 0 ? e : mode == 1 ? stg : stg * e; // the cutoff, then the filter !hold ? fc = min(max(fbase * 2 ^ (dir * depth * amt), 25), min(18000, srate * 0.42)); fg = tan($pi * fc / srate); fa1 = 1 / (1 + fg * (fg + fk)); fa2 = fg * fa1; fa3 = fg * fa2; xl = th(dl * dg) / dg * (1 + (dg - 1) * 0.3); xr = th(dr * dg) / dg * (1 + (dg - 1) * 0.3); yl = FL.svf(xl); yr = FR.svf(xr); yl = th(yl * 0.7) / 0.7; yr = th(yr * 0.7) / 0.7; mix_s += ((fx_on ? mix_t : 0) - mix_s) * sm; oL = (dl + (yl - dl) * mix_s) * (fx_on ? og : 1); oR = (dr + (yr - dr) * mix_s) * (fx_on ? og : 1); q_ord ? ( q_proc(oL, oR); oL = q_out_l; oR = q_out_r; ); // EQ after oL = ostL.ost_band(oL * in_comp); oR = ostR.ost_band(oR * in_comp); ost_out(oL, oR); spl0 = ost_l; spl1 = ost_r; fcm = fc; envm = max(envm, e); @gfx 900 514 ui_begin(900, 514); dy_ink = WHT; dy_bg = BLU; dy_kst = 0; dy_nov = 1; function txt(x, y, s, a) ( gfx_setfont(4); ui_cola(WHT, a); ui_text(x * sc, y * sc, s); ); // a five-point star: filled, or as a ring of rhinestones function starpts(cx, cy, R) local(i, a) ( i = 0; loop(10, a = -$pi * 0.5 + i * $pi / 5; SPX[i] = cx + cos(a) * (i % 2 ? R * 0.42 : R); SPY[i] = cy + sin(a) * (i % 2 ? R * 0.42 : R); i += 1; ); ); function star(cx, cy, R, col) local(i, j) ( starpts(cx, cy, R); ui_col(col); i = 0; loop(10, j = (i + 1) % 10; gfx_triangle(cx * sc, cy * sc, SPX[i] * sc, SPY[i] * sc, SPX[j] * sc, SPY[j] * sc); i += 1; ); ); function rhinestar(cx, cy, R, n) local(i, j, t, k, x, y) ( starpts(cx, cy, R); i = 0; loop(10, j = (i + 1) % 10; k = 0; loop(n, t = k / n; x = SPX[i] + (SPX[j] - SPX[i]) * t; y = SPY[i] + (SPY[j] - SPY[i]) * t; ui_col(0x8E949C); gfx_circle(x * sc, y * sc, 2.4 * sc, 1, 1); ui_col(0xE8ECF2); gfx_circle(x * sc, y * sc, 1.7 * sc, 1, 1); ui_cola(0xFFFFFF, 0.9); gfx_circle((x - 0.6) * sc, (y - 0.6) * sc, 0.7 * sc, 1, 1); k += 1; ); i += 1; ); ); // a slide switch (n positions) with a coloured cap; returns the new position when clicked function slide(x, y, w, n, pos, cap, labs, title) local(cx, hit, i, sw) ( sw = (w - 16) / max(n - 1, 1); ui_col(0x0A0A12); ui_rrect(x * sc, (y - 6) * sc, w * sc, 12 * sc, 3 * sc); cx = x + 8 + pos * sw; ui_cola(0x000000, 0.4); gfx_rect((cx - 11) * sc, (y - 7) * sc, 24 * sc, 16 * sc); ui_col(cap); gfx_rect((cx - 12) * sc, (y - 8) * sc, 24 * sc, 16 * sc); ui_cola(0xFFFFFF, 0.35); gfx_rect((cx - 12) * sc, (y - 8) * sc, 24 * sc, 3 * sc); ui_cola(0x000000, 0.25); i = -2; loop(5, gfx_rect((cx + i * 4) * sc, (y - 4) * sc, 1 * sc, 9 * sc); i += 1; ); gfx_setfont(6, "Arial", 12 * sc, 'b'); ui_col(WHT); ui_text((x + w * 0.5) * sc, (y + 10) * sc, title); hit = pressed && drag_id == 0 && mouse_x >= x * sc && mouse_x < (x + w) * sc && abs(mouse_y - y * sc) < 10 * sc; hit ? ( drag_id = -1; pos = n == 2 ? 1 - pos : min(n - 1, max(0, floor((mouse_x / sc - x) / w * n))); ); pos; ); // a starfish: five soft arms, coral, with a row of dots down each arm function starfish(cx, cy, R, rot) local(i, j, a, k, t) ( i = 0; loop(10, a = -$pi * 0.5 + rot + i * $pi / 5; SPX[i] = cx + cos(a) * (i % 2 ? R * 0.36 : R); SPY[i] = cy + sin(a) * (i % 2 ? R * 0.36 : R); i += 1; ); k = 0; loop(2, ui_col(k == 0 ? 0xB8502A : 0xF28A4C); i = 0; loop(10, j = (i + 1) % 10; gfx_triangle((cx + k * 0) * sc, cy * sc, (SPX[i] + (k ? 0 : (SPX[i] - cx) * 0.06)) * sc, (SPY[i] + (k ? 0 : (SPY[i] - cy) * 0.06)) * sc, (SPX[j] + (k ? 0 : (SPX[j] - cx) * 0.06)) * sc, (SPY[j] + (k ? 0 : (SPY[j] - cy) * 0.06)) * sc); i += 1; ); i = 0; loop(5, gfx_circle(SPX[i * 2] * sc, SPY[i * 2] * sc, R * (k ? 0.15 : 0.19) * sc, 1, 1); i += 1; ); k += 1; ); ui_cola(0xFFFFFF, 0.18); gfx_circle((cx - R * 0.08) * sc, (cy - R * 0.1) * sc, R * 0.3 * sc, 1, 1); i = 0; loop(5, k = 1; loop(3, t = k / 4; ui_col(0xFFE2C8); gfx_circle((cx + (SPX[i * 2] - cx) * t) * sc, (cy + (SPY[i * 2] - cy) * t) * sc, max(1, R * 0.06 * (1.2 - t * 0.5)) * sc, 1, 1); k += 1; ); i += 1; ); ui_col(0xFFE2C8); gfx_circle(cx * sc, cy * sc, R * 0.08 * sc, 1, 1); ); // a palm tree silhouette (base at x, y; h tall), in col function palm(x, y, h, col, a) local(i, t, px, py, nx, ny, f, an, l, fx, fy, w) ( ui_cola(col, a); i = 0; px = x; py = y; loop(12, t = (i + 1) / 12; nx = x + sin(t * 1.3) * h * 0.18; ny = y - t * h; w = (1 - t * 0.5) * h * 0.035; gfx_triangle((px - w) * sc, py * sc, (px + w) * sc, py * sc, (nx + w * 0.9) * sc, ny * sc, (nx - w * 0.9) * sc, ny * sc); px = nx; py = ny; i += 1; ); f = 0; loop(7, an = -$pi + f / 6 * $pi * 1.0 + (f > 3 ? 0.15 : -0.15); l = h * (f == 3 ? 0.42 : 0.55); i = 0; fx = px; fy = py; loop(10, t = (i + 1) / 10; nx = px + cos(an) * l * t; ny = py + sin(an) * l * t + l * 0.55 * t * t; w = sin(t * $pi) * h * 0.07; gfx_triangle(fx * sc, fy * sc, nx * sc, ny * sc, (fx + nx) * 0.5 * sc, ((fy + ny) * 0.5 + w) * sc); gfx_line(fx * sc, fy * sc, nx * sc, ny * sc, 1); fx = nx; fy = ny; i += 1; ); f += 1; ); gfx_circle((px - h * 0.02) * sc, (py + h * 0.03) * sc, h * 0.035 * sc, 1, 1); gfx_circle((px + h * 0.03) * sc, (py + h * 0.04) * sc, h * 0.03 * sc, 1, 1); ); // ---------- body ---------- fh = 506; ui_col(0x0B0B0C); gfx_rect(0, 0, gfx_w, gfx_h); pk_chassis(8 * sc, 4 * sc, 884 * sc, fh * sc, BLU); gfx_gradrect(10 * sc, 6 * sc, 880 * sc, (fh - 8) * sc, 0.12, 0.25, 0.8, 0, 0, 0, 0, 0, 0, 0, 0, 0.0008 / sc); // header ui_col(WHT); gfx_circle(36 * sc, 30 * sc, 13 * sc, 1, 1); gfx_setfont(4); ui_col(BLU); ui_text(36 * sc, 25 * sc, "GFX"); rhinestar(78, 31, 17, 4); gfx_setfont(6, "Arial", 26 * sc, 'bi'); ui_col(WHT); gfx_x = 104 * sc; gfx_y = 15 * sc; gfx_drawstr("ECHOFISH"); txt(320, 26, "ENVELOPE + STEP FILTER", 0.75); pk_ledbtn(400 * sc, 19 * sc, 26 * sc, 22 * sc, slider18, 0xFF3A2A) ? ( slider18 = 1 - slider18; ui_auto(18); ); gfx_setfont(4); ui_col(WHT); gfx_x = 432 * sc; gfx_y = 25 * sc; gfx_drawstr("HOLD"); pk_ledbtn(474 * sc, 19 * sc, 26 * sc, 22 * sc, slider17, dy_grn) ? ( slider17 = 1 - slider17; ui_auto(17); ); gfx_setfont(4); ui_col(WHT); gfx_x = 506 * sc; gfx_y = 25 * sc; gfx_drawstr("ON"); pk_pextra = "Reset all to defaults"; // the house preset display: PRESETS, prev / next, LCD (click it for the list) k_ = pk_preset(540 * sc, 12 * sc, 282 * sc, 34 * sc, pr_cur, "Init|Funk Up|Bass Quack (down)|Ship Steps|Random Gurgle|Envelope Steps|Notch Sweep|Slow Talker|Clav Wah|Synth Blips|Lowpass Bloom"); k_ > 0 ? ( load_preset(k_); pr_cur = k_ > 11 ? 0 : k_; ); ui_size_picker(834 * sc, 20 * sc); // ---------- display: the cutoff over time ---------- ui_col(0x050507); ui_rrect(18 * sc, 58 * sc, 344 * sc, 124 * sc, 5 * sc); ui_col(0x140808); gfx_rect(22 * sc, 62 * sc, 336 * sc, 116 * sc); gi = 0; loop(HN - 1, HFC[gi] = HFC[gi + 1]; HST[gi] = HST[gi + 1]; gi += 1; ); HFC[HN - 1] = fcm; HST[HN - 1] = lstep != lsd; lsd = lstep; palm(76, 178, 82, 0x000000, 0.8); gi = 0; loop(4, gf_ = gi == 0 ? 100 : gi == 1 ? 1000 : gi == 2 ? 10000 : 50; gyy = 174 - log(gf_ / 25) / log(720) * 108; ui_cola(0xFF4030, 0.14); gfx_rect(22 * sc, gyy * sc, 336 * sc, 1 * sc); gi += 1; ); txt(40, 164 - log(100 / 25) / log(720) * 108, "100", 0.35); txt(40, 164 - log(1000 / 25) / log(720) * 108, "1k", 0.35); txt(40, 164 - log(10000 / 25) / log(720) * 108, "10k", 0.35); gi = 1; loop(HN - 1, gx0 = 22 + (gi - 1) / (HN - 1) * 336; gx1 = 22 + gi / (HN - 1) * 336; gy0 = 174 - log(max(HFC[gi - 1], 25) / 25) / log(720) * 108; gy1 = 174 - log(max(HFC[gi], 25) / 25) / log(720) * 108; mode && HST[gi] ? ( ui_cola(0xFF6A4A, 0.25); gfx_rect(gx1 * sc, 62 * sc, 1 * sc, 116 * sc); ); ui_cola(0xFF5040, 0.35); gfx_line(gx0 * sc, (gy0 + 1.5) * sc, gx1 * sc, (gy1 + 1.5) * sc, 1); ui_col(0xFFB0A0); gfx_line(gx0 * sc, gy0 * sc, gx1 * sc, gy1 * sc, 1); gfx_line(gx0 * sc, (gy0 - 0.8) * sc, gx1 * sc, (gy1 - 0.8) * sc, 1); gi += 1; ); fcm >= 1000 ? sprintf(#v, "%.2f kHz", fcm / 1000) : sprintf(#v, "%.0f Hz", fcm); gfx_setfont(4); ui_col(0xFFB0A0); gfx_x = 300 * sc; gfx_y = 66 * sc; gfx_drawstr(#v); envd = max(envm, envd * 0.85); envm = 0; pk_led(34 * sc, 72 * sc, 4 * sc, envd > 0.08, 0xFF3A2A); gfx_setfont(4); ui_cola(0xFFB0A0, 0.8); gfx_x = 42 * sc; gfx_y = 67 * sc; gfx_drawstr("ENV"); // ---------- the split block: UP (blue) / DOWN (red), DEPTH and FREQ ---------- ui_col(slider6 ? SKYB : ui_mix(SKYB, BLUD, 0.55)); gfx_rect(376 * sc, 58 * sc, 118 * sc, 124 * sc); ui_col(!slider6 ? REDP : ui_mix(REDP, BLUD, 0.55)); gfx_rect(496 * sc, 58 * sc, 118 * sc, 124 * sc); ui_col(0x0A0A12); gfx_triangle(388 * sc, 78 * sc, 402 * sc, 78 * sc, 395 * sc, 66 * sc); gfx_triangle(588 * sc, 66 * sc, 602 * sc, 66 * sc, 595 * sc, 78 * sc); pressed && drag_id == 0 && mouse_y >= 58 * sc && mouse_y < 90 * sc ? ( mouse_x >= 376 * sc && mouse_x < 416 * sc ? ( slider6 = 1; ui_auto(6); drag_id = -1; ); mouse_x >= 574 * sc && mouse_x < 614 * sc ? ( slider6 = 0; ui_auto(6); drag_id = -1; ); ); sprintf(#v, "%.1f oct", slider4); dy_ink = 0x0A0A12; dy_knob(435, 118, 22, 4, 0, 6, 3.5, 0, WHT, "DEPTH", #v); slider3 >= 1000 ? sprintf(#v, "%.2f kHz", slider3 / 1000) : sprintf(#v, "%.0f Hz", slider3); dy_knob(555, 118, 22, 3, 60, 3000, 250, 1, WHT, "FREQ", #v); dy_ink = WHT; // ---------- right: filter type, three slide switches ---------- dy_step = 4; dy_steps = "LP|BP|HP|N"; dy_knob(668, 124, 18, 2, 0, 3, 1, 0, WHT, "FILTER", ""); gs_ = slide(732, 76, 140, 2, slider7, 0x3A70E8, "", "RANGE LO / HI"); gs_ != slider7 ? ( slider7 = gs_; ui_auto(7); ); gs_ = slide(732, 116, 140, 2, slider13, 0xF2F4FA, "", "PATTERN FREE / LOOP"); gs_ != slider13 ? ( slider13 = gs_; ui_auto(13); ); gs_ = slide(732, 156, 140, 3, slider1, 0xE0402E, "", "ENV / S&H / BOTH"); gs_ != slider1 ? ( slider1 = gs_; ui_auto(1); ); // ---------- knob row ---------- ui_cola(WHT, 0.18); gfx_rect(18 * sc, 192 * sc, 864 * sc, 1 * sc); sprintf(#v, "%.1f", slider5); dy_knob(52, 236, 16, 5, 0.5, 15, 4, 1, WHT, "PEAK", #v); sprintf(#v, "%.0f%%", slider8); dy_knob(132, 236, 16, 8, 0, 100, 60, 0, WHT, "SENS", #v); sprintf(#v, "%.0f%%", slider9); dy_knob(212, 236, 16, 9, 0, 100, 40, 0, WHT, "RESPONSE", #v); ui_cola(WHT, 0.18); gfx_rect(260 * sc, 200 * sc, 1 * sc, 82 * sc); dy_step = 5; dy_steps = "1/2|1/4|1/8|1/16|1/32"; dy_knob(312, 236, 16, 10, 0, 4, 2, 0, WHT, "RATE", ""); gi = 0; loop(3, gx_ = 384 + gi * 26; ui_col(slider11 == gi ? WHT : BLUD); gfx_circle(gx_ * sc, 230 * sc, 10 * sc, 1, 1); ui_col(WHT); gfx_circle(gx_ * sc, 230 * sc, 10 * sc, 0, 1); gfx_setfont(6, "Arial", 11 * sc, 'b'); ui_col(slider11 == gi ? BLU : WHT); ui_text(gx_ * sc, 224 * sc, gi == 0 ? "1" : gi == 1 ? "2" : "4"); pressed && drag_id == 0 && (mouse_x - gx_ * sc) ^ 2 + (mouse_y - 230 * sc) ^ 2 < (11 * sc) ^ 2 ? ( slider11 = gi; ui_auto(11); drag_id = -1; ); gi += 1; ); gfx_setfont(5); ui_col(WHT); ui_text(410 * sc, 248 * sc, "MULT"); sprintf(#v, "%.0f%%", slider12); dy_knob(482, 236, 16, 12, 0, 100, 15, 0, WHT, "GLIDE", #v); ui_cola(WHT, 0.18); gfx_rect(530 * sc, 200 * sc, 1 * sc, 82 * sc); sprintf(#v, "%.0f%%", slider14); dy_knob(582, 236, 16, 14, 0, 100, 20, 0, WHT, "DRIVE", #v); sprintf(#v, "%.0f%%", slider15); dy_knob(662, 236, 16, 15, 0, 100, 100, 0, WHT, "MIX", #v); sprintf(#v, "%+.1f dB", slider16); dy_knob(742, 236, 16, 16, -12, 12, 0, 0, WHT, "OUTPUT", #v); starfish(830, 236, 30, 0.25); star(866, 206, 6, WHT); star(800, 204, 5, SKYB); // ================= the EQ strip ================= // (Starfish's style) the echo strip: the Echo engine, stripped back ui_cola(WHT, 0.3); gfx_rect(14 * sc, 296 * sc, 872 * sc, 1 * sc); gfx_setfont(6, "Arial", 20 * sc, 'bi'); ui_col(WHT); gfx_x = 24 * sc; gfx_y = 304 * sc; gfx_drawstr("ECHO"); star(96, 316, 6, SKYB); function qbtn(x, on, col, s) local(h) ( h = pk_ledbtn(x * sc, 306 * sc, 26 * sc, 20 * sc, on, col); gfx_setfont(4); ui_col(WHT); gfx_x = (x + 31) * sc; gfx_y = 311 * sc; gfx_drawstr(s); h; ); qbtn(124, slider28, dy_grn, "SYNC") ? ( slider28 = 1 - slider28; ui_auto(28); ); qbtn(200, slider36, dy_grn, "PING-PONG") ? ( slider36 = 1 - slider36; ui_auto(36); ); qbtn(306, slider37, 0xFF3A2A, "FREEZE") ? ( slider37 = 1 - slider37; ui_auto(37); ); qbtn(392, slider44 > 0, 0xFFB030, slider44 == 0 ? "VINTAGE" : slider44 == 1 ? "VINTAGE -3" : slider44 == 2 ? "VINTAGE -6" : "VINTAGE -9") ? ( slider44 = (slider44 + 1) % 4; ui_auto(44); ); gfx_setfont(4); ui_cola(WHT, 0.75); gfx_x = 660 * sc; gfx_y = 311 * sc; gfx_drawstr("ORDER"); ui_button(702 * sc, 304 * sc, 176 * sc, 24 * sc, slider45 ? "ECHO > FILTER" : "FILTER > ECHO", 0, 1) ? ( slider45 = 1 - slider45; ui_auto(45); ); gs_ = slide(24, 358, 150, 3, slider27, SKYB, "", "TAPE / BUCKET / DIGITAL"); gs_ != slider27 ? ( slider27 = gs_; ui_auto(27); ); ui_cola(WHT, 0.18); gfx_rect(190 * sc, 336 * sc, 1 * sc, 62 * sc); dy_ink = WHT; dy_kst = 0; slider28 ? ( dy_knob(232, 362, 15, 30, 0, 15, 10, 0, WHT, "TIME", lfo_div_name(slider30)); slider30 = floor(slider30 + 0.5); ) : ( sprintf(#v, "%.0f ms", slider29); dy_knob(232, 362, 15, 29, 20, 2000, 375, 1, WHT, "TIME", #v); ); sprintf(#v, "%.0f%%", slider31); dy_knob(298, 362, 15, 31, 0, 110, 40, 0, WHT, "FEEDBACK", #v); sprintf(#v, "%.0f%%", slider32); dy_knob(364, 362, 15, 32, 0, 100, 30, 0, WHT, "DRIVE", #v); ui_cola(WHT, 0.18); gfx_rect(400 * sc, 336 * sc, 1 * sc, 62 * sc); sprintf(#v, "%.0f Hz", slider33); dy_kf = 1; dy_knob(436, 362, 15, 33, 20, 1000, 120, 1, WHT, "LO CUT", #v); sprintf(#v, "%.0f Hz", slider34); dy_knob(502, 362, 15, 34, 1000, 20000, 6000, 1, WHT, "HI CUT", #v); ui_cola(WHT, 0.18); gfx_rect(538 * sc, 336 * sc, 1 * sc, 62 * sc); sprintf(#v, "%.0f%%", slider35); dy_knob(574, 362, 15, 35, 0, 100, 0, 0, WHT, "DUCK", #v); sprintf(#v, "%.0f%%", slider46); dy_knob(673, 362, 15, 46, 0, 100, 50, 0, WHT, "DRIFT", #v); ui_cola(WHT, 0.18); gfx_rect(742 * sc, 336 * sc, 1 * sc, 62 * sc); sprintf(#v, "%.0f%%", slider42); dy_knob(778, 362, 15, 42, 0, 100, 100, 0, WHT, "WIDTH", #v); sprintf(#v, "%.0f%%", slider41); dy_knob(846, 362, 15, 41, 0, 100, 35, 0, WHT, "MIX", #v); // ---------- IN / OUT band ---------- ui_cola(WHT, 0.3); gfx_rect(14 * sc, 412 * sc, 872 * sc, 1 * sc); gfx_setfont(4); ui_cola(WHT, 0.8); gfx_x = 30 * sc; gfx_y = 456 * sc; gfx_drawstr("IN / OUT"); dy_ink = WHT; dy_bg = BLU; dy_nov = 1; rd_iok = 20; rd_ioc1 = 0x1C2440; rd_ioc2 = 0xF2C21B; rd_ioc3 = 0xFF5A3A; rd_io(96, 434, 19, 20, 21, 26); dy_nov = 0; pk_end(); ui_end();