desc:GFX Guitar Head //tags: amp amplifier guitar valve tube cabinet reverb spring 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. // // Guitar Head - an all-valve guitar amp with spring reverb and a 2x12 or 4x12 cabinet, in the spirit of the // classic 50-watt heads (personal use; the sound is designed from scratch). // GAIN - the preamp. CLEAN: one valve, sparkly to edge-of-breakup. DRIVE: a second cascaded valve. // BRIGHT - a treble lift ahead of the gain, strongest at low gain (as the real bright cap). // BASS / MIDDLE / TREBLE - the tone stack, 0..10 (5 = flat). // PRESENCE - the top end after the power stage, 0..10. // REVERB - a spring tank between the preamp and the power stage. // MASTER - how hard the EL34-style power stage is driven: headroom, then sag and crunch. // CAB / MIC - off (line out), 2x12 open back or 4x12 closed back, and the mic from on axis to off axis. // 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:5<0,10,0.1>-Gain slider2:0<0,1,1{Off,On}>-Bright slider3:0<0,1,1{Clean,Drive}>-Channel slider4:5<0,10,0.1>-Bass slider5:5<0,10,0.1>-Middle slider6:5<0,10,0.1>-Treble slider7:5<0,10,0.1>-Presence slider8:4<0,10,0.1>-Master slider9:2<0,10,0.1>-Reverb slider10:-80<-80,-20,0.5>-Gate threshold (dB, -80 = off) slider11:1<0,2,1{Off,2x12 open,4x12 closed}>-Cabinet slider12:3<0,10,0.1>-Mic position (on - off axis) 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 (preamp valves 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-guitar-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 = brt.am_run(y); // bright cap y = am_tube(y * g1, b1); // V1 y = mil.am_run(y); slider3 ? ( // DRIVE: a second cascaded valve y = d2hp.am_run(y); y = d2lp.am_run(y); y = am_tube(y * g2, b2) * c2; ) : y *= c1; y = tb.am_run(y); y = tm.am_run(y); y = tt.am_run(y); // tone stack y += rv.am_spring_run(y, 0.5) * rvw; // spring reverb y = am_tube(y * 0.9, -0.04); // phase inverter y = phc.am_run(y) * pm; // into the power stage (MASTER) y = sup * am_tube(y / sup, 0.22); // EL34-style push-pull: asymmetric, with supply sag sgenv += (abs(y) - sgenv) * (abs(y) > sgenv ? sg_att : sg_rel); sup = 1 - 0.35 * min(sgenv, 1); y = pres.am_run(y); // presence (after the power stage) y = xfo.xf_run(y + pbias * y * y, 0.6); y * mk; ); function update() local(fs2) ( fs2 = 2 * srate; g1 = 10 ^ ((slider1 * 3.4 - 4) / 20); c1 = 1 / sqrt(g1); // -4 .. +30 dB into V1 g2 = 10 ^ ((slider1 * 1.4 + 6) / 20); c2 = 0.32 * max(1, 2.2 - slider1 * 0.3); // +6 .. +20 dB into V2 (DRIVE) pm = 10 ^ ((slider8 * 3.2 - 14) / 20); mk = 0.5 / (pm ^ 0.55); // -14 .. +18 dB into the power stage ihp.am_bq(1, 70, 0.7, 0, fs2); brt.am_bq(3, 2500, 0.6, slider2 ? 9 * (1 - slider1 / 13) : 0, fs2); mil.am_bq(0, slider3 ? 9000 : 12000, 0.7, 0, fs2); d2hp.am_bq(1, 160, 0.7, 0, fs2); // tight low end into the second valve d2lp.am_bq(0, 6500, 0.7, 0, fs2); tb.am_bq(4, 110, 0.7, (slider4 - 5) * 2.4, fs2); tm.am_bq(2, 700, 0.8, (slider5 - 5) * 2.4 - 2, fs2); tt.am_bq(3, 3200, 0.7, (slider6 - 5) * 2.4, fs2); pres.am_bq(3, 4500, 0.7, slider7 * 1.1 - 3, fs2); phc.am_bq(1, 40, 0.7, 0, fs2); rvw = (slider9 / 10) ^ 1.5 * 0.9; cab.am_cab_set(slider11 == 1 ? 2 : slider11 == 2 ? 3 : 0, slider12); 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.004 * 2 * srate)); sg_rel = 1 - exp(-1 / (0.09 * 2 * srate)); sup = 1; sgenv = 0; up.am_aa(); dn.am_aa(); rv.am_spring_init(200000, 2 * srate); 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; 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.20 + 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, slider10); // 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); out_s += (out_t - out_s) * sm; mute_s += ((slider14 ? 0 : 1) - mute_s) * sm; y *= 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 red stripe and lamp) BH_PNL = ui_hw ? C_S2 : C_HDR; BH_INK = ui_ink(BH_PNL); BH_ACC = ui_thm == 5 ? 0xFF3B30 : 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("Guitar Head"); ); ui_col(BH_TXT); gfx_x = 32 * sc; gfx_y = (T + 10) * sc; gfx_drawstr("Guitar Head"); gfx_setfont(4); ui_cola(BH_TXT, 0.6); gfx_x += 10 * sc; gfx_y = (T + 20) * sc; gfx_drawstr("GFX ยท ALL VALVE 50"); 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 ? 0xFF3B30 : 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(76, ky, 1, 0, 10, 5, 0, "GAIN", "%.1f"); ui_keycap(128 * sc, (py0 + 34) * sc, 64 * sc, 22 * sc, "BRIGHT", slider2, 1) ? ( slider2 = 1 - slider2; slider_automate(2 ^ 1); ); ui_keycap(128 * sc, (py0 + 64) * sc, 64 * sc, 22 * sc, "DRIVE", slider3, 1) ? ( slider3 = 1 - slider3; slider_automate(2 ^ 2); ); bh_big(250, ky, 4, 0, 10, 5, 0, "BASS", "%.1f"); bh_big(328, ky, 5, 0, 10, 5, 0, "MIDDLE", "%.1f"); bh_big(406, ky, 6, 0, 10, 5, 0, "TREBLE", "%.1f"); bh_big(484, ky, 7, 0, 10, 5, 0, "PRESENCE", "%.1f"); bh_big(580, ky, 9, 0, 10, 2, 0, "REVERB", "%.1f"); bh_big(676, 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 | OUTPUT + meter ly = T + 246; ui_section(2); C_INK = BH_TXT; ui_knonum = 2; ui_knob(60 * sc, ly * sc, 10, -80, -20, -80, 0, "GATE", slider10 <= -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"); ); knob_step = 1; ui_knob(370 * sc, ly * sc, 11, 0, 2, 1, 0, "CAB", slider11 == 0 ? "OFF" : slider11 == 1 ? "2x12 OPEN" : "4x12"); ui_knob(450 * sc, ly * sc, 12, 0, 10, 3, 0, "MIC", "%.1f"); ui_knob(560 * 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();