desc:GFX Tilt EQ //tags: equalizer eq tilt tone filter high-pass low-pass mid-side //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 tilt equaliser with high- and low-pass filters, in the spirit of the classic one-knob hi-fi and mastering tilt // controls (personal use; the sound is designed from scratch). // TILT - one knob seesaws the whole spectrum around the PIVOT: turn right and the top rises as the bottom // falls (brighter), turn left for darker. The value is the lift / cut at each end: up to 6 dB (12 dB end to end). // PIVOT - the balance point, 100 Hz - 5 kHz. // GENTLE / STEEP - GENTLE is a long, even 6 dB/oct slope across the whole range; STEEP turns over more quickly // around the pivot and leaves the far ends flatter. // HIGH-PASS / LOW-PASS - FREQ (fully left / right = off) and SLOPE: 6, 12, 18, 24, 36 or 48 dB/oct (Butterworth). // STEREO / MID / SIDE - where the tilt works: on both sides, on the middle only or on the sides only // (e.g. darken the sides, brighten the centre). The filters always work on both sides. // OUTPUT, EQ IN. // STYLE (header) switches between two looks: HORIZON (sky and sea, gold) and CLASSIC (cream and red); // HW switches between the hardware panel and a flat one. // PRESETS. IN / OUT (bottom row): TRIM with the GUARD key, SAT and the OUT stage (OFF / CLIP / LIM). 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 slider1:0<-6,6,0.1>-Tilt (dB at each end) slider2:800<100,5000,1>-Pivot (Hz) slider3:0<0,1,1{Gentle,Steep}>-Shape slider4:10<10,1000,1>-High-pass (Hz, 10 = off) slider5:1<0,5,1{6,12,18,24,36,48}>-High-pass slope (dB/oct) slider6:22000<1000,22000,10>-Low-pass (Hz, 22000 = off) slider7:1<0,5,1{6,12,18,24,36,48}>-Low-pass slope (dB/oct) slider8:0<0,2,1{Stereo,Mid,Side}>-Tilt on slider9:0<-12,12,0.1>-Output (dB) slider10:1<0,1,1{Out,In}>-EQ in slider11:0<-24,12,0.1>-Input trim (dB) slider12:1<0,1,1{Off,On}>-Input guard slider13:0<0,100,1>-Band sat (%) slider14:30<0,100,1>-Bias (%) slider15:120<40,400,1>-Sat low band (Hz) slider16:6000<2000,16000,10>-Sat high band (Hz) slider17:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider18: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-tilt-eq"; // the ? pop-up links to this manual RSP = 30000; QT = 30400; // Butterworth section Qs: order 2, 3 (+ one pole), 4, 6, 8 QT[0] = 0.70711; QT[1] = 1.0; QT[2] = 0.54120; QT[3] = 1.30656; QT[4] = 0.51764; QT[5] = 0.70711; QT[6] = 1.93185; QT[7] = 0.50980; QT[8] = 0.60134; QT[9] = 0.89998; QT[10] = 2.56292; // first-order sections (bilinear): high-pass / low-pass, and the gentle tilt shelf function op_run(x) instance(n0, n1, d1, x1, y1) local(y) ( y = n0 * x + n1 * x1 - d1 * y1; x1 = x; y1 = y; y; ); function op_hp(f) instance(n0, n1, d1) local(k) ( k = tan($pi * min(f, srate * 0.45) / srate); n0 = 1 / (1 + k); n1 = -n0; d1 = (k - 1) / (k + 1); ); function op_lp(f) instance(n0, n1, d1) local(k) ( k = tan($pi * min(f, srate * 0.45) / srate); n0 = k / (1 + k); n1 = n0; d1 = (k - 1) / (k + 1); ); function op_tilt(f, gdb) instance(n0, n1, d1) local(k, a, b, d0) ( k = tan($pi * min(f, srate * 0.45) / srate); a = 10 ^ (gdb / 40); b = 1 / a; // first-order high shelf, gain gdb d0 = b / k + 1; n0 = (a / k + 1) / d0 / a; n1 = (1 - a / k) / d0 / a; d1 = (1 - b / k) / d0; // / a: pivots round 0 dB ); function op_db(f) instance(n0, n1, d1) local(w, c1, nu, de) ( w = 2 * $pi * f / srate; c1 = cos(w); nu = n0 * n0 + n1 * n1 + 2 * n0 * n1 * c1; de = 1 + d1 * d1 + 2 * d1 * c1; 10 * log10(max(nu, 0.000000000001) / max(de, 0.000000000001)); ); // a Butterworth filter of order n (1, 2, 3, 4, 6, 8): up to four biquads + one pole function bw_set(type, f, n) instance(nb, odd, q0) ( odd = n == 1 || n == 3; nb = n == 1 ? 0 : n == 2 || n == 3 ? 1 : n == 4 ? 2 : n == 6 ? 3 : 4; q0 = n == 2 ? 0 : n == 3 ? 1 : n == 4 ? 2 : n == 6 ? 4 : 7; odd ? ( type ? this.p.op_hp(f) : this.p.op_lp(f); ); nb > 0 ? this.s1.bq_set(type, f, QT[q0], 0); nb > 1 ? this.s2.bq_set(type, f, QT[q0 + 1], 0); nb > 2 ? this.s3.bq_set(type, f, QT[q0 + 2], 0); nb > 3 ? this.s4.bq_set(type, f, QT[q0 + 3], 0); ); function bw_run(x) instance(nb, odd) ( odd ? x = this.p.op_run(x); nb > 0 ? x = this.s1.bq_run(x); nb > 1 ? x = this.s2.bq_run(x); nb > 2 ? x = this.s3.bq_run(x); nb > 3 ? x = this.s4.bq_run(x); x; ); function bw_db(f) instance(nb, odd) local(d) ( d = 0; odd ? d += this.p.op_db(f); nb > 0 ? d += this.s1.bq_db(f); nb > 1 ? d += this.s2.bq_db(f); nb > 2 ? d += this.s3.bq_db(f); nb > 3 ? d += this.s4.bq_db(f); d; ); function ordr(i) ( i == 0 ? 1 : i == 1 ? 2 : i == 2 ? 3 : i == 3 ? 4 : i == 4 ? 6 : 8 ); // the tilt: gentle = first-order shelf, steep = a biquad shelf, both pivoting round 0 dB function tl_set() instance() ( steep ? this.b.bq_set(3, slider2, 0.707, 2 * slider1) : this.o.op_tilt(slider2, 2 * slider1); ); function tl_run(x) instance() ( steep ? this.b.bq_run(x) * tl_comp : this.o.op_run(x); ); function tl_db(f) instance() ( steep ? this.b.bq_db(f) - slider1 : this.o.op_db(f); ); function update() local(b, cs) ( cs = 0; b = 1; loop(10, cs += slider(b) * (b * 7.31 + 1); b += 1; ); cs != last_cs ? ( redraw = 1; last_cs = cs; ); steep = slider3; tl_comp = 10 ^ (-slider1 / 20); tl_on = abs(slider1) > 0.04; TLL.tl_set(); TLR.tl_set(); hp_on = slider4 > 10.5; lp_on = slider6 < 21990; hp_on ? ( HPL.bw_set(1, slider4, ordr(slider5)); HPR.bw_set(1, slider4, ordr(slider5)); ); lp_on ? ( LPL.bw_set(0, slider6, ordr(slider7)); LPR.bw_set(0, slider6, ordr(slider7)); ); tmode = slider8; og = 10 ^ (slider9 / 20); eq_on = slider10; in_setup(slider11, slider12); ins_output_setup(13); ); // ---------- presets (leave INPUT and OUTPUT alone) ---------- function pt(t, pv, sh, hp, hs, lp, ls, md) ( slider1 = t; slider2 = pv; slider3 = sh; slider4 = hp; slider5 = hs; slider6 = lp; slider7 = ls; slider8 = md; slider9 = 0; slider10 = 1; ); function load_preset(p) ( p == 1 ? pt(1.5, 1000, 0, 25, 3, 22000, 1, 0) : // Mix Bus Lift p == 2 ? pt(-1.5, 800, 0, 10, 1, 18000, 1, 0) : // Warm It Up p == 3 ? pt(3, 1500, 1, 80, 2, 22000, 1, 0) : // Vocal Forward p == 4 ? pt(-3, 600, 0, 30, 3, 9000, 1, 0) : // Dark & Thick p == 5 ? pt(2, 700, 0, 10, 1, 22000, 1, 2) : // Bright Sides p == 6 ? pt(-2, 2000, 1, 10, 1, 22000, 1, 2) : // Tame the Sides p == 7 ? pt(0, 800, 0, 300, 3, 3000, 3, 0) : // Telephone Band p == 8 ? pt(2.5, 400, 0, 120, 5, 22000, 1, 0) : // Thin Out (steep HPF) p == 9 ? pt(-1, 1200, 0, 20, 3, 14000, 2, 1) : // Vintage Mid Roll-off p == 10 ? pt(0, 800, 0, 30, 4, 20000, 4, 0); // Clean Up (filters only) p >= 1 && p <= 10 ? slider_automate(1023); // sliders 1-10 p == 11 ? ( // Reset all to defaults pt(0, 800, 0, 10, 1, 22000, 1, 0); slider11 = 0; slider12 = 1; slider13 = 0; slider14 = 30; slider15 = 120; slider16 = 6000; slider17 = -0.3; slider18 = 1; slider_automate(262143); ); ); in_init(); ost_init(20000); ostL.ost_ch_init(); ostR.ost_ch_init(); !ui_dflt ? ( ui_thm = 5; ui_hw = 1; tlook = 1; ); last_cs = -1; // new instances: HORIZON look, hardware panel WIN = 0x161412; GOLD = 0xE2B24A; SKY = 0x7EA6CB; HAZE = 0xCFD9DD; SEA = 0x617487; SAND = 0xA48D70; WBAND = 0xFAF7EF; update(); 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_var(0, ui_hw); file_var(0, tlook); ui_hw = 1; file_avail(0) != 0 ? file_var(0, pr_cur); // the preset in use (older projects end before it) @sample in_process(spl0, spl1); xl = in_l; xr = in_r; eq_on ? ( hp_on ? ( xl = HPL.bw_run(xl); xr = HPR.bw_run(xr); ); lp_on ? ( xl = LPL.bw_run(xl); xr = LPR.bw_run(xr); ); tl_on ? ( tmode == 0 ? ( xl = TLL.tl_run(xl); xr = TLR.tl_run(xr); ) : ( tm_ = (xl + xr) * 0.5; ts_ = (xl - xr) * 0.5; tmode == 1 ? tm_ = TLL.tl_run(tm_) : ts_ = TLR.tl_run(ts_); xl = tm_ + ts_; xr = tm_ - ts_; ); ); ); xl *= og; xr *= og; oL = ostL.ost_band(xl * in_comp); oR = ostR.ost_band(xr * in_comp); ost_out(oL, oR); spl0 = ost_l; spl1 = ost_r; @gfx 900 453 ui_begin(900, 453); // the two looks: HORIZON (white band, sky / sea window, gold) and CLASSIC (cream, black window, red) tlook ? ( CRM = 0xEFE5D0; INK = 0x3A2A1E; TOM = 0xB8862C; ) : ( CRM = 0xEDE4CF; INK = 0x2A2420; TOM = 0xD9472B; ); // flat panel: theme colours for the flat knobs and buttons, taken from the look C_ACC = TOM; C_KA = TOM; C_DIM = ui_mix(CRM, INK, 0.2); C_KNOB = ui_mix(CRM, 0xFFFFFF, 0.55); C_KEDGE = ui_mix(CRM, INK, 0.55); C_PTR = INK; C_BTN = ui_mix(CRM, INK, 0.12); C_BTNT = INK; dy_ink = INK; dy_bg = CRM; dy_kst = ui_hw ? 0 : 34; // LED button on the hardware panel, a flat key on the flat one function tbtn(x, y, on, s) local(h) ( ui_hw ? dy_btn(x, y, on, TOM, s) : ( h = ui_button(x * sc, (y + 2) * sc, 44 * sc, 22 * sc, s, on, 1); h; ); ); // widely spaced capitals, centred on cx function spaced(cx, y, s, gap) local(i, n, w, tw, th) ( n = strlen(s); w = 0; i = 0; loop(n, strcpy_substr(#sc1, s, i, 1); gfx_measurestr(#sc1, tw, th); w += tw + gap * sc; i += 1; ); gfx_x = cx * sc - (w - gap * sc) * 0.5; gfx_y = y * sc; i = 0; loop(n, strcpy_substr(#sc1, s, i, 1); gfx_measurestr(#sc1, tw, th); gfx_drawstr(#sc1); gfx_x += gap * sc; i += 1; ); ); // a vertical gradient between two colours function vgrad(x, y, w, h, c1, c2) local(r1, g1, b1, r2, g2, b2) ( r1 = ((c1 >> 16) & 255) / 255; g1 = ((c1 >> 8) & 255) / 255; b1 = (c1 & 255) / 255; r2 = ((c2 >> 16) & 255) / 255; g2 = ((c2 >> 8) & 255) / 255; b2 = (c2 & 255) / 255; gfx_gradrect(x * sc, y * sc, w * sc, h * sc, r1, g1, b1, 1, 0, 0, 0, 0, (r2 - r1) / (h * sc), (g2 - g1) / (h * sc), (b2 - b1) / (h * sc), 0); ); function txt(x, y, s, a) ( gfx_setfont(4); ui_cola(INK, a); ui_text(x * sc, y * sc, s); ); function head(x, y, s) ( gfx_setfont(6, "Arial", 13 * sc, 'b'); ui_col(TOM); ui_text(x * sc, y * sc, s); ); function resp(f) local(d) ( d = 0; eq_on ? ( hp_on ? d += HPL.bw_db(f); lp_on ? d += LPL.bw_db(f); tl_on ? d += TLL.tl_db(f); ); d + slider9; ); // ---------- panel ---------- fh = 444; ui_col(0x0B0B0C); gfx_rect(0, 0, gfx_w, gfx_h); ui_hw ? pk_chassis(8 * sc, 4 * sc, 884 * sc, fh * sc, CRM) : ( ui_col(CRM); gfx_rect(0, 0, gfx_w, gfx_h); ); tlook ? ( ui_col(WBAND); gfx_rect((ui_hw ? 10 : 0) * sc, 8 * sc, (ui_hw ? 880 : 900) * sc, 46 * sc); ui_col(GOLD); gfx_rect((ui_hw ? 10 : 0) * sc, 54 * sc, (ui_hw ? 880 : 900) * sc, 1 * sc); ui_col(TOM); gfx_circle(38 * sc, 32 * sc, 13 * sc, 1, 1); gfx_setfont(4); ui_col(WBAND); ui_text(38 * sc, 27 * sc, "GFX"); gfx_setfont(6, "Arial", 20 * sc); ui_col(INK); spaced(190, 20, "TILT EQ", 9); ) : ( ui_col(TOM); gfx_circle(38 * sc, 32 * sc, 13 * sc, 1, 1); gfx_setfont(4); ui_col(CRM); ui_text(38 * sc, 27 * sc, "GFX"); gfx_setfont(6, "Arial", 24 * sc, 'b'); ui_col(INK); gfx_x = 60 * sc; gfx_y = 18 * sc; gfx_drawstr("TILT EQ"); txt(220, 28, "TILT + FILTERS", 0.7); ); ui_button(390 * sc, 20 * sc, 74 * sc, 24 * sc, tlook ? "HORIZON" : "CLASSIC", 0, 1) ? tlook = 1 - tlook; // removed 'style' text // ui_hw_toggle(476 * sc, 23 * sc); pk_pextra = "Reset all to defaults"; // the house preset display: PRESETS, prev / next, LCD (click it for the list) k_ = pk_preset(476 * sc, 14 * sc, 340 * sc, 34 * sc, pr_cur, "Mix Bus Lift|Warm It Up|Vocal Forward|Dark & Thick|Bright Sides|Tame the Sides|Telephone Band|Thin Out (steep HPF)|Vintage Mid Roll-off|Clean Up (filters only)"); k_ > 0 ? ( load_preset(k_); pr_cur = k_ > 10 ? 0 : k_; ); ui_size_picker(828 * sc, 22 * sc); // ---------- response window ---------- gx = 22; gy = 62; gw = 856; gh = 101; // a plain dark frame round the window (square corners, no corner shading) ui_hw ? ( ui_col(INK); gfx_rect((gx - 3) * sc, (gy - 3) * sc, (gw + 6) * sc, (gh + 6) * sc); ); tlook ? ( vgrad(gx, gy, gw, gh * 0.5, SKY, HAZE); vgrad(gx, gy + gh * 0.5, gw, gh * 0.5, SEA, SAND); ) : ( ui_col(WIN); gfx_rect(gx * sc, gy * sc, gw * sc, gh * sc); ); LGRID = tlook ? 0xFFFFFF : CRM; CFILL = tlook ? GOLD : TOM; CLINE = tlook ? GOLD : CRM; 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; gxx = gx + log(gf_ / 20) / log(1000) * gw; ui_cola(LGRID, tlook ? 0.1 : 0.07); gfx_rect(gxx * sc, gy * sc, 1 * sc, gh * sc); gi += 1; ); ui_cola(LGRID, tlook ? 0.5 : 0.22); gfx_rect(gx * sc, (gy + gh * 0.5) * sc, gw * sc, 1 * sc); redraw ? ( gi = 0; loop(301, RSP[gi] = resp(20 * 1000 ^ (gi / 300)); gi += 1; ); redraw = 0; ); gi = 0; loop(301, gxx = gx + gi / 300 * gw; gyy = gy + gh * 0.5 - min(max(RSP[gi], -8), 8) / 8 * (gh * 0.5 - 6); ui_cola(CFILL, tlook ? 0.22 : 0.3); gfx_rect(gxx * sc, min(gyy, gy + gh * 0.5) * sc, max(gw / 300, 1) * sc + 1, abs(gyy - gy - gh * 0.5) * sc + 1); gi += 1; ); gi = 1; loop(300, gya = gy + gh * 0.5 - min(max(RSP[gi - 1], -8), 8) / 8 * (gh * 0.5 - 6); gyb = gy + gh * 0.5 - min(max(RSP[gi], -8), 8) / 8 * (gh * 0.5 - 6); tlook ? ui_cola(0x3A2A1E, 0.35) : ui_cola(0x000000, 0); gfx_line((gx + (gi - 1) / 300 * gw) * sc, (gya + 2) * sc, (gx + gi / 300 * gw) * sc, (gyb + 2) * sc, 1); ui_col(CLINE); gfx_line((gx + (gi - 1) / 300 * gw) * sc, gya * sc, (gx + gi / 300 * gw) * sc, gyb * sc, 1); gfx_line((gx + (gi - 1) / 300 * gw) * sc, (gya + 0.8) * sc, (gx + gi / 300 * gw) * sc, (gyb + 0.8) * sc, 1); gi += 1; ); gpx = gx + log(slider2 / 20) / log(1000) * gw; ui_col(tlook ? GOLD : TOM); gfx_triangle((gpx - 5) * sc, (gy + gh) * sc, (gpx + 5) * sc, (gy + gh) * sc, gpx * sc, (gy + gh - 7) * sc); tlook ? ( gfx_setfont(6, "Brush Script MT", 34 * sc, 'i'); ui_cola(GOLD, 0.9); gfx_x = (gx + gw - 110) * sc; gfx_y = (gy + gh - 40) * sc; gfx_drawstr("Tilt"); ); gfx_setfont(4); ui_cola(tlook ? 0xFFFFFF : CRM, tlook ? 0.6 : 0.4); gfx_x = (gx + log(100 / 20) / log(1000) * gw + 3) * sc; gfx_y = (gy + gh - 13) * sc; gfx_drawstr("100"); gfx_x = (gx + log(1000 / 20) / log(1000) * gw + 3) * sc; gfx_y = (gy + gh - 13) * sc; gfx_drawstr("1k"); gfx_x = (gx + log(10000 / 20) / log(1000) * gw + 3) * sc; gfx_y = (gy + gh - 13) * sc; gfx_drawstr("10k"); // ---------- HIGH-PASS ---------- ui_cola(INK, 0.25); gfx_rect(270 * sc, 177 * sc, 1 * sc, 150 * sc); gfx_rect(630 * sc, 177 * sc, 1 * sc, 150 * sc); head(145, 179, "HIGH-PASS"); slider4 <= 10.5 ? strcpy(#v, "OFF") : sprintf(#v, "%.0f Hz", slider4); dy_kf = 1; dy_knob(90, 247, 22, 4, 10, 1000, 10, 1, TOM, "FREQ", #v); dy_step = 6; dy_steps = "6|12|18|24|36|48"; dy_knob(200, 247, 17, 5, 0, 5, 1, 0, TOM, "", ""); // ---------- TILT ---------- head(450, 179, "TILT"); sprintf(#v, "%+.1f dB / %+.1f dB", -slider1, slider1); dy_knob(400, 259, 40, 1, -6, 6, 0, 0, TOM, "", ""); txt(400, 307, slider1 > 0.04 ? "BRIGHTER" : slider1 < -0.04 ? "DARKER" : "FLAT", 0.9); dy_act ? txt(400, 321, #v, 0.6); txt(340, 305, "DARK", 0.5); txt(460, 305, "BRIGHT", 0.5); sprintf(#v, "%.0f Hz", slider2); slider2 >= 1000 ? sprintf(#v, "%.2f kHz", slider2 / 1000); txt(548, 196, "PIVOT", 0.9); dy_knob(548, 235, 16, 2, 100, 5000, 800, 1, TOM, "", ""); dy_act ? txt(548, 260, #v, 0.7); tbtn(506, 287, slider3 == 0, "GENTLE") ? ( slider3 = 0; ui_auto(3); ); tbtn(552, 287, slider3 == 1, "STEEP") ? ( slider3 = 1; ui_auto(3); ); // ---------- LOW-PASS ---------- head(755, 179, "LOW-PASS"); slider6 >= 21990 ? strcpy(#v, "OFF") : sprintf(#v, "%.1f kHz", slider6 / 1000); dy_kf = 2; dy_knob(700, 247, 22, 6, 1000, 22000, 22000, 1, TOM, "FREQ", #v); dy_step = 6; dy_steps = "6|12|18|24|36|48"; dy_knob(810, 247, 17, 7, 0, 5, 1, 0, TOM, "", ""); // ---------- bottom row ---------- ui_cola(INK, 0.25); gfx_rect(22 * sc, 345 * sc, 856 * sc, 1 * sc); // removed tilt on text tbtn(30, 372, slider8 == 0, "STEREO") ? ( slider8 = 0; ui_auto(8); ); tbtn(80, 372, slider8 == 1, "MID") ? ( slider8 = 1; ui_auto(8); ); tbtn(130, 372, slider8 == 2, "SIDE") ? ( slider8 = 2; ui_auto(8); ); sprintf(#v, "%+.1f dB", slider9); dy_nov = 1; dy_knob(720, 386, 12, 9, -12, 12, 0, 0, TOM, "", #v); dy_nov = 0; txt(760, 382, "OUTPUT", 0.8); // txt(292, 374, "IN / OUT", 0.7); dy_nov = 1; rd_iok = ui_hw ? 20 : 34; rd_ioc1 = 0x2A2522; rd_ioc2 = GOLD; rd_ioc3 = TOM; rd_io(334, 367, 11, 12, 13, 18); dy_nov = 0; (ui_hw ? dy_btn(820, 372, slider10, dy_grn, "EQ IN") : ui_button(820 * sc, 376 * sc, 50 * sc, 22 * sc, "EQ IN", slider10, 1)) ? ( slider10 = 1 - slider10; ui_auto(10); ); pk_end(); ui_end();