desc:GFX Mid/Side //tags: mid side stereo width imaging mastering //author: JGWizrad / Garden FX (GFX) // // Requires gfx-drift, gfx-input, gfx-output and gfx-ui (.jsfx-inc) in the same folder. // // Mid/side processor: // MID / SIDE gain, WIDTH (0-200 %, loudness kept steady), BASS MONO (sides removed below a frequency, 24 dB/oct), // AIR (high shelf on the sides), FOCUS (presence on the mid), L/R or M/S input and output formats. // Listen: STEREO, MID solo, SIDE solo (monitoring only). // Drift: width breathes, air wanders, balance leans. The DRIFT dial scales the three drift amounts // (50% = the drift settings, 100% = twice, 0 = none) and speeds the wander up with them. // 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-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{Stereo,Mid,Side}>-Listen slider2:0<-12,12,0.1>-Mid gain (dB) slider3:0<-12,12,0.1>-Side gain (dB) slider4:100<0,200,1>-Width (%) slider5:0<0,300,1>-Bass mono (Hz, 0 = off) slider6:0<0,9,0.1>-Air (dB) slider7:0<0,6,0.1>-Mid focus (dB) slider8:0<0,1,1{L/R,M/S}>-Input format slider9:0<0,1,1{L/R,M/S}>-Output format slider10:10<0,50,1>-Width drift (%) slider11:10<0,50,1>-Air drift (%) slider12:5<0,50,1>-Balance drift (%) slider13:0.25<0.02,2,0.01>-Drift speed (Hz) slider14:0<-24,12,0.1>-Output (dB) slider15:0<-100,100,1>-Balance (%) slider16:0<0,100,1>-Band sat (%) slider17:30<0,100,1>-Bias (%) slider18:120<40,400,1>-Sat low band (Hz) slider19:6000<2000,16000,10>-Sat high band (Hz) slider20:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider21:1<0,2,1{Off,Clip,Limit}>-Output stage slider22:0<-24,12,0.1>-Input trim (dB) slider23:1<0,1,1{Off,On}>-Input guard slider24: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-midside"; // the ? pop-up links to this manual // biquad (RBJ) with coefficients in the instance function bq_hp(f) instance(b0, b1, b2, a1, a2) local(w0, cw, al, n) ( w0 = 2 * $pi * min(f, srate * 0.45) / srate; cw = cos(w0); al = sin(w0) / (2 * 0.70710678); n = 1 + al; b0 = (1 + cw) * 0.5 / n; b1 = -(1 + cw) / n; b2 = b0; a1 = -2 * cw / n; a2 = (1 - al) / n; ); function bq_shelf(f, gdb) instance(b0, b1, b2, a1, a2) local(amp, w0, cw, sw, al, sq, n) ( amp = 10 ^ (gdb / 40); w0 = 2 * $pi * min(f, srate * 0.45) / srate; cw = cos(w0); sw = sin(w0); al = sw / 2 * sqrt(2); sq = 2 * sqrt(amp) * al; n = (amp + 1) - (amp - 1) * cw + sq; b0 = amp * ((amp + 1) + (amp - 1) * cw + sq) / n; b1 = -2 * amp * ((amp - 1) + (amp + 1) * cw) / n; b2 = amp * ((amp + 1) + (amp - 1) * cw - sq) / n; a1 = 2 * ((amp - 1) - (amp + 1) * cw) / n; a2 = ((amp + 1) - (amp - 1) * cw - sq) / n; ); function bq_peak(f, gdb, q) instance(b0, b1, b2, a1, a2) local(amp, w0, cw, al, n) ( amp = 10 ^ (gdb / 40); w0 = 2 * $pi * min(f, srate * 0.45) / srate; cw = cos(w0); al = sin(w0) / (2 * q); n = 1 + al / amp; b0 = (1 + al * amp) / n; b1 = -2 * cw / n; b2 = (1 - al * amp) / n; a1 = -2 * cw / n; a2 = (1 - al / amp) / n; ); 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; ); function update() ( listen = slider1; mid_t = 10 ^ (slider2 / 20); side_t = 10 ^ (slider3 / 20); wid_t = slider4 / 100; bmono = slider5; bmono >= 20 ? ( hpS.bq_hp(bmono); hpS2.bq_hp(bmono); ); air_db = slider6; slider7 != lastfoc ? ( lastfoc = slider7; pkM.bq_peak(2500, slider7, 0.8); ); infmt = slider8; outfmt = slider9; drift_macro(slider24); wd = slider10 / 100 * dm_a; ad = slider11 / 100 * dm_a; bd = slider12 / 100 * dm_a; dspd = slider13 * dm_s; out_t = 10 ^ (slider14 / 20); bal_t = slider15 / 100; in_setup(slider22, slider23); ost_setup(slider16 / 100, slider17 / 100, slider18, slider19, slider20, slider21); ); // ---------- presets (leave Output, Drift speed and the I/O rows alone) ---------- function setp(ms, ss, w, bm, ai, fo, inf, outf, w_, a_, b_) ( slider2 = ms; slider3 = ss; slider4 = w; slider5 = bm; slider6 = ai; slider7 = fo; slider8 = inf; slider9 = outf; slider10 = w_; slider11 = a_; slider12 = b_; ); function load_preset(p) ( p == 1 ? setp(0, 0, 120, 0, 1.5, 0, 0, 0, 10, 10, 5) : // Subtle Widen p == 2 ? setp(0, 0.5, 140, 120, 2.5, 0, 0, 0, 8, 10, 3) : // Wide Master p == 3 ? setp(0, 0, 100, 150, 0, 0, 0, 0, 0, 0, 0) : // Mono Bass p == 4 ? setp(1, 0, 90, 100, 0, 3, 0, 0, 5, 5, 3) : // Vocal Focus p == 5 ? setp(0, 1, 175, 200, 4, 0, 0, 0, 15, 15, 5) : // Super Wide p == 6 ? setp(0, -2, 70, 150, 0, 0, 0, 0, 0, 0, 0) : // Narrow & Tight p == 7 ? setp(0, 0, 100, 0, 0, 0, 1, 0, 0, 0, 0) : // M/S Decode p == 8 ? setp(0, 0, 100, 0, 0, 0, 0, 1, 0, 0, 0); // M/S Encode p >= 1 && p <= 8 ? slider_automate(4094); // sliders 2-12 p == 9 ? ( // Reset all to defaults slider1 = 0; slider2 = 0; slider3 = 0; slider4 = 100; slider5 = 0; slider6 = 0; slider7 = 0; slider8 = 0; slider9 = 0; slider10 = 10; slider11 = 10; slider12 = 5; slider13 = 0.25; slider14 = 0; slider15 = 0; slider16 = 0; slider17 = 30; slider18 = 120; slider19 = 6000; slider20 = -0.3; slider21 = 1; slider22 = 0; slider23 = 1; slider24 = 50; slider_automate(16777215); ); ); // ---------- memory ---------- scL = 0; scR = 512; scN = 512; // vectorscope ring memset(0, 0, 1024); scw = 0; scd = 0; // ---------- state ---------- sm = 1 - exp(-1 / (0.03 * srate)); vuc = 1 - exp(-1 / (0.01 * srate)); crc = 1 - exp(-1 / (0.3 * srate)); drW.drift_init(0.8 + rand(0.5)); drA.drift_init(0.8 + rand(0.5)); drB.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; cLR = 0; cLL = 0; cRR = 0; eM = 0; eS = 0; lastfoc = -1; lastair = -99; !ui_dflt ? ui_thm = 5; !ui_dflt ? ui_hw = 0; // defaults for new instances: Graphite, flat (saved projects keep their own) update(); mid_s = mid_t; side_s = side_t; wid_s = wid_t; out_s = out_t; bal_s = bal_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(); @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; vW = drW.drift_tick(dinc); vA = drA.drift_tick(dinc); vB = drB.drift_tick(dinc); // smoothing mid_s += (mid_t - mid_s) * sm; side_s += (side_t - side_s) * sm; wid_s += (wid_t - wid_s) * sm; out_s += (out_t - out_s) * sm; bal_s += (bal_t - bal_s) * sm; // air shelf follows its drift (recomputed only when it moves noticeably) airnow = air_db * (1 + ad * vA); abs(airnow - lastair) > 0.05 ? ( lastair = airnow; shS.bq_shelf(8000, airnow); ); // encode infmt ? ( m = inL; s = inR; ) : ( m = 0.5 * (inL + inR); s = 0.5 * (inL - inR); ); // process bmono >= 20 ? s = hpS2.bq_run(hpS.bq_run(s)); // 24 dB/oct air_db > 0.05 ? s = shS.bq_run(s); slider7 > 0.05 ? m = pkM.bq_run(m); w = max(wid_s * (1 + wd * 0.5 * vW), 0); gM = min(sqrt(2 / (1 + w * w)), 1.4142); m *= mid_s * gM; s *= side_s * w * gM; eM += (m * m - eM) * crc; eS += (s * s - eS) * crc; // listen / decode listen == 1 ? ( oL = m; oR = m; ) : listen == 2 ? ( oL = s; oR = s; ) : outfmt ? ( oL = m; oR = s; ) : ( oL = m + s; oR = m - s; ); // balance (with drift) bal = min(max(bal_s + bd * 0.3 * vB, -1), 1); oL *= min(1, 1 - bal); oR *= min(1, 1 + bal); // output stage 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)); // vectorscope + correlation cLR += (spl0 * spl1 - cLR) * crc; cLL += (spl0 * spl0 - cLL) * crc; cRR += (spl1 * spl1 - cRR) * crc; (scd += 1) >= 3 ? ( scd = 0; scL[scw] = spl0; scR[scw] = spl1; scw = (scw + 1) % scN; ); @gfx 640 392 ui_begin(640, 392); ui_swb(); ui_swbg(14, 626, 45, 348, 392); // drawn off-screen, then shown with the preset row moved up under the header ui_header("MID/SIDE"); // ---------- header controls ---------- ui_theme_picker(148 * sc, 17 * sc); ui_size_picker(184 * sc, 13 * sc); ui_hw_toggle(248 * sc, 13 * sc); // the preset row under the header: PRESETS, prev / next, LCD (click it for the list), at the right; // designed below the panel and shown at the top ui_swu(45 - 348); pk_pextra = "Reset all to defaults"; k_ = pk_preset(244 * sc, 356 * sc, 380 * sc, 30 * sc, pr_cur, "Subtle Widen|Wide Master|Mono Bass|Vocal Focus|Super Wide|Narrow & Tight|M/S Decode|M/S Encode"); k_ > 0 ? ( load_preset(k_); pr_cur = k_ > 8 ? 0 : k_; ); ui_swu(0); ui_button(410 * sc, 10 * sc, 70 * sc, 24 * sc, "STEREO", slider1 == 0, 1) ? ( slider1 = 0; slider_automate(slider1); ); ui_button(482 * sc, 10 * sc, 70 * sc, 24 * sc, "MID", slider1 == 1, 1) ? ( slider1 = 1; slider_automate(slider1); ); ui_button(554 * sc, 10 * sc, 70 * sc, 24 * sc, "SIDE", slider1 == 2, 1) ? ( slider1 = 2; slider_automate(slider1); ); ui_swu(44); // the panel, shown below the preset row // ---------- display window: vectorscope, correlation, mid / side energy ---------- ui_screen(16 * sc, 52 * sc, 454 * sc, 80 * sc); vcx = 72 * sc; vcy = 92 * sc; vr = 34 * sc; ui_col(C_SG); gfx_line(vcx - vr, vcy, vcx + vr, vcy); gfx_line(vcx, vcy - vr, vcx, vcy + vr); gfx_line(vcx - vr * 0.7, vcy - vr * 0.7, vcx + vr * 0.7, vcy + vr * 0.7); gfx_line(vcx + vr * 0.7, vcy - vr * 0.7, vcx - vr * 0.7, vcy + vr * 0.7); gfx_setfont(4); ui_col(C_ST); gfx_x = vcx - vr * 0.7 - 6 * sc; gfx_y = vcy - vr * 0.7 - 10 * sc; gfx_drawstr("L"); gfx_x = vcx + vr * 0.7 + 1 * sc; gfx_y = vcy - vr * 0.7 - 10 * sc; gfx_drawstr("R"); vk = vr * 0.9 / max(sqrt(max(cLL, cRR)) * 2.5, 0.05); ui_cola(C_VA, 0.7); i = 0; loop(scN, sl = scL[i]; sr_ = scR[i]; qx = vcx + (sr_ - sl) * 0.7071 * vk; qy = vcy - (sl + sr_) * 0.7071 * vk; abs(qx - vcx) < vr && abs(qy - vcy) < vr ? gfx_rect(qx, qy, 1.5 * sc, 1.5 * sc); i += 1; ); // correlation corr = cLL > 0.0000001 && cRR > 0.0000001 ? cLR / sqrt(cLL * cRR) : 1; bx0 = 140 * sc; bw0 = 300 * sc; by0 = 66 * sc; ui_col(C_SG); gfx_rect(bx0, by0, bw0, 6 * sc); gfx_line(bx0 + bw0 * 0.5, by0 - 3 * sc, bx0 + bw0 * 0.5, by0 + 9 * sc); corr < 0 ? ui_col(C_CLIP) : ui_col(C_VB); gfx_rect(bx0 + bw0 * 0.5 + min(corr, 0) * bw0 * 0.5, by0, abs(corr) * bw0 * 0.5, 6 * sc); gfx_setfont(4); ui_col(C_ST); gfx_x = bx0; gfx_y = by0 + 9 * sc; gfx_drawstr("-1"); gfx_x = bx0 + bw0 - 8 * sc; gfx_y = by0 + 9 * sc; gfx_drawstr("+1"); sprintf(#cr, "CORRELATION %+.2f", corr); ui_text(bx0 + bw0 * 0.5, by0 + 9 * sc, #cr); // mid / side energy mdb_ = 10 * log10(max(eM, 0.000000001)); sdb_ = 10 * log10(max(eS, 0.000000001)); ui_col(C_SG); gfx_rect(bx0, 96 * sc, bw0, 6 * sc); gfx_rect(bx0, 112 * sc, bw0, 6 * sc); ui_col(C_VA); gfx_rect(bx0, 96 * sc, bw0 * min(max((mdb_ + 60) / 60, 0), 1), 6 * sc); ui_col(C_VB); gfx_rect(bx0, 112 * sc, bw0 * min(max((sdb_ + 60) / 60, 0), 1), 6 * sc); ui_col(C_ST); gfx_x = 118 * sc; gfx_y = 94 * sc; gfx_drawstr("MID"); gfx_x = 118 * sc; gfx_y = 110 * sc; gfx_drawstr("SIDE"); // ---------- input VU ---------- vdb = 10 * log10(max(vu_ms, 0.0000000001)) + 18; // 0 VU = -18 dBFS RMS vuIn.ui_vu(478 * sc, 52 * sc, 146 * sc, 80 * sc, vdb, "INPUT"); // ---------- M/S group ---------- ui_group(16 * sc, 140 * sc, 608 * sc, 96 * sc, 0, "M/S"); ui_noled = 1; ui_button(42 * sc, 156 * sc, 78 * sc, 22 * sc, slider8 ? "IN M/S" : "IN L/R", slider8 == 1, 1) ? ( slider8 = 1 - slider8; slider_automate(slider8); ); ui_noled = 1; ui_button(42 * sc, 186 * sc, 78 * sc, 22 * sc, slider9 ? "OUT M/S" : "OUT L/R", slider9 == 1, 1) ? ( slider9 = 1 - slider9; slider_automate(slider9); ); ui_knob(170 * sc, 186 * sc, 2, -12, 12, 0, 0, "MID", "%+.1f dB"); ui_knob(244 * sc, 186 * sc, 3, -12, 12, 0, 0, "SIDE", "%+.1f dB"); ui_knob(318 * sc, 186 * sc, 4, 0, 200, 100, 0, "WIDTH", "%.0f%%"); ui_knob(392 * sc, 186 * sc, 5, 0, 300, 0, 0, "BASS MONO", slider5 < 20 ? "OFF" : "%.0f Hz"); ui_knob(466 * sc, 186 * sc, 6, 0, 9, 0, 0, "AIR", "+%.1f dB"); ui_knob(540 * sc, 186 * sc, 7, 0, 6, 0, 0, "FOCUS", "+%.1f dB"); // ---------- DRIFT (row 4, left) ---------- ui_group(16 * sc, 244 * sc, 92 * sc, 96 * sc, 1, "DRIFT"); ui_knob(62 * sc, 290 * sc, 24, 0, 100, 50, 0, "DRIFT", "%.0f%%"); // ---------- IN / OUT (row 4, middle) ---------- ui_group(116 * sc, 244 * sc, 236 * sc, 96 * sc, 3, "IN / OUT"); ui_io(146, 262, 22, 23, 16, 21); // ---------- MASTER group (row 4, right) ---------- ui_group(360 * sc, 244 * sc, 264 * sc, 96 * sc, 2, "MASTER"); ui_knob(414 * sc, 290 * sc, 15, -100, 100, 0, 0, "BALANCE", "%+.0f%%"); ui_knob(488 * sc, 290 * sc, 14, -24, 12, 0, 0, "OUTPUT", "%.1f dB"); mL.ui_ledm(564 * sc, 256 * sc, 14 * sc, 70 * sc, pkL); mR.ui_ledm(584 * sc, 256 * sc, 14 * sc, 70 * sc, pkR); pkL *= 0.85; pkR *= 0.85; gfx_setfont(4); ui_col(C_INK); ui_text(581 * sc, 328 * sc, "OUT"); ui_swrow(14, 626, 45, 348, 392, 0, 0, 0, 0, 0); ui_end();