desc:GFX Bass Head //tags: amp amplifier bass valve tube cabinet di tuner //author: JGWizrad / Garden FX (GFX) // // Requires gfx-drift, gfx-input, gfx-output, gfx-ui, gfx-panel, gfx-dyn, gfx-faceplate, gfx-instrument, gfx-headsmooth and gfx-amp // (.jsfx-inc) in the same folder. // // Bass Head - a big all-valve bass amp and an 8x10 cabinet, in the spirit of the classic 300-watt heads // (personal use; the sound is designed from scratch). // GAIN - the preamp valve: clean and round low down, growling as it goes up. // ULTRA LO - deep low lift with a scooped midrange. ULTRA HI - a bright top-end lift. // BASS / TREBLE - shelves, +/-12 dB. MIDRANGE - +/-12 dB at 220 / 450 / 800 / 1.6k / 3k (FREQUENCY). // MASTER - how hard the push-pull power stage is driven: headroom, then sag and compression. // CAB / MIC - the 8x10 cabinet on or off (line out), and the mic from on axis (bright) to off axis (dark). // DI BLEND - the clean DI signal mixed under the amp. // GATE - a noise gate before the amp (fully down = off). TUNER - shows the note and mutes the output. // THEME colours the control panel, pinstripe and lamp; HW switches between the faceplate and the flat look. // The amp runs in mono (as a real amp) at twice the sample rate; both outputs carry it. // DRIFT: the valves' bias wanders slightly. The DRIFT dial scales it (50% = the drift amount / speed settings, // 100% = twice, 0 = none). // IN / OUT: TRIM (with the GUARD key), SAT and the OUT stage (OFF / CLIP / LIM) along the bottom of the head. 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 import gfx-headsmooth.jsfx-inc import gfx-amp.jsfx-inc slider1:4<0,10,0.1>-Gain slider2:0<0,1,1{Off,On}>-Ultra lo slider3:0<0,1,1{Off,On}>-Ultra hi slider4:0<-12,12,0.1>-Bass (dB) slider5:0<-12,12,0.1>-Midrange (dB) slider6:1<0,4,1{220 Hz,450 Hz,800 Hz,1.6 kHz,3 kHz}>-Midrange frequency slider7:0<-12,12,0.1>-Treble (dB) slider8:4<0,10,0.1>-Master slider9:-80<-80,-20,0.5>-Gate threshold (dB, -80 = off) slider10:1<0,1,1{Off,On}>-Cabinet slider11:3<0,10,0.1>-Mic position (on - off axis) slider12:0<0,100,1>-DI blend (%) slider13:0<-24,12,0.1>-Output (dB) slider14:0<0,1,1{Off,On}>-Tuner (mutes) slider15:0<-24,12,0.1>-Input trim (dB) slider16:0<0,1,1{Off,On}>-Input guard slider17:25<0,100,1>-Drift amount (%) slider18:0.25<0.02,2,0.01>-Drift speed (Hz) slider19:0<0,100,1>-Band sat (%) slider20:30<0,100,1>-Bias (%) slider21:120<40,400,1>-Sat low band (Hz) slider22:6000<2000,16000,10>-Sat high band (Hz) slider23:-0.3<-24,0,0.1>-Ceiling / limit threshold (dB) slider24:1<0,2,1{Off,Clip,Limit}>-Output stage // DRIFT dial: scales how far the valves' bias wanders (V1, V2 and the power stage) and how fast slider25: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-bass-head"; // the ? pop-up links to this manual // ---------- the amp core, run at twice the sample rate ---------- // output transformer: the core saturates on flux (integrate, saturate, differentiate) function xf_run(x, k) instance(ph, sp) local(s, y) ( ph = ph * xf_lk + x * xf_c; s = am_tanh(ph * k) / k; y = (s - sp * xf_lk) / xf_c; sp = s; y; ); function amp_core(x) local(y) ( y = ihp.am_run(x); y = am_tube(y * g1, b1); // V1 y = mil.am_run(y) * c1; y = tb.am_run(y); y = tm.am_run(y); y = tt.am_run(y); // tone stack y = ul1.am_run(y); y = ul2.am_run(y); y = uh.am_run(y); y = c2hp.am_run(y); y = am_tube(y * 2.5, b2) * 0.4; // V2 + cathode follower y = am_tube(y * 0.9, -0.04); y = phc.am_run(y) * pm; // into the power stage (MASTER) y = sup * am_tanh(y / sup); // push-pull, with supply sag sgenv += (abs(y) - sgenv) * (abs(y) > sgenv ? sg_att : sg_rel); sup = 1 - 0.3 * min(sgenv, 1); y = xfo.xf_run(y + pbias * y * y, 0.5); y * mk; ); function update() local(fs2, mf) ( fs2 = 2 * srate; g1 = 10 ^ ((slider1 * 4.2 - 6) / 20); c1 = 1 / sqrt(g1); // -6 .. +36 dB into V1 pm = 10 ^ ((slider8 * 3.4 - 15) / 20); mk = 0.55 / (pm ^ 0.55); // -15 .. +19 dB into the power stage mf = slider6 == 0 ? 220 : slider6 == 1 ? 450 : slider6 == 2 ? 800 : slider6 == 3 ? 1600 : 3000; ihp.am_bq(1, 35, 0.7, 0, fs2); mil.am_bq(0, 14000, 0.7, 0, fs2); tb.am_bq(4, 80, 0.7, slider4, fs2); tm.am_bq(2, mf, 0.9, slider5, fs2); tt.am_bq(3, 5000, 0.7, slider7, fs2); ul1.am_bq(4, 40, 0.8, slider2 ? 4 : 0, fs2); ul2.am_bq(2, 500, 0.8, slider2 ? -10 : 0, fs2); uh.am_bq(3, 8000, 0.7, slider3 ? 8 : 0, fs2); c2hp.am_bq(1, 20, 0.7, 0, fs2); phc.am_bq(1, 25, 0.7, 0, fs2); cab.am_cab_set(slider10 ? 1 : 0, slider11); dihp.am_bq(1, 30, 0.7, 0, srate); di_t = slider12 / 100; out_t = 10 ^ (slider13 / 20); drift_macro(slider25); drift = slider17 / 100 * dm_a; dspd = slider18 * dm_s; in_setup(slider15, slider16); ins_output_setup(19); ); xf_c = 2 * $pi * 40 / (2 * srate); xf_lk = 1 - 2 * $pi * 5 / (2 * srate); sg_att = 1 - exp(-1 / (0.005 * 2 * srate)); sg_rel = 1 - exp(-1 / (0.12 * 2 * srate)); sup = 1; sgenv = 0; up.am_aa(); dn.am_aa(); dB1.drift_init(0.8 + rand(0.5)); dB2.drift_init(0.8 + rand(0.5)); ost_init(20000); ostL.ost_ch_init(); ostR.ost_ch_init(); in_init(); sm = 1 - exp(-1 / (0.03 * srate)); mpk = 0; !ui_dflt ? ui_thm = 5; !ui_dflt ? ui_hw = 1; !ui_dflt ? ui_kstyle = 0; // defaults for new instances: Graphite (aluminium panel), hardware BH_TOL = 0x141416; update(); out_s = out_t; di_s = di_t; mute_s = 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(); dinc = dspd * samplesblock / srate; b1 = 0.15 + 0.06 * drift * dB1.drift_tick(dinc); b2 = 0.10 + 0.05 * drift * dB2.drift_tick(dinc); pbias = 0.02 * drift * dB2.v; @serialize file_var(0, ui_thm); file_var(0, ui_sz); file_var(0, ui_hw); @sample in_process(spl0, spl1); x = (in_l + in_r) * 0.5; slider14 ? am_tun_push(x); x *= gt.am_gate(x, slider9); // amp at 2x z = up.am_aa_run(x * 2); dn.am_aa_run(amp_core(z)); z = up.am_aa_run(0); y = dn.am_aa_run(amp_core(z)) * 2; y = cab.am_cab_run(y); di_s += (di_t - di_s) * sm; out_s += (out_t - out_s) * sm; mute_s += ((slider14 ? 0 : 1) - mute_s) * sm; y = (y * (1 - di_s) + dihp.am_run(x) * di_s) * out_s * mute_s; oL = ostL.ost_band(y); oR = ostR.ost_band(y); ost_out(oL, oR); spl0 = ost_l; spl1 = ost_r; mpk = max(mpk, abs(spl0)); @gfx 780 315 ui_begin(780, 315); ui_col(ui_hw ? 0x0B0C0D : C_BG); gfx_rect(0, 0, gfx_w, gfx_h); // colours from the theme: the panel takes its light tone, the pinstripe and lamp its accent // (Graphite keeps the aluminium panel with a blue stripe and lamp) BH_PNL = ui_hw ? C_S2 : C_HDR; BH_INK = ui_ink(BH_PNL); BH_ACC = ui_thm == 5 ? 0x3A8DFF : C_ACC; BH_TXT = ui_hw ? 0xD8DCE2 : C_TITLE; // a big control: the hardware knob, or the flat GFX knob function bh_big(cx, cy, idx, vmin, vmax, vdef, step, label, fmt) ( ui_hw ? ( pk_bkstep = step; pk_bigknob(cx * sc, cy * sc, 28 * sc, idx, vmin, vmax, vdef, 0, 0, 1, label, fmt); pk_bkstep = 0; ) : ( knob_step = step; ui_section(0); C_INK = BH_INK; ui_knob(cx * sc, cy * sc, idx, vmin, vmax, vdef, 0, label, fmt); ); ); // ---------- the head: tolex box, control panel ---------- function bh_tolex(x, y, w, h) local(i, px, py) ( ui_hw ? ( ui_col(0x050506); ui_rrs(x * sc, y * sc, w * sc, h * sc, 8 * sc); ui_col(BH_TOL); ui_rrs((x + 1) * sc, (y + 1) * sc, (w - 2) * sc, (h - 2) * sc, 7 * sc); i = 0; loop(900, // the grain px = x + 4 + ((i * 7919) % 997) / 997 * (w - 8); py = y + 4 + ((i * 104729) % 991) / 991 * (h - 8); ui_cola(i % 2 ? 0xFFFFFF : 0x000000, i % 2 ? 0.035 : 0.25); gfx_rect(px * sc, py * sc, 1 * sc + 1, 1 * sc + 1); i += 1; ); gfx_gradrect((x + 2) * sc, (y + 2) * sc, (w - 4) * sc, 30 * sc, 1, 1, 1, 0.06, 0, 0, 0, 0, 0, 0, 0, -0.06 / (30 * sc)); ) : ( ui_col(C_BG); gfx_rect(x * sc, y * sc, w * sc, h * sc); ); ); T = 8; TH = 298; bh_tolex(8, T, 764, TH); // name and the pilot lamp gfx_setfont(6, "Trebuchet MS", 22 * sc, 'bi'); ui_hw ? ( ui_cola(0x000000, 0.6); gfx_x = 33 * sc; gfx_y = (T + 11) * sc; gfx_drawstr("Bass Head"); ); ui_col(BH_TXT); gfx_x = 32 * sc; gfx_y = (T + 10) * sc; gfx_drawstr("Bass Head"); gfx_setfont(4); ui_cola(BH_TXT, 0.6); gfx_x += 10 * sc; gfx_y = (T + 20) * sc; gfx_drawstr("GFX ยท ALL VALVE 300"); pk_led(446 * sc, (T + 24) * sc, 5 * sc, 1, BH_ACC); ui_theme_picker(468 * sc, (T + 15) * sc); BH_PNL = ui_hw ? C_S2 : C_HDR; BH_INK = ui_ink(BH_PNL); BH_ACC = ui_thm == 5 ? 0x3A8DFF : C_ACC; BH_TXT = ui_hw ? 0xD8DCE2 : C_TITLE; ui_hw_toggle(496 * sc, (T + 15) * sc); ui_size_picker(560 * sc, (T + 15) * sc); // control panel px0 = 20; py0 = T + 46; pw = 740; ph = 150; ui_hw ? ( ui_col(0x2A2C30); ui_rrs(px0 * sc, py0 * sc, pw * sc, ph * sc, 4 * sc); ui_col(BH_PNL); ui_rrs((px0 + 1) * sc, (py0 + 1) * sc, (pw - 2) * sc, (ph - 2) * sc, 3 * sc); gi = 0; loop(floor((ph - 4) / 2), ui_cola(gi % 3 ? 0xFFFFFF : 0x000000, gi % 3 ? 0.06 : 0.03); gfx_line((px0 + 2) * sc, (py0 + 2 + gi * 2) * sc, (px0 + pw - 3) * sc, (py0 + 2 + gi * 2) * sc); gi += 1; ); ui_col(ui_mix(BH_ACC, 0x000000, 0.35)); gfx_rect((px0 + 2) * sc, (py0 + ph - 9) * sc, (pw - 4) * sc, 3 * sc); // pinstripe pk_screw((px0 + 10) * sc, (py0 + 12) * sc); pk_screw((px0 + pw - 10) * sc, (py0 + 12) * sc); ) : ( ui_col(BH_PNL); ui_rrect(px0 * sc, py0 * sc, pw * sc, ph * sc, 5 * sc); ui_col(C_ACC); gfx_rect(px0 * sc, (py0 + 8) * sc, 3 * sc, (ph - 16) * sc); ); pk_ink = BH_INK; pk_bg = BH_PNL; pk_bkfs = 11; pk_nonum = 1; pk_ptr = 0xEDE3C8; pk_kstyle = 8; // skirted amp knobs ky = py0 + 66; bh_big(80, ky, 1, 0, 10, 4, 0, "GAIN", "%.1f"); ui_keycap(134 * sc, (py0 + 34) * sc, 70 * sc, 22 * sc, "ULTRA LO", slider2, 1) ? ( slider2 = 1 - slider2; slider_automate(2 ^ 1); ); ui_keycap(134 * sc, (py0 + 64) * sc, 70 * sc, 22 * sc, "ULTRA HI", slider3, 1) ? ( slider3 = 1 - slider3; slider_automate(2 ^ 2); ); bh_big(260, ky, 4, -12, 12, 0, 0.5, "BASS", "%+.1f dB"); bh_big(350, ky, 5, -12, 12, 0, 0.5, "MIDRANGE", "%+.1f dB"); ui_hw ? pk_step(440, ky, 6, 5, "220|450|800|1.6k|3k", "FREQUENCY") : ( knob_step = 1; ui_section(0); C_INK = BH_INK; ui_knob(440 * sc, ky * sc, 6, 0, 4, 1, 0, "FREQ", slider6 == 0 ? "220 Hz" : slider6 == 1 ? "450 Hz" : slider6 == 2 ? "800 Hz" : slider6 == 3 ? "1.6 kHz" : "3 kHz"); ); bh_big(530, ky, 7, -12, 12, 0, 0.5, "TREBLE", "%+.1f dB"); bh_big(640, ky, 8, 0, 10, 4, 0, "MASTER", "%.1f"); pk_nonum = 0; pk_bkfs = 15; pk_ptr = -1; pk_kstyle = 0; // lower strip: GATE | TUNER | CAB + MIC | DI BLEND | OUTPUT + meter ly = T + 246; ui_section(2); C_INK = BH_TXT; ui_knonum = 2; ui_knob(60 * sc, ly * sc, 9, -80, -20, -80, 0, "GATE", slider9 <= -79.5 ? "OFF" : "%.0f dB"); ui_keycap(108 * sc, (ly - 22) * sc, 56 * sc, 22 * sc, "TUNER", slider14, 1) ? ( slider14 = 1 - slider14; slider_automate(2 ^ 13); ); slider14 ? am_tun_draw(172 * sc, (ly - 24) * sc, 130 * sc, 44 * sc, 0xD8DCE2, 0xFFB030) : ( ui_col(0x0A0B0C); ui_rrect(172 * sc, (ly - 24) * sc, 130 * sc, 44 * sc, 3 * sc); gfx_setfont(4); ui_cola(0xD8DCE2, 0.3); ui_text(237 * sc, (ly - 7) * sc, "TUNER OFF"); ); ui_keycap(326 * sc, (ly - 22) * sc, 56 * sc, 22 * sc, "CAB", slider10, 1) ? ( slider10 = 1 - slider10; slider_automate(2 ^ 9); ); ui_knob(410 * sc, ly * sc, 11, 0, 10, 3, 0, "MIC", "%.1f"); ui_knob(490 * sc, ly * sc, 12, 0, 100, 0, 0, "DI BLEND", "%.0f%%"); ui_knob(570 * sc, ly * sc, 13, -24, 12, 0, 0, "OUTPUT", "%+.1f dB"); ui_knonum = 0; bh_lv = max(mpk, bh_lv * 0.8); mpk = 0; hs_leds(690, ly - 4, bh_lv, BH_TXT); // bottom row: DRIFT | IN / OUT // by = T + 294; kc = by + (ui_hw ? 40 : 48); // the flat knobs carry their name above // pk_box(226 * sc, by * sc, 84 * sc, 90 * sc, BH_TXT, "DRIFT"); // pk_box(322 * sc, by * sc, 232 * sc, 90 * sc, BH_TXT, "IN / OUT"); // ui_section(2); C_INK = BH_TXT; ui_knonum = 2; // ui_knob(268 * sc, kc * sc, 25, 0, 100, 50, 0, "DRIFT", "%.0f%%"); // ui_io(338, kc - 30, 15, 16, 19, 24); // ui_knonum = 0; pk_end(); ui_end();