desc:GFX Lustre //tags: summing mixer transformer console harmonics air stems //author: JGWizrad / Garden FX (GFX) // // Requires gfx-drift, gfx-output, gfx-ui, gfx-panel, gfx-instrument and gfx-dyn (.jsfx-inc) in the same folder. // // A transformer-coupled British console from the late 80s as a summing mixer, in the spirit of the silky, // open-sounding desks designed by the great transformer-console designers (personal use; the sound is designed // from scratch): smooth, wide and expensive-sounding, with weight in the low mids and a top end that never bites. // // SET-UP (REAPER): set the bus track to 16 channels and send each stem to its own pair (1/2, 3/4 ... 15/16; // turn off the stems' master send). The mix leaves on 1/2. // // STEMS LEVEL, PAN (balance), SOLO, MUTE; a light shows signal arriving. // TRANSFORMER in: the console's colour; out: a straight, clean sum. // DRIVE level into the bus and its transformers (level-matched: it changes the colour, not the volume). // DENSITY how hard the transformer iron works (saturation concentrated in the low mids, 80 - 400 Hz). // COLOUR the character of the harmonics: left = EVEN (2nd: rounder, warmer), right = ODD (3rd: firmer, // more forward); centre = the balanced blend. // AIR a broad, silky lift at the very top (above ~12 kHz). // WEIGHT the transformer's low-mid weight (a broad lift around 180 Hz). // MASTER level, VU meters. // PRESETS. // NAMES - click the name at the top of a channel strip and type (e.g. Drums); Enter keeps it, Escape cancels, // an empty name goes back to the number. The names are saved with the project. import gfx-drift.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 slider1:0<-6,12,0.1>-Drive (dB) slider2:30<0,100,1>-Density (%) slider3:0<-100,100,1>-Colour (- even / + odd) slider4:0<0,6,0.1>-Air (dB) slider5:0<0,4,0.1>-Weight (dB) slider6:1<0,1,1{Out,In}>-Transformer slider7:0<-60,10,0.1>-Master (dB) slider8:0<-60,10,0.1>-Stem 1 level (dB) slider9:0<-60,10,0.1>-Stem 2 level (dB) slider10:0<-60,10,0.1>-Stem 3 level (dB) slider11:0<-60,10,0.1>-Stem 4 level (dB) slider12:0<-60,10,0.1>-Stem 5 level (dB) slider13:0<-60,10,0.1>-Stem 6 level (dB) slider14:0<-60,10,0.1>-Stem 7 level (dB) slider15:0<-60,10,0.1>-Stem 8 level (dB) slider16:0<-100,100,1>-Stem 1 pan slider17:0<-100,100,1>-Stem 2 pan slider18:0<-100,100,1>-Stem 3 pan slider19:0<-100,100,1>-Stem 4 pan slider20:0<-100,100,1>-Stem 5 pan slider21:0<-100,100,1>-Stem 6 pan slider22:0<-100,100,1>-Stem 7 pan slider23:0<-100,100,1>-Stem 8 pan slider24:0<0,1,1{Off,On}>-Stem 1 mute slider25:0<0,1,1{Off,On}>-Stem 2 mute slider26:0<0,1,1{Off,On}>-Stem 3 mute slider27:0<0,1,1{Off,On}>-Stem 4 mute slider28:0<0,1,1{Off,On}>-Stem 5 mute slider29:0<0,1,1{Off,On}>-Stem 6 mute slider30:0<0,1,1{Off,On}>-Stem 7 mute slider31:0<0,1,1{Off,On}>-Stem 8 mute slider32:0<0,1,1{Off,On}>-Stem 1 solo slider33:0<0,1,1{Off,On}>-Stem 2 solo slider34:0<0,1,1{Off,On}>-Stem 3 solo slider35:0<0,1,1{Off,On}>-Stem 4 solo slider36:0<0,1,1{Off,On}>-Stem 5 solo slider37:0<0,1,1{Off,On}>-Stem 6 solo slider38:0<0,1,1{Off,On}>-Stem 7 solo slider39:0<0,1,1{Off,On}>-Stem 8 solo slider40:0<0,100,1>-Band sat (%) slider41:30<0,100,1>-Bias (%) slider42:120<40,400,1>-Sat low band (Hz) slider43:6000<2000,16000,10>-Sat high band (Hz) slider44:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider45:1<0,2,1{Off,Clip,Limit}>-Output stage in_pin:stem 1 L in_pin:stem 1 R in_pin:stem 2 L in_pin:stem 2 R in_pin:stem 3 L in_pin:stem 3 R in_pin:stem 4 L in_pin:stem 4 R in_pin:stem 5 L in_pin:stem 5 R in_pin:stem 6 L in_pin:stem 6 R in_pin:stem 7 L in_pin:stem 7 R in_pin:stem 8 L in_pin:stem 8 R out_pin:mix L out_pin:mix R @init #ui_man = "gfx-lustre"; // the ? pop-up links to this manual LHD = 180; // signal-light hold per input (gfx) DEN = 0.00000000000000000001; SG = 100; SGT = 110; SPK = 120; PGL = 130; PGR = 140; memset(100, 0, 50); // the transformer bus: low-mid band through the iron (even / odd blend), gentle full-band density, // low-mid weight, air function tx_set() instance(wt, ar) ( wt.bq_set(2, 180, 0.7, slider5); ar.bq_set(3, 12000, 0.5, slider4); ); function tx(x) instance(lm, hp, wt, ar, dcx, dcy) local(b, e, o, s, y) ( lm += (x - lm) * lm_c + DEN; // below ~400 Hz ... hp += (lm - hp) * hp_c + DEN; b = lm - hp; // ... and above ~60 Hz: the low-mid band the iron works on e = b + ke * b * b; // even: 2nd harmonic o = ost_tanh(b * ko) / ko; // odd: 3rd harmonic s = e * (1 - cmix) + o * cmix; y = x - b + s; y = ost_tanh(y * kd) / kd; // a touch of full-band density dcy = y - dcx + 0.9995 * dcy; dcx = y; // the even harmonics bring DC: block it ar.bq_run(wt.bq_run(dcy)); ); function update() local(i, any, a, d) ( drv = 10 ^ (slider1 / 20); d = slider2 / 100; ke = 0.4 * d; ko = 1 + 1.6 * d; kd = 1 + 0.5 * d; cmix = (slider3 + 100) / 200; tx_on = slider6; mst = slider7 <= -59.9 ? 0 : 10 ^ (slider7 / 20); bl.tx_set(); br.tx_set(); any = 0; i = 0; loop(8, any |= slider(32 + i); i += 1; ); i = 0; loop(8, SGT[i] = slider(24 + i) || (any && !slider(32 + i)) || slider(8 + i) <= -59.9 ? 0 : 10 ^ (slider(8 + i) / 20); a = (slider(16 + i) + 100) / 200; PGL[i] = min(1, 2 * (1 - a)); PGR[i] = min(1, 2 * a); i += 1; ); ost_setup(0, 0, 120, 6000, -0.3, 0); // no GFX output stage on this unit: its own character does that job ); // ---------- presets (stems are left alone) ---------- // drive, density, colour, air, weight, transformer function pp(dv, dn, cl, ai, wt, tx) ( slider1 = dv; slider2 = dn; slider3 = cl; slider4 = ai; slider5 = wt; slider6 = tx; ); function load_preset(p) ( p == 1 ? pp(0, 30, 0, 1, 1, 1) : // Console Sheen p == 2 ? pp(4, 50, -60, 2, 2, 1) : // Warm and Round (even) p == 3 ? pp(4, 50, 60, 1.5, 1, 1) : // Forward and Firm (odd) p == 4 ? pp(8, 80, -20, 2.5, 2.5, 1) : // Driven Desk p == 5 ? pp(0, 15, 0, 3, 0.5, 1) : // Open Top (air) p == 6 ? pp(2, 40, -40, 0, 3, 1) : // Low-Mid Weight p == 7 ? pp(0, 0, 0, 0, 0, 0); // Straight Sum (transformer out) p >= 1 && p <= 7 ? slider_automate(63); p == 8 ? ( // Reset all to defaults pp(0, 30, 0, 0, 0, 1); slider7 = 0; i = 0; loop(8, slider(8 + i) = 0; slider(16 + i) = 0; slider(24 + i) = 0; slider(32 + i) = 0; i += 1; ); slider40 = 0; slider41 = 30; slider42 = 120; slider43 = 6000; slider44 = -0.3; slider45 = 1; i = 1; loop(45, ui_auto(i); i += 1; ); ); ); ost_init(20000); ostL.ost_ch_init(); ostR.ost_ch_init(); sm = 1 - exp(-1 / (0.02 * srate)); lm_c = 1 - exp(-2 * $pi * 400 / srate); hp_c = 1 - exp(-2 * $pi * 60 / srate); vuc = 1 - exp(-1 / (0.3 * srate)); !ui_dflt ? ui_thm = 5; !ui_dflt ? ui_hw = 1; PNL = 0xE6DDC6; INK = 0x3E2818; BRN = 0x6E4428; AMB = 0xC8862E; CRM = 0xF4EEDC; update(); i = 0; loop(8, SG[i] = SGT[i]; i += 1; ); 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); gn = 0; loop(8, file_avail(0) != 0 ? file_string(0, UI_NM + gn); gn += 1; ); // channel names (older projects have none) file_avail(0) != 0 ? file_var(0, pr_cur); // the preset in use (older projects end before it) @sample sl = 0; sr = 0; k = 0; loop(8, SG[k] += (SGT[k] - SG[k]) * sm; SPK[k] = max(SPK[k], max(abs(spl(2 * k)), abs(spl(2 * k + 1)))); sl += spl(2 * k) * SG[k] * PGL[k]; sr += spl(2 * k + 1) * SG[k] * PGR[k]; k += 1; ); tx_on ? ( sl = bl.tx(sl * drv) / drv; sr = br.tx(sr * drv) / drv; ); sl *= mst; sr *= mst; sl = ostL.ost_band(sl); sr = ostR.ost_band(sr); ost_out(sl, sr); spl0 = ost_l; spl1 = ost_r; k = 2; loop(14, spl(k) = 0; k += 1; ); vmL += (spl0 * spl0 - vmL) * vuc; vmR += (spl1 * spl1 - vmR) * vuc; @gfx 900 360 ui_begin(900, ins_show_out ? 480 : 360); dy_ink = INK; dy_bg = PNL; dy_kst = 7; dy_nov = 1; // ---------- panel ---------- fh = ins_show_out ? 472 : 352; ui_col(0x0E0B08); gfx_rect(0, 0, gfx_w, gfx_h); pk_chassis(8 * sc, 4 * sc, 884 * sc, fh * sc, PNL); ui_col(BRN); gfx_rect(10 * sc, 6 * sc, 880 * sc, 54 * sc); // brown header band ui_col(CRM); gfx_circle(40 * sc, 33 * sc, 14 * sc, 1, 1); gfx_setfont(4); ui_col(BRN); ui_text(40 * sc, 28 * sc, "GFX"); gfx_setfont(6, "Trebuchet MS", 24 * sc, 'b'); ui_col(CRM); gfx_x = 62 * sc; gfx_y = 19 * sc; gfx_drawstr("LUSTRE"); gfx_setfont(4); ui_col(0xE8B868); gfx_x = 172 * sc; gfx_y = 29 * sc; gfx_drawstr("TRANSFORMER CONSOLE SUMMING"); pk_pextra = "Reset all to defaults"; // the house preset display: PRESETS, prev / next, LCD (click it for the list) k_ = pk_preset(470 * sc, 14 * sc, 346 * sc, 36 * sc, pr_cur, "Console Sheen|Warm and Round (even)|Forward and Firm (odd)|Driven Desk|Open Top (air)|Low-Mid Weight|Straight Sum (transformer out)"); k_ > 0 ? ( load_preset(k_); pr_cur = k_ > 7 ? 0 : k_; ); ui_size_picker(828 * sc, 22 * sc); ui_col(AMB); gfx_rect(10 * sc, 60 * sc, 880 * sc, 2 * sc); // ---------- stems ---------- gk = 0; loop(8, cx = 48 + gk * 62; ui_cola(BRN, 0.08); gfx_rect((cx - 29) * sc, 70 * sc, 58 * sc, 274 * sc); sprintf(#cn, "%d", gk + 1); ui_namebox(cx, 71, 56, gk, #cn, INK); // click to name the stem sprintf(#cn, "%d-%d", 2 * gk + 1, 2 * gk + 2); dy_lbl(cx, 89, #cn, 0.55); LHD[gk] = max(SPK[gk], LHD[gk] * 0.9); SPK[gk] = 0; // held, so the light does not flicker between audio blocks pk_led(cx * sc, 104 * sc, 3.2 * sc, LHD[gk] > 0.001 && SGT[gk] > 0, dy_grn); slider(16 + gk) == 0 ? strcpy(#v, "C") : sprintf(#v, "%s%.0f", slider(16 + gk) < 0 ? "L" : "R", abs(slider(16 + gk))); dy_knob(cx, 140, 12, 16 + gk, -100, 100, 0, 0, 0xB89870, "PAN", #v); slider(8 + gk) <= -59.9 ? strcpy(#v, "OFF") : sprintf(#v, "%+.1f", slider(8 + gk)); dy_knob(cx, 222, 18, 8 + gk, -60, 10, 0, 0, BRN, "LEVEL", #v); pk_ledbtn((cx - 24) * sc, 296 * sc, 22 * sc, 18 * sc, slider(32 + gk), 0xF2C21B) ? ( slider(32 + gk) = 1 - slider(32 + gk); ui_auto(32 + gk); ); pk_ledbtn((cx + 2) * sc, 296 * sc, 22 * sc, 18 * sc, slider(24 + gk), dy_red) ? ( slider(24 + gk) = 1 - slider(24 + gk); ui_auto(24 + gk); ); dy_lbl(cx - 13, 317, "SOLO", 0.65); dy_lbl(cx + 13, 317, "MUTE", 0.65); gk += 1; ); // ---------- master: VUs, the transformer bus, master level ---------- ui_cola(BRN, 0.14); gfx_rect(536 * sc, 70 * sc, 348 * sc, 274 * sc); vuL.ui_vu(544 * sc, 76 * sc, 166 * sc, 92 * sc, 10 * log10(vmL + 0.0000000001) + 18, "L"); vuR.ui_vu(712 * sc, 76 * sc, 166 * sc, 92 * sc, 10 * log10(vmR + 0.0000000001) + 18, "R"); dy_nov = 0; sprintf(#v, "%+.1f dB", slider1); dy_knob(570, 216, 15, 1, -6, 12, 0, 0, AMB, "DRIVE", #v); sprintf(#v, "%.0f", slider2); dy_knob(628, 216, 15, 2, 0, 100, 30, 0, AMB, "DENSITY", #v); slider3 < -2 ? sprintf(#v, "EVEN %.0f", -slider3) : slider3 > 2 ? sprintf(#v, "ODD %.0f", slider3) : strcpy(#v, "BLEND"); dy_knob(686, 216, 15, 3, -100, 100, 0, 0, AMB, "COLOUR", #v); sprintf(#v, "+%.1f dB", slider4); dy_knob(744, 216, 15, 4, 0, 6, 0, 0, 0xB89870, "AIR", #v); sprintf(#v, "+%.1f dB", slider5); dy_knob(802, 216, 15, 5, 0, 4, 0, 0, 0xB89870, "WEIGHT", #v); slider7 <= -59.9 ? strcpy(#v, "OFF") : sprintf(#v, "%+.1f dB", slider7); dy_knob(852, 216, 15, 7, -60, 10, 0, 0, BRN, "MASTER", #v); dy_btn(552, 290, slider6, AMB, "TRANSFORMER") ? ( slider6 = 1 - slider6; ui_auto(6); ); dy_lbl(712, 300, slider6 ? "TRANSFORMER-COUPLED BUS" : "STRAIGHT SUM", 0.6); dy_lbl(712, 314, "EVEN < COLOUR > ODD", 0.45); pk_end(); ui_end();