desc:GFX Surgical EQ //tags: equaliser eq parametric dynamic surgical analyser mid side notch //author: JGWizrad / Garden FX (GFX) // // Requires gfx-drift, gfx-input, gfx-output, gfx-ui, gfx-panel and gfx-instrument (.jsfx-inc) in the same folder. // // Surgical EQ - six precise parametric bands on a large curve with a live spectrum (personal use; designed // from scratch). // The curve: drag a band's dot to move it (frequency + gain; Shift = fine), mouse wheel = Q. // Double-click empty space to add a band there; double-click a dot to switch the band off. // TYPE - BELL, LOW SHELF, HIGH SHELF, NOTCH, LOW CUT, HIGH CUT (24 dB / octave) or OFF. // Q - 0.1 - 30, for wide moves or very narrow resonances. // ST / M / S - each band works on the stereo pair, the mid or the side. // SOLO - hear only the region the selected band works on (a band-pass at its frequency and Q). // HPF / LPF - ahead of the bands (OFF = out of circuit). SLOPE x1 - x4 = 12 / 24 / 36 / 48 dB per octave // (Butterworth: no bump at the corner). // DYNAMICS - bells only: when the band's own level goes over THRESH the band moves by up to RANGE // (negative = cut a resonance only when it rings, positive = lift only when busy). // ATTACK / RELEASE are shared by all bands. The curve shows where each band is right now. // IN / OUT: 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-dyn.jsfx-inc import gfx-faceplate.jsfx-inc import gfx-instrument.jsfx-inc // six bands x 8: type, freq, gain, Q, target, dynamic, threshold, range slider1:1<0,6,1{Off,Bell,Low shelf,High shelf,Notch,Low cut,High cut}>-Band 1 type slider2:80<20,20000,1>-Band 1 frequency (Hz) slider3:0<-24,24,0.1>-Band 1 gain (dB) slider4:1<0.1,30,0.01>-Band 1 Q slider5:0<0,2,1{Stereo,Mid,Side}>-Band 1 target slider6:0<0,1,1{Off,On}>-Band 1 dynamic slider7:-30<-60,0,0.1>-Band 1 threshold (dB) slider8:0<-24,24,0.1>-Band 1 range (dB) slider9:1<0,6,1{Off,Bell,Low shelf,High shelf,Notch,Low cut,High cut}>-Band 2 type slider10:250<20,20000,1>-Band 2 frequency (Hz) slider11:0<-24,24,0.1>-Band 2 gain (dB) slider12:1<0.1,30,0.01>-Band 2 Q slider13:0<0,2,1{Stereo,Mid,Side}>-Band 2 target slider14:0<0,1,1{Off,On}>-Band 2 dynamic slider15:-30<-60,0,0.1>-Band 2 threshold (dB) slider16:0<-24,24,0.1>-Band 2 range (dB) slider17:1<0,6,1{Off,Bell,Low shelf,High shelf,Notch,Low cut,High cut}>-Band 3 type slider18:800<20,20000,1>-Band 3 frequency (Hz) slider19:0<-24,24,0.1>-Band 3 gain (dB) slider20:1<0.1,30,0.01>-Band 3 Q slider21:0<0,2,1{Stereo,Mid,Side}>-Band 3 target slider22:0<0,1,1{Off,On}>-Band 3 dynamic slider23:-30<-60,0,0.1>-Band 3 threshold (dB) slider24:0<-24,24,0.1>-Band 3 range (dB) slider25:1<0,6,1{Off,Bell,Low shelf,High shelf,Notch,Low cut,High cut}>-Band 4 type slider26:2500<20,20000,1>-Band 4 frequency (Hz) slider27:0<-24,24,0.1>-Band 4 gain (dB) slider28:1<0.1,30,0.01>-Band 4 Q slider29:0<0,2,1{Stereo,Mid,Side}>-Band 4 target slider30:0<0,1,1{Off,On}>-Band 4 dynamic slider31:-30<-60,0,0.1>-Band 4 threshold (dB) slider32:0<-24,24,0.1>-Band 4 range (dB) slider33:1<0,6,1{Off,Bell,Low shelf,High shelf,Notch,Low cut,High cut}>-Band 5 type slider34:6000<20,20000,1>-Band 5 frequency (Hz) slider35:0<-24,24,0.1>-Band 5 gain (dB) slider36:1<0.1,30,0.01>-Band 5 Q slider37:0<0,2,1{Stereo,Mid,Side}>-Band 5 target slider38:0<0,1,1{Off,On}>-Band 5 dynamic slider39:-30<-60,0,0.1>-Band 5 threshold (dB) slider40:0<-24,24,0.1>-Band 5 range (dB) slider41:1<0,6,1{Off,Bell,Low shelf,High shelf,Notch,Low cut,High cut}>-Band 6 type slider42:12000<20,20000,1>-Band 6 frequency (Hz) slider43:0<-24,24,0.1>-Band 6 gain (dB) slider44:1<0.1,30,0.01>-Band 6 Q slider45:0<0,2,1{Stereo,Mid,Side}>-Band 6 target slider46:0<0,1,1{Off,On}>-Band 6 dynamic slider47:-30<-60,0,0.1>-Band 6 threshold (dB) slider48:0<-24,24,0.1>-Band 6 range (dB) slider49:5<0.5,100,0.1>-Dynamics attack (ms) slider50:120<10,1000,1>-Dynamics release (ms) slider51:0<-24,12,0.1>-Output (dB) slider52:0<-24,12,0.1>-Input trim (dB) slider53:1<0,1,1{Off,On}>-Input guard slider54:0<0,100,1>-Band sat (%) slider55:30<0,100,1>-Bias (%) slider56:120<40,400,1>-Sat low band (Hz) slider57:6000<2000,16000,10>-Sat high band (Hz) slider58:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider59:1<0,2,1{Off,Clip,Limit}>-Output stage slider60:20<20,1000,1>-HPF (Hz, 20 = off) slider61:20000<1000,20000,10>-LPF (Hz, 20000 = off) slider62:1<0,3,1{x1 12 dB/oct,x2 24 dB/oct,x3 36 dB/oct,x4 48 dB/oct}>-HPF slope slider63:1<0,3,1{x1 12 dB/oct,x2 24 dB/oct,x3 36 dB/oct,x4 48 dB/oct}>-LPF slope in_pin:left input in_pin:right input out_pin:left output out_pin:right output @init #ui_man = "gfx-surgical-eq"; // the ? pop-up links to this manual // ---------- biquads (RBJ) ---------- // type: 0 low-pass, 1 high-pass, 2 peak, 3 high shelf, 4 low shelf, 5 notch, 6 band-pass (0 dB peak) function bq_set(type, f, q, gdb) instance(b0, b1, b2, a1, a2) local(w0, cw, sw, al, amp, sa, n) ( f = min(max(f, 10), srate * 0.45); w0 = 2 * $pi * f / srate; cw = cos(w0); sw = sin(w0); al = sw / (2 * q); amp = 10 ^ (gdb / 40); type == 0 ? ( n = 1 + al; b0 = (1 - cw) * 0.5 / n; b1 = (1 - cw) / n; b2 = b0; a1 = -2 * cw / n; a2 = (1 - al) / n; ) : type == 1 ? ( n = 1 + al; b0 = (1 + cw) * 0.5 / n; b1 = -(1 + cw) / n; b2 = b0; a1 = -2 * cw / n; a2 = (1 - al) / n; ) : type == 2 ? ( n = 1 + al / amp; b0 = (1 + al * amp) / n; b1 = -2 * cw / n; b2 = (1 - al * amp) / n; a1 = b1; a2 = (1 - al / amp) / n; ) : type == 3 ? ( sa = 2 * sqrt(amp) * al; n = (amp + 1) - (amp - 1) * cw + sa; b0 = amp * ((amp + 1) + (amp - 1) * cw + sa) / n; b1 = -2 * amp * ((amp - 1) + (amp + 1) * cw) / n; b2 = amp * ((amp + 1) + (amp - 1) * cw - sa) / n; a1 = 2 * ((amp - 1) - (amp + 1) * cw) / n; a2 = ((amp + 1) - (amp - 1) * cw - sa) / n; ) : type == 4 ? ( sa = 2 * sqrt(amp) * al; n = (amp + 1) + (amp - 1) * cw + sa; b0 = amp * ((amp + 1) - (amp - 1) * cw + sa) / n; b1 = 2 * amp * ((amp - 1) - (amp + 1) * cw) / n; b2 = amp * ((amp + 1) - (amp - 1) * cw - sa) / n; a1 = -2 * ((amp - 1) + (amp + 1) * cw) / n; a2 = ((amp + 1) + (amp - 1) * cw - sa) / n; ) : type == 5 ? ( n = 1 + al; b0 = 1 / n; b1 = -2 * cw / n; b2 = b0; a1 = b1; a2 = (1 - al) / n; ) : ( n = 1 + al; b0 = al / n; b1 = 0; b2 = -al / n; a1 = -2 * cw / n; a2 = (1 - al) / n; ); ); function bq_thru() instance(b0, b1, b2, a1, a2) ( b0 = 1; b1 = 0; b2 = 0; a1 = 0; a2 = 0; ); function bq_run(x) instance(b0, b1, b2, a1, a2, x1, x2, y1, y2) local(y) ( y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; x2 = x1; x1 = x; y2 = y1; y1 = y; y; ); // allpass for the dynamic bell (after Zolzer): y = x + H0/2 (x - A(x)); (x - A(x)) / 2 = band-pass function ap_set(f, q, gdb) instance(c, d) local(t, v) ( f = min(max(f, 10), srate * 0.45); t = tan($pi * min(f / q, srate * 0.45) / srate); v = 10 ^ (gdb / 20); c = gdb >= 0 ? (t - 1) / (t + 1) : (t - v) / (t + v); d = -cos(2 * $pi * f / srate); ); function ap_run(x) instance(c, d, x1, x2, y1, y2) local(y) ( y = -c * x + d * (1 - c) * x1 + x2 - d * (1 - c) * y1 + c * y2; x2 = x1; x1 = x; y2 = y1; y1 = y; y; ); // ---------- one band ---------- // i0: its first slider. Works on the globals bl / br (L / R in, L / R out). function bd_setup(i0) instance(i, ty, f, g, q, tg, dyn, thr, rng, gs, sta, fa, fb, ga, gb, pa, pb, so_a, so_b) ( i = i0; ty = slider(i0); f = slider(i0 + 1); q = slider(i0 + 3); tg = slider(i0 + 4); dyn = slider(i0 + 5) && ty == 1; thr = slider(i0 + 6); rng = slider(i0 + 7); sta += (slider(i0 + 2) - sta) * bs; ty == 1 ? ( pa.ap_set(f, q, gs); pb.ap_set(f, q, gs); ) : ty == 2 ? ( fa.bq_set(4, f, max(q, 0.3), sta); fb.bq_set(4, f, max(q, 0.3), sta); ) : ty == 3 ? ( fa.bq_set(3, f, max(q, 0.3), sta); fb.bq_set(3, f, max(q, 0.3), sta); ) : ty == 4 ? ( fa.bq_set(5, f, q, 0); fb.bq_set(5, f, q, 0); ) : ty == 5 ? ( fa.bq_set(1, f, 0.5412, 0); fb.bq_set(1, f, 0.5412, 0); ga.bq_set(1, f, 1.3066, 0); gb.bq_set(1, f, 1.3066, 0); ) : ty == 6 ? ( fa.bq_set(0, f, 0.5412, 0); fb.bq_set(0, f, 0.5412, 0); ga.bq_set(0, f, 1.3066, 0); gb.bq_set(0, f, 1.3066, 0); ); so_a.bq_set(6, f, ty >= 5 ? 0.7 : q, 0); so_b.bq_set(6, f, ty >= 5 ? 0.7 : q, 0); ty != 1 ? gs = sta; ); function bd_run() instance(ty, tg, dyn, thr, rng, gs, sta, env, grs, fa, fb, ga, gb, pa, pb) local(m, s, ua, ub, ya, yb, e, lv, o, h0) ( ty > 0 ? ( tg == 0 ? ( ua = bl; ub = br; ) : ( m = (bl + br) * 0.5; s = (bl - br) * 0.5; ua = m; ub = s; ); ty == 1 ? ( h0 = 10 ^ (gs / 20) - 1; e = 0; tg != 2 ? ( ya = pa.ap_run(ua); e = abs((ua - ya) * 0.5); ua += h0 * 0.5 * (ua - ya); ); tg != 1 ? ( yb = pb.ap_run(ub); e = max(e, abs((ub - yb) * 0.5)); ub += h0 * 0.5 * (ub - yb); ); dyn ? ( e *= e; env += (e - env) * (e > env ? dy_att : dy_rel); lv = 10 * log10(max(env * 2, 0.000000000001)); o = max(lv - thr, 0); gs += (sta + rng * o / (o + 6) - gs) * 0.02; grs = max(grs, abs(gs - sta)); ) : gs += (sta - gs) * 0.02; ) : ( tg != 2 ? ( ua = fa.bq_run(ua); ty >= 5 ? ua = ga.bq_run(ua); ); tg != 1 ? ( ub = fb.bq_run(ub); ty >= 5 ? ub = gb.bq_run(ub); ); ); tg == 0 ? ( bl = ua; br = ub; ) : ( bl = ua + ub; br = ua - ub; ); ); ); // solo: a band-pass at the band, on its target function bd_solo(l, r) instance(tg, so_a, so_b) local(m, s) ( tg == 0 ? ( bl = so_a.bq_run(l); br = so_b.bq_run(r); ) : tg == 1 ? ( m = so_a.bq_run((l + r) * 0.5); bl = m; br = m; ) : ( s = so_b.bq_run((l - r) * 0.5); bl = s; br = -s; ); ); // Butterworth section Q: n sections (order 2n), section k function bw_q(n, k) ( 1 / (2 * sin((2 * k + 1) * $pi / (4 * n))) ); // HPF (t = 1) / LPF (t = 0) cascade of up to four sections; unused sections pass through function fc_set(t, f, n) instance(s1L, s1R, s2L, s2R, s3L, s3R, s4L, s4R) ( n < 1 ? ( s1L.bq_thru(); s1R.bq_thru(); ) : ( s1L.bq_set(t, f, bw_q(n, 0), 0); s1R.bq_set(t, f, bw_q(n, 0), 0); ); n < 2 ? ( s2L.bq_thru(); s2R.bq_thru(); ) : ( s2L.bq_set(t, f, bw_q(n, 1), 0); s2R.bq_set(t, f, bw_q(n, 1), 0); ); n < 3 ? ( s3L.bq_thru(); s3R.bq_thru(); ) : ( s3L.bq_set(t, f, bw_q(n, 2), 0); s3R.bq_set(t, f, bw_q(n, 2), 0); ); n < 4 ? ( s4L.bq_thru(); s4R.bq_thru(); ) : ( s4L.bq_set(t, f, bw_q(n, 3), 0); s4R.bq_set(t, f, bw_q(n, 3), 0); ); ); function fc_l(x) instance(s1L, s2L, s3L, s4L) ( s4L.bq_run(s3L.bq_run(s2L.bq_run(s1L.bq_run(x)))) ); function fc_r(x) instance(s1R, s2R, s3R, s4R) ( s4R.bq_run(s3R.bq_run(s2R.bq_run(s1R.bq_run(x)))) ); function update() ( dy_att = 1 - exp(-1 / (slider49 * 0.001 * srate)); dy_rel = 1 - exp(-1 / (slider50 * 0.001 * srate)); out_t = 10 ^ (slider51 / 20); in_setup(slider52, slider53); ins_output_setup(54); ); // spectrum: a 4096-sample ring of the output, analysed in @gfx SP_N = 4096; SP_RING = 40000; SP_FFT = 65536; SP_WIN = 80000; SP_COL = 90000; SP_NC = 360; i = 0; loop(SP_N, SP_WIN[i] = 0.5 - 0.5 * cos(2 * $pi * i / (SP_N - 1)); i += 1; ); memset(SP_COL, -120, SP_NC); sp_w = 0; ost_init(20000); ostL.ost_ch_init(); ostR.ost_ch_init(); in_init(); sm = 1 - exp(-1 / (0.03 * srate)); pkL = 0; pkR = 0; es_sel = 0; es_solo = 0; es_an = 1; !ui_dflt ? ui_thm = 5; !ui_dflt ? ui_hw = 1; // defaults for new instances: Graphite, hardware bs = 1; b1.bd_setup(1); b2.bd_setup(9); b3.bd_setup(17); b4.bd_setup(25); b5.bd_setup(33); b6.bd_setup(41); update(); 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(); bs = 1 - exp(-samplesblock / (0.03 * srate)); b1.bd_setup(1); b2.bd_setup(9); b3.bd_setup(17); b4.bd_setup(25); b5.bd_setup(33); b6.bd_setup(41); hpc.fc_set(1, slider60, slider60 <= 20.5 ? 0 : slider62 + 1); lpc.fc_set(0, slider61, slider61 >= 19990 ? 0 : slider63 + 1); @serialize file_var(0, ui_thm); file_var(0, ui_sz); file_var(0, ui_hw); file_var(0, es_an); @sample in_process(spl0, spl1); es_solo > 0 ? ( es_solo == 1 ? b1.bd_solo(in_l, in_r) : es_solo == 2 ? b2.bd_solo(in_l, in_r) : es_solo == 3 ? b3.bd_solo(in_l, in_r) : es_solo == 4 ? b4.bd_solo(in_l, in_r) : es_solo == 5 ? b5.bd_solo(in_l, in_r) : b6.bd_solo(in_l, in_r); ) : ( bl = lpc.fc_l(hpc.fc_l(in_l)); br = lpc.fc_r(hpc.fc_r(in_r)); b1.bd_run(); b2.bd_run(); b3.bd_run(); b4.bd_run(); b5.bd_run(); b6.bd_run(); ); out_s += (out_t - out_s) * sm; oL = ostL.ost_band(bl * out_s); oR = ostR.ost_band(br * out_s); ost_out(oL, oR); spl0 = ost_l; spl1 = ost_r; SP_RING[sp_w] = (spl0 + spl1) * 0.5; sp_w = (sp_w + 1) & (SP_N - 1); pkL = max(pkL, abs(spl0)); pkR = max(pkR, abs(spl1)); @gfx 780 546 ui_begin(780, 546); ui_header("SURGICAL EQ"); // ---------- helpers ---------- // magnitude (dB) of one RBJ section at frequency fr function es_mag(fr, typ, f0, ge, q) local(w, w2, Ag, n, d, s) ( w = tan($pi * min(fr, srate * 0.49) / srate) / tan($pi * min(f0, srate * 0.45) / srate); w2 = w * w; Ag = 10 ^ (ge / 40); typ == 0 ? ( n = sqr(1 - w2) + sqr(Ag * w / q); d = sqr(1 - w2) + sqr(w / (Ag * q)); ) : typ == 1 ? ( s = Ag * w2 / (q * q); n = Ag * Ag * (sqr(Ag - w2) + s); d = sqr(1 - Ag * w2) + s; ) : typ == 2 ? ( s = Ag * w2 / (q * q); n = Ag * Ag * (sqr(1 - Ag * w2) + s); d = sqr(Ag - w2) + s; ) : typ == 3 ? ( n = w2 * w2; d = sqr(1 - w2) + w2 / (q * q); ) : typ == 4 ? ( n = 1; d = sqr(1 - w2) + w2 / (q * q); ) : ( n = sqr(1 - w2); d = sqr(1 - w2) + w2 / (q * q); ); // notch 10 * log10(max(n, 0.000000000001) / max(d, 0.000000000001)); ); // one band's response (dB) at fr; gd = gain to draw (static or the live dynamic one) function es_band_db(fr, i0, gd) local(ty, f, q) ( ty = slider(i0); f = slider(i0 + 1); q = slider(i0 + 3); ty == 1 ? es_mag(fr, 0, f, gd, q) : ty == 2 ? es_mag(fr, 1, f, gd, max(q, 0.3)) : ty == 3 ? es_mag(fr, 2, f, gd, max(q, 0.3)) : ty == 4 ? max(es_mag(fr, 5, f, 0, q), -40) : ty == 5 ? es_mag(fr, 3, f, 0, 0.5412) + es_mag(fr, 3, f, 0, 1.3066) : ty == 6 ? es_mag(fr, 4, f, 0, 0.5412) + es_mag(fr, 4, f, 0, 1.3066) : 0; ); function es_bcol(b) ( b == 0 ? 0xFF6B5B : b == 1 ? 0xFFB347 : b == 2 ? 0xE8DC5A : b == 3 ? 0x5CD68D : b == 4 ? 0x4FA8FF : 0xB685FF; ); function es_live(b) ( b == 0 ? b1.gs : b == 1 ? b2.gs : b == 2 ? b3.gs : b == 3 ? b4.gs : b == 4 ? b5.gs : b6.gs; ); // ---------- header controls ---------- ui_theme_picker(200 * sc, 15 * sc); ui_size_picker(304 * sc, 13 * sc); ui_hw_toggle(364 * sc, 13 * sc); ui_button(560 * sc, 10 * sc, 96 * sc, 24 * sc, "ANALYSER", es_an, 1) ? es_an = !es_an; ui_noled = 1; ui_button(660 * sc, 10 * sc, 104 * sc, 24 * sc, es_solo ? "SOLO OFF" : "FLAT ALL", 0, 1) ? ( es_solo ? es_solo = 0 : ( b_ = 0; loop(6, slider(1 + b_ * 8 + 2) = 0; slider_automate(2 ^ (b_ * 8 + 2)); b_ += 1; ); ); ); // ---------- the display: spectrum, curve, band dots ---------- dx0 = 16 * sc; dy0 = 52 * sc; dw = 748 * sc; dh = 250 * sc; ui_screen(dx0, dy0, dw, dh); gx0 = dx0 + 30 * sc; gw = dw - 40 * sc; gy0 = dy0 + 10 * sc; gh = dh - 28 * sc; gmid = gy0 + gh * 0.5; DBR = 24; // curve range: +/-24 dB // grid gfx_setfont(4); d_ = 10; loop(4, j_ = 1; loop(9, fq_ = d_ * j_; fq_ >= 20 && fq_ <= 20000 ? ( qx = gx0 + log(fq_ / 20) / log(1000) * gw; ui_cola(C_SG, j_ == 1 ? 0.9 : 0.35); gfx_line(qx, gy0, qx, gy0 + gh); (j_ == 1 || j_ == 2 || j_ == 5) ? ( ui_col(C_ST); gfx_x = qx + 2 * sc; gfx_y = gy0 + gh + 3 * sc; sprintf(#gl, fq_ >= 1000 ? "%dk" : "%d", fq_ >= 1000 ? fq_ / 1000 : fq_); gfx_drawstr(#gl); ); ); j_ += 1; ); d_ *= 10; ); g_ = -24; loop(9, qy = gmid - g_ / DBR * gh * 0.5; ui_cola(C_SG, g_ == 0 ? 0.9 : 0.35); gfx_line(gx0, qy, gx0 + gw, qy); ui_col(C_ST); sprintf(#gl, "%+d", g_); gfx_x = dx0 + 4 * sc; gfx_y = qy - 5 * sc; gfx_drawstr(g_ == 0 ? "0" : #gl); g_ += 6; ); // spectrum (output, after the EQ): FFT of the last 4096 samples, peak per column, slow fall es_an ? ( i = 0; j = sp_w; loop(SP_N, SP_FFT[2 * i] = SP_RING[j] * SP_WIN[i]; SP_FFT[2 * i + 1] = 0; j = (j + 1) & (SP_N - 1); i += 1; ); fft(SP_FFT, SP_N); fft_permute(SP_FFT, SP_N); c = 0; loop(SP_NC, fa_ = 20 * 1000 ^ (c / SP_NC); fb_ = 20 * 1000 ^ ((c + 1) / SP_NC); ka = max(floor(fa_ / srate * SP_N), 1); kb_ = max(floor(fb_ / srate * SP_N), ka); mx = 0; k = ka; loop(kb_ - ka + 1, mx = max(mx, sqr(SP_FFT[2 * k]) + sqr(SP_FFT[2 * k + 1])); k += 1; ); v = 10 * log10(max(mx, 0.0000000000001)) + 20 * log10(4 / SP_N); SP_COL[c] = max(v, SP_COL[c] - 1.2); c += 1; ); // draw: -96 .. 0 dBFS over the full height, filled ui_cola(C_VA, 0.16); c = 0; loop(SP_NC, qx = gx0 + c / SP_NC * gw; qy = gy0 + gh - min(max((SP_COL[c] + 96) / 96, 0), 1) * gh; gfx_rect(qx, qy, max(gw / SP_NC, 1) + 1, gy0 + gh - qy); c += 1; ); ); // band curves: each active band faintly in its colour, then the total b_ = 0; loop(6, i0 = 1 + b_ * 8; slider(i0) > 0 ? ( ui_cola(es_bcol(b_), b_ == es_sel ? 0.5 : 0.22); i = 0; loop(181, fr_ = 20 * 1000 ^ (i / 180); db_ = es_band_db(fr_, i0, slider(i0 + 2)); qx = gx0 + i / 180 * gw; qy = gmid - min(max(db_, -DBR), DBR) / DBR * gh * 0.5; i > 0 ? gfx_line(px, py, qx, qy, 1); px = qx; py = qy; i += 1; ); ); b_ += 1; ); // total: static (bright) and, where a band is dynamic, the live curve (accent) pass = 0; loop(2, pass == 0 ? ui_col(C_VA) : ui_cola(C_VB, 0.9); i = 0; loop(241, fr_ = 20 * 1000 ^ (i / 240); db_ = 0; b_ = 0; loop(6, i0 = 1 + b_ * 8; db_ += es_band_db(fr_, i0, pass ? es_live(b_) : slider(i0 + 2)); b_ += 1; ); slider60 > 20.5 ? ( k_ = 0; loop(slider62 + 1, db_ += es_mag(fr_, 3, slider60, 0, bw_q(slider62 + 1, k_)); k_ += 1; ); ); slider61 < 19990 ? ( k_ = 0; loop(slider63 + 1, db_ += es_mag(fr_, 4, slider61, 0, bw_q(slider63 + 1, k_)); k_ += 1; ); ); qx = gx0 + i / 240 * gw; qy = gmid - min(max(db_, -DBR), DBR) / DBR * gh * 0.5; i > 0 ? ( gfx_line(px, py, qx, qy, 1); pass == 0 ? gfx_line(px, py + 1, qx, qy + 1, 1); ); px = qx; py = qy; i += 1; ); pass += 1; any_dyn = slider(6) || slider(14) || slider(22) || slider(30) || slider(38) || slider(46); !any_dyn ? pass = 2; ); // dots: drag = frequency + gain (Shift = fine), wheel = Q, double-click = off; double-click empty = add dr = 7 * sc; hit_any = 0; b_ = 0; loop(6, i0 = 1 + b_ * 8; slider(i0) > 0 ? ( ty_ = slider(i0); bx = gx0 + log(slider(i0 + 1) / 20) / log(1000) * gw; by = ty_ <= 3 ? gmid - min(max(slider(i0 + 2), -DBR), DBR) / DBR * gh * 0.5 : gmid; hov_ = (mouse_x - bx) ^ 2 + (mouse_y - by) ^ 2 <= (dr + 2 * sc) ^ 2; hov_ ? hit_any = 1; pressed && drag_id == 0 && hov_ ? ( time_precise() - es_clk < 0.3 && es_clkb == b_ ? ( slider(i0) = 0; slider_automate(2 ^ (i0 - 1)); drag_id = -1; ) : ( drag_id = 300 + b_; es_sel = b_; es_mx = mouse_x; es_my = mouse_y; es_f0 = slider(i0 + 1); es_g0 = slider(i0 + 2); ); es_clk = time_precise(); es_clkb = b_; ); drag_id == 300 + b_ && mdown ? ( k_ = (mouse_cap & 8) ? 0.2 : 1; slider(i0 + 1) = floor(min(max(es_f0 * 1000 ^ ((mouse_x - es_mx) * k_ / gw), 20), 20000) + 0.5); ty_ <= 3 ? slider(i0 + 2) = floor(min(max(es_g0 + (es_my - mouse_y) * k_ / (gh * 0.5) * DBR, -24), 24) * 10 + 0.5) / 10; slider_automate(2 ^ i0); slider_automate(2 ^ (i0 + 1)); ); hov_ && mouse_wheel != 0 && drag_id == 0 ? ( slider(i0 + 3) = min(max(slider(i0 + 3) * 2 ^ (mouse_wheel / 120 * 0.2), 0.1), 30); slider_automate(2 ^ (i0 + 2)); mouse_wheel = 0; ); (hov_ || drag_id == 300 + b_ || es_sel == b_) ? ( ui_cola(es_bcol(b_), 0.35); gfx_circle(bx, by, dr + 3 * sc, 1, 1); ); ui_col(es_bcol(b_)); ty_ == 1 || ty_ == 4 ? gfx_circle(bx, by, dr - 1 * sc, 1, 1) : ui_rrect(bx - dr + 1 * sc, by - dr + 1 * sc, 2 * dr - 2 * sc, 2 * dr - 2 * sc, 2 * sc); gfx_setfont(4); ui_col(0x101010); sprintf(#dn, "%d", b_ + 1); ui_text(bx + 0.5, by - 5 * sc, #dn); slider(i0 + 4) ? ( ui_col(es_bcol(b_)); ui_text(bx, by + dr + 2 * sc, slider(i0 + 4) == 1 ? "M" : "S"); ); ); b_ += 1; ); // double-click on empty space: switch on the first free band there mouse_x >= gx0 && mouse_x <= gx0 + gw && mouse_y >= gy0 && mouse_y <= gy0 + gh && !hit_any && pressed && drag_id == 0 ? ( time_precise() - es_eclk < 0.3 ? ( b_ = 0; fnd = -1; loop(6, fnd < 0 && slider(1 + b_ * 8) == 0 ? fnd = b_; b_ += 1; ); fnd >= 0 ? ( i0 = 1 + fnd * 8; slider(i0) = 1; slider(i0 + 1) = floor(min(max(20 * 1000 ^ ((mouse_x - gx0) / gw), 20), 20000) + 0.5); slider(i0 + 2) = floor(min(max((gmid - mouse_y) / (gh * 0.5) * DBR, -24), 24) * 10 + 0.5) / 10; slider(i0 + 3) = 1; slider_automate(2 ^ (i0 - 1)); slider_automate(2 ^ i0); slider_automate(2 ^ (i0 + 1)); es_sel = fnd; ); es_eclk = 0; ) : es_eclk = time_precise(); ); // ---------- selected band ---------- i0 = 1 + es_sel * 8; ui_group(16 * sc, 310 * sc, 470 * sc, 120 * sc, 0, "BAND"); // band picker: six coloured keys b_ = 0; loop(6, ui_noled = 1; ui_button((44 + b_ * 34) * sc, 317 * sc, 30 * sc, 22 * sc, "", es_sel == b_, 1) ? es_sel = b_; ui_col(es_bcol(b_)); rd_rr((49 + b_ * 34) * sc, 321 * sc, 20 * sc, 14 * sc, 2 * sc); gfx_setfont(4); ui_col(0x101010); sprintf(#dn, "%d", b_ + 1); ui_keytext((59 + b_ * 34) * sc, 328 * sc, #dn); b_ += 1; ); i0 = 1 + es_sel * 8; // type: a key that opens a menu ui_noled = 1; ui_button(256 * sc, 318 * sc, 104 * sc, 20 * sc, slider(i0) == 0 ? "OFF" : slider(i0) == 1 ? "BELL" : slider(i0) == 2 ? "LOW SHELF" : slider(i0) == 3 ? "HIGH SHELF" : slider(i0) == 4 ? "NOTCH" : slider(i0) == 5 ? "LOW CUT" : "HIGH CUT", slider(i0) > 0, 1) ? ( gfx_x = 256 * sc; gfx_y = 338 * sc; m_ = gfx_showmenu("Off|Bell|Low shelf|High shelf|Notch|Low cut (24 dB/oct)|High cut (24 dB/oct)"); m_ > 0 ? ( slider(i0) = m_ - 1; slider_automate(2 ^ (i0 - 1)); ); ); // solo ui_keycap(372 * sc, 316 * sc, 60 * sc, 22 * sc, "SOLO", es_solo == es_sel + 1, 1) ? es_solo = es_solo == es_sel + 1 ? 0 : es_sel + 1; // knobs ui_section(0); ui_knob(76 * sc, 386 * sc, i0 + 1, 20, 20000, 1000, 1, "FREQ", "%.0f Hz"); slider(i0) <= 3 ? ( knob_step = 0.1; ui_knob(156 * sc, 386 * sc, i0 + 2, -24, 24, 0, 0, "GAIN", "%+.1f dB"); ) : ( gfx_setfont(4); ui_cola(C_INK, 0.4); ui_text(156 * sc, 382 * sc, "-"); ); slider(i0) <= 4 ? ui_knob(236 * sc, 386 * sc, i0 + 3, 0.1, 30, 1, 1, "Q", "%.2f") : ( gfx_setfont(4); ui_cola(C_INK, 0.4); ui_text(236 * sc, 382 * sc, "24 dB/oct"); ); // target b_ = 0; loop(3, ui_button((300 + b_ * 30) * sc, 376 * sc, 28 * sc, 20 * sc, b_ == 0 ? "ST" : b_ == 1 ? "M" : "S", slider(i0 + 4) == b_, 1) ? ( slider(i0 + 4) = b_; slider_automate(2 ^ (i0 + 3)); ); b_ += 1; ); gfx_setfont(4); ui_col(C_INK); ui_text(344 * sc, 402 * sc, "TARGET"); ui_col(es_bcol(es_sel)); sprintf(#bn, "BAND %d", es_sel + 1); ui_text(440 * sc, 376 * sc, #bn); es_solo ? ( ui_col(es_bcol(es_solo - 1)); sprintf(#sl, "SOLO: BAND %d", es_solo); ui_text(440 * sc, 392 * sc, #sl); ); // ---------- dynamics (the selected band) ---------- ui_group(494 * sc, 310 * sc, 270 * sc, 120 * sc, 1, "DYNAMICS"); slider(i0) == 1 ? ( ui_keycap(526 * sc, 316 * sc, 56 * sc, 22 * sc, "DYN", slider(i0 + 5), 1) ? ( slider(i0 + 5) = 1 - slider(i0 + 5); slider_automate(2 ^ (i0 + 4)); ); ui_knob(550 * sc, 390 * sc, i0 + 6, -60, 0, -30, 0, "THRESH", "%.1f dB"); knob_step = 0.1; ui_knob(606 * sc, 390 * sc, i0 + 7, -24, 24, 0, 0, "RANGE", "%+.1f dB"); ) : ( gfx_setfont(4); ui_cola(C_INK, 0.5); ui_text(578 * sc, 384 * sc, "BELLS ONLY"); ); ui_knob(662 * sc, 390 * sc, 49, 0.5, 100, 5, 1, "ATTACK", "%.1f ms"); ui_knob(718 * sc, 390 * sc, 50, 10, 1000, 120, 1, "RELEASE", "%.0f ms"); // ---------- FILTERS, IN / OUT, MASTER ---------- ui_group(16 * sc, 438 * sc, 258 * sc, 96 * sc, 0, "FILTERS"); ui_kfilter = 1; strcpy(#kf_lo, "20"); strcpy(#kf_hi, "1k"); ui_knob(72 * sc, 480 * sc, 60, 20, 1000, 20, 1, "HPF", slider60 <= 20.5 ? "OFF" : "%.0f Hz"); ui_kfilter = 2; strcpy(#kf_lo, "1k"); strcpy(#kf_hi, "20k"); ui_knob(184 * sc, 480 * sc, 61, 1000, 20000, 20000, 1, "LPF", slider61 >= 19990 ? "OFF" : "%.0f Hz"); // slope multipliers: click to step x1 -> x4 (12 - 48 dB / octave) gfx_setfont(4); ui_cola(C_INK, 0.6); ui_text(124 * sc, 452 * sc, "SLOPE"); ui_text(236 * sc, 452 * sc, "SLOPE"); sprintf(#hs, "x%d", slider62 + 1); sprintf(#ls, "x%d", slider63 + 1); ui_keycap(108 * sc, 466 * sc, 32 * sc, 22 * sc, #hs, slider60 > 20.5, 1) ? ( slider62 = (slider62 + 1) % 4; slider_automate(slider62); ); ui_keycap(220 * sc, 466 * sc, 32 * sc, 22 * sc, #ls, slider61 < 19990, 1) ? ( slider63 = (slider63 + 1) % 4; slider_automate(slider63); ); gfx_setfont(4); ui_cola(C_INK, 0.6); sprintf(#hs, "%d dB", (slider62 + 1) * 12); ui_text(124 * sc, 498 * sc, #hs); sprintf(#ls, "%d dB", (slider63 + 1) * 12); ui_text(236 * sc, 498 * sc, #ls); ui_group(282 * sc, 438 * sc, 270 * sc, 96 * sc, 3, "IN / OUT"); ui_io(330, 452, 52, 53, 54, 59); ui_group(560 * sc, 438 * sc, 204 * sc, 96 * sc, 2, "MASTER"); ui_knob(630 * sc, 484 * sc, 51, -24, 12, 0, 0, "OUTPUT", "%.1f dB"); mL.ui_ledm(712 * sc, 450 * sc, 14 * sc, 70 * sc, pkL); mR.ui_ledm(732 * sc, 450 * sc, 14 * sc, 70 * sc, pkR); pkL *= 0.85; pkR *= 0.85; gfx_setfont(4); ui_col(C_INK); ui_text(729 * sc, 522 * sc, "OUT"); ui_end();