desc:GFX Model 44 //tags: channel strip preamp tape saturation rack companding punch //author: JGWizrad / Garden FX (GFX) // // Requires gfx-output, gfx-input, gfx-ui, gfx-panel and gfx-dyn (.jsfx-inc) in the same folder. // // A single-channel strip in the spirit of the early-90s rack-mountable cassette portable-studio ecosystem: // a cleaner, higher-headroom solid-state preamp, a tighter semi-parametric EQ, a dense magnetic // saturation ceiling with a Normal / Double speed switch, and a companding noise reduction tuned for // less pumping than the earlier 80s machines (personal use; the sound is designed from scratch). // // GAIN - a clean, high-headroom preamp: it stays pristine well past unity before the tape stage's // ceiling takes over. // MID - a sweepable mid-band (FREQ / GAIN, fixed Q): surgical sculpting without falling apart. // TREBLE / BASS - clean fixed shelves (12 kHz / 80 Hz). // TAPE DRIVE - the magnetic saturation ceiling's depth. // SPEED - Normal (1 7/8 ips) or Double (3 3/4 ips): double speed pushes the ceiling higher, // extends the top end, tightens the sub-bass and keeps transients punchier. // NR - 2:1 / 1:2 compander noise reduction, in / out: gentler and less pumpy than an 80s unit. // MIX - blend of the coloured signal against the dry input. // IN/OUT - input TRIM and GUARD, OUTPUT level, and the output band-saturation / clipper / limiter stage. // PRESETS. import gfx-output.jsfx-inc import gfx-input.jsfx-inc import gfx-ui.jsfx-inc import gfx-panel.jsfx-inc import gfx-dyn.jsfx-inc slider1:6<-6,24,0.1>-Gain (dB) slider2:1000<200,5000,1>-Mid freq (Hz) slider3:0<-12,12,0.1>-Mid gain (dB) slider4:0<-10,10,0.1>-Treble (12 kHz) (dB) slider5:0<-10,10,0.1>-Bass (80 Hz) (dB) slider6:35<0,100,1>-Tape drive (%) slider7:0<0,1,1{Normal (1 7/8 ips),Double (3 3/4 ips)}>-Speed slider8:1<0,1,1{Off,On}>-Noise reduction slider9:100<0,100,1>-Mix (%) slider10:0<-24,12,0.1>-Output (dB) slider11:0<0,100,1>-Band sat (%) slider12:30<0,100,1>-Bias (%) slider13:120<40,400,1>-Sat low band (Hz) slider14:6000<2000,16000,10>-Sat high band (Hz) slider15:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider16:1<0,2,1{Off,Clip,Limit}>-Output stage slider17:0<-24,12,0.1>-Input trim (dB) slider18:1<0,1,1{Off,On}>-Input guard in_pin:left input in_pin:right input out_pin:left output out_pin:right output @init #ui_man = "gfx-model-44"; DEN = 0.00000000000000000001; // ---------- clean, high-headroom preamp: no coloration here, the ceiling lives in the tape stage ---------- function vx_gain(x) ( x * g_lin ); // ---------- semi-parametric EQ: sweepable mid, clean fixed shelves ---------- function vx_eq_set() instance(mid, ts, bsf) ( mid.bq_set(2, mid_hz, 0.9, mid_db); ts.bq_set(3, 12000, 0.7, tr_db); bsf.bq_set(4, 80, 0.7, bs_db); ); function vx_eq(x) instance(mid, ts, bsf) ( bsf.bq_run(ts.bq_run(mid.bq_run(x))) ); // ---------- the dense, punchy magnetic saturation ceiling ---------- function vx_ceil(x) local(a, y) ( a = abs(x); a <= vx_knee ? y = a : y = vx_knee + (1 - vx_knee) * ost_tanh((a - vx_knee) * vx_drv / max(1 - vx_knee, 0.0001)); sign(x) * y; ); // ---------- the tape: ceiling saturation, speed-dependent HF / sub-bass, 2:1 / 1:2 compander NR ---------- function vx_tape_set() instance(hp) ( hp.bq_set(1, sub_hz, 0.7, 0); ); function vx_tape(x, nron) instance(pe, de, hp, lp, ee, ed) local(y, lv) ( // record-side NR encode: compress 2:1 around -20 dBFS, with a treble lift into the tape nron ? ( y = pe.bq_run(x); ee += (y * y - ee) * nr_c + DEN; lv = 10 * log10(ee); y *= 10 ^ (min(-0.5 * (lv + 20), 24) / 20); ) : y = x; // the ceiling, and the speed switch's sub-bass tightening y = y + tape_wet * (vx_ceil(y) - y); y = hp.bq_run(y); // the speed switch's top-end behaviour (fixed per speed, not dynamic: this unit stays clean) lp += (y - lp) * hf_c; y = lp; // playback-side NR decode: 1:2 expand against what came off the tape nron ? ( ed += (y * y - ed) * nr_c + DEN; lv = 10 * log10(ed); y *= 10 ^ (max(lv + 20, -60) / 20); y = de.bq_run(y); ); y; ); function update() ( g_db = slider1; g_lin = 10 ^ (g_db / 20); mid_hz = slider2; mid_db = slider3; tr_db = slider4; bs_db = slider5; tape_amt = slider6 / 100; speed = slider7; vx_knee = speed ? 0.75 : 0.55; vx_drv = 2 + 7 * tape_amt; tape_wet = min(tape_amt * 2, 1); sub_hz = speed ? 45 : 25; hf_hz = speed ? 16000 : 10000; hf_c = 1 - exp(-2 * $pi * min(hf_hz, srate * 0.45) / srate); nr_c = 1 - exp(-1 / (0.035 * srate)); // slower than an 80s unit: a gentler, less pumpy compander nr_on = slider8; mix_t = slider9 / 100; out_t = 10 ^ (slider10 / 20); eqL.vx_eq_set(); eqR.vx_eq_set(); tpL.pe.bq_set(3, 2000, 0.6, 10); tpL.de.bq_set(3, 2000, 0.6, -10); tpR.pe.bq_set(3, 2000, 0.6, 10); tpR.de.bq_set(3, 2000, 0.6, -10); tpL.vx_tape_set(); tpR.vx_tape_set(); in_setup(slider17, slider18); ost_setup(slider11 / 100, slider12 / 100, slider13, slider14, slider15, slider16); ); // ---------- presets (gain, mid freq, mid gain, treble, bass, drive, speed, nr, mix - output is left alone) ---------- function setp(g, mf, mg, tr, bs, dr, sp, n, mx) ( slider1 = g; slider2 = mf; slider3 = mg; slider4 = tr; slider5 = bs; slider6 = dr; slider7 = sp; slider8 = n; slider9 = mx; ); function pname(p) ( p == 1 ? "Clean Double Speed" : p == 2 ? "Normal Speed Warm" : p == 3 ? "Rack Punch" : p == 4 ? "Scooped Mids" : p == 5 ? "No NR" : "Reset all to defaults"; ); function load_preset(p) ( p == 1 ? setp(6, 1000, 0, 2, 0, 20, 1, 1, 100) : // Clean Double Speed p == 2 ? setp(9, 800, 1, 0, 1, 35, 0, 1, 100) : // Normal Speed Warm p == 3 ? setp(14, 2500, 2, 3, 0, 55, 1, 1, 100) : // Rack Punch p == 4 ? setp(6, 600, -4, 1, 1, 25, 1, 1, 100) : // Scooped Mids p == 5 ? setp(6, 1000, 0, 0, 0, 30, 0, 0, 100); // No NR p >= 1 && p <= 5 ? slider_automate(1023); p == 6 ? ( // Reset all to defaults setp(6, 1000, 0, 0, 0, 35, 0, 1, 100); slider10 = 0; slider11 = 0; slider12 = 30; slider13 = 120; slider14 = 6000; slider15 = -0.3; slider16 = 1; slider17 = 0; slider18 = 1; slider_automate(262143); ); ); ost_init(40000); ostL.ost_ch_init(); ostR.ost_ch_init(); in_init(); sm = 1 - exp(-1 / (0.02 * srate)); vuc = 1 - exp(-1 / (0.3 * srate)); cur_preset = 1; !ui_dflt ? ui_thm = 5; !ui_dflt ? ui_hw = 1; update(); mix_s = mix_t; out_s = out_t; ui_dflt = 1; @slider update(); @block update(); @serialize file_var(0, ui_sz); @sample in_process(spl0, spl1); inL = in_l; inR = in_r; gL = vx_gain(inL); gR = vx_gain(inR); eL = eqL.vx_eq(gL); eR = eqR.vx_eq(gR); tL = tpL.vx_tape(eL, nr_on); tR = tpR.vx_tape(eR, nr_on); mix_s += (mix_t - mix_s) * sm; out_s += (out_t - out_s) * sm; oL = inL * (1 - mix_s) + tL * mix_s; oR = inR * (1 - mix_s) + tR * mix_s; oL = ostL.ost_band(oL) * out_s; oR = ostR.ost_band(oR) * out_s; ost_out(oL, oR); spl0 = ost_l; spl1 = ost_r; vmL += (spl0 * spl0 - vmL) * vuc; vmR += (spl1 * spl1 - vmR) * vuc; pkL = max(pkL, abs(spl0)); pkR = max(pkR, abs(spl1)); @gfx 760 404 ui_begin(760, 404); PNL = 0xB4B8BE; INK = 0x1C1F24; ACC = 0x2F6FD0; WIN = 0x1A1C20; dy_ink = INK; dy_bg = PNL; dy_kst = 20; dy_nov = 1; ui_col(0x0A0A0B); gfx_rect(0, 0, gfx_w, gfx_h); pk_chassis(8 * sc, 4 * sc, 744 * sc, 396 * sc, PNL); ui_col(INK); gfx_circle(40 * sc, 34 * sc, 14 * sc, 1, 1); gfx_setfont(4); ui_col(PNL); ui_text(40 * sc, 29 * sc, "GFX"); gfx_setfont(6, "Trebuchet MS", 24 * sc, 'b'); ui_col(INK); gfx_x = 62 * sc; gfx_y = 20 * sc; gfx_drawstr("MODEL 44"); gfx_setfont(4); ui_col(ACC); gfx_x = 208 * sc; gfx_y = 30 * sc; gfx_drawstr("RACK CHANNEL"); ui_button(388 * sc, 20 * sc, 26 * sc, 28 * sc, "<", 0, 1) ? ( cur_preset = cur_preset <= 1 ? 6 : cur_preset - 1; load_preset(cur_preset); ); ui_button(416 * sc, 20 * sc, 26 * sc, 28 * sc, ">", 0, 1) ? ( cur_preset = cur_preset >= 6 ? 1 : cur_preset + 1; load_preset(cur_preset); ); ui_button(450 * sc, 20 * sc, 90 * sc, 28 * sc, "PRESETS", 0, 1) ? ( gfx_x = 450 * sc; gfx_y = 48 * sc; cur_preset = gfx_showmenu("Clean Double Speed|Normal Speed Warm|Rack Punch|Scooped Mids|No NR|Reset all to defaults"); cur_preset > 0 ? load_preset(cur_preset); ); ui_size_picker(560 * sc, 22 * sc); pk_lcd(624 * sc, 16 * sc, 120 * sc, 32 * sc, 1); pk_lcdfont(9, 9 * sc); ui_col(pk_lcdc); gfx_x = 630 * sc; gfx_y = 22 * sc; gfx_drawstr("PATCH"); pk_lcdfont(9, 11 * sc); ui_col(pk_lcdc); gfx_x = 630 * sc; gfx_y = 33 * sc; gfx_drawstr(pname(cur_preset)); ui_col(ACC); gfx_rect(14 * sc, 60 * sc, 732 * sc, 3 * sc); // ---------- CHANNEL: gain, mid, shelves, trim / guard, mix ---------- ui_cola(INK, 0.06); gfx_rect(22 * sc, 72 * sc, 368 * sc, 316 * sc); gfx_setfont(6, "Trebuchet MS", 13 * sc, 'b'); ui_col(ACC); gfx_x = 34 * sc; gfx_y = 82 * sc; gfx_drawstr("CHANNEL"); ui_col(ACC); gfx_rect(34 * sc, 98 * sc, 344 * sc, 1 * sc); LPK = max(max(pkL, pkR), LPK * 0.9); pkL = 0; pkR = 0; pk_led(206 * sc, 112 * sc, 3.5 * sc, LPK > 0.001, dy_grn); sprintf(#v, "%+.1f dB", slider1); dy_knob(90, 160, 20, 1, -6, 24, 6, 0, ACC, "GAIN", #v); sprintf(#v, "%.0f Hz", slider2); dy_knob(170, 160, 16, 2, 200, 5000, 1000, 1, 0xB8BCC2, "MID FREQ", #v); sprintf(#v, "%+.1f dB", slider3); dy_knob(250, 160, 16, 3, -12, 12, 0, 0, 0xB8BCC2, "MID GAIN", #v); sprintf(#v, "%+.1f dB", slider4); dy_knob(330, 160, 16, 4, -10, 10, 0, 0, 0xB8BCC2, "TREBLE", #v); sprintf(#v, "%+.1f dB", slider5); dy_knob(90, 260, 16, 5, -10, 10, 0, 0, 0xB8BCC2, "BASS", #v); sprintf(#v, "%+.1f dB", slider17); dy_knob(170, 260, 16, 17, -24, 12, 0, 0, 0xB8BCC2, "TRIM", #v); dy_btn(238, 244, slider18, dy_grn, "GUARD") ? ( slider18 = 1 - slider18; ui_auto(18); ); sprintf(#v, "%.0f%%", slider9); dy_knob(330, 260, 16, 9, 0, 100, 100, 0, 0xF2F2EE, "MIX", #v); // ---------- meters ---------- vuL.ui_vu(400 * sc, 72 * sc, 170 * sc, 104 * sc, 10 * log10(vmL + 0.0000000001) + 18, "L"); vuR.ui_vu(576 * sc, 72 * sc, 170 * sc, 104 * sc, 10 * log10(vmR + 0.0000000001) + 18, "R"); // ---------- tape + output ---------- ui_cola(INK, 0.06); gfx_rect(400 * sc, 188 * sc, 346 * sc, 200 * sc); gfx_setfont(6, "Trebuchet MS", 13 * sc, 'b'); ui_col(ACC); gfx_x = 410 * sc; gfx_y = 194 * sc; gfx_drawstr("TAPE"); ui_col(ACC); gfx_rect(410 * sc, 210 * sc, 326 * sc, 1 * sc); dy_nov = 0; sprintf(#v, "%.0f%%", slider6); dy_knob(452, 250, 20, 6, 0, 100, 35, 0, ACC, "DRIVE", #v); sprintf(#v, "%+.1f dB", slider10); dy_knob(710, 250, 20, 10, -24, 12, 0, 0, 0xF2F2EE, "OUTPUT", #v); dy_btn(540, 236, speed == 0, ACC, "NORMAL") ? ( slider7 = 0; ui_auto(7); ); dy_btn(540, 270, speed == 1, ACC, "DOUBLE") ? ( slider7 = 1; ui_auto(7); ); dy_lbl(562, 224, "SPEED", 0.6); dy_btn(650, 330, slider8, dy_grn, "NR") ? ( slider8 = 1 - slider8; ui_auto(8); ); dy_lbl(574, 338, speed ? "3 3/4 IPS" : "1 7/8 IPS", 0.7); dy_lbl(574, 352, "COMPANDER NR", 0.7); pk_end(); ui_end();