Play around with artificial electric guitar sounds!
Most of this is based on Hysteria by MUSE
Log in to post a comment.
/* Constants */ const sin = Math.sin; const TAU = 2*Math.PI; ditty.bpm = 94; // start position in song START = 0; /* Filters */ // clamp11 and amp from https://dittytoy.net/ditty/989ebdaad7 let clamp11 = (x) => x < -1 ? -1 : x > 1 ? 1 : x; /* Overdrive just .. overdrives the amp, higher values result in clipping, adding fuzz and dirt to the sound. */ input.overdrive = 47; // min=1, max=100, step=0.1 const amp = filter.def(class { constructor(options) { this.amp = options.amp || 0.5; // gain > 1 = overdrive this.gain = options.gain || 1.0; } process(inpt, options) { return [clamp11(input.overdrive*inpt[0]) * this.amp, clamp11(input.overdrive*inpt[1]) * this.amp]; } }); /* Each coeff[n]x is a multiplicator applied to the base note and the corresponding coeff[n]y is the gain of this particular partial tone. Coefficients are preconfigured to yield a nice sounding virtual electric guitar. */ input.coeff1x = 1; // min=1, max=10, step=0.01 input.coeff1y = 0.5; // min=0, max=1, step=0.01 input.coeff2x = 2.01; // min=1, max=10, step=0.01 input.coeff2y = 0.25; // min=0, max=1, step=0.01 input.coeff3x = 3.02; // min=1, max=10, step=0.01 input.coeff3y = 0.125; // min=0, max=1, step=0.01 input.coeff4x = 4.03; // min=1, max=10, step=0.01 input.coeff4y = 0.06; // min=0, max=1, step=0.01 /* Feedback controls how much of the previous sample will be added to the current one, higher values eventually lead to clipping and noisier sounds. */ input.feedback = 3; // min=1, max=10, step=0.01 /* The guitar sound is basically constructed from a couple of carefully chosen frequency-modulated sine waves (i.e. FM synthesis) which are added onto each other with a built-in feedback loop as one way to oversaturate the signal. */ const Strat = synth.def( class { constructor (options) { this.t = Math.random(); this.i = 0; this.mul = options.mul; this.bend = options.bend || 0; this.coeff = [ [input.coeff1x, input.coeff1y], [input.coeff2x, input.coeff2y], [input.coeff3x, input.coeff3y], [input.coeff4x, input.coeff4y], ]; this.feedback = input.feedback || 0; this.last = 0; } add_fm(note, env, tick) { // carrier frequency const fc = midi_to_hz(note); let x = 0; const fb = this.feedback * this.last; this.coeff.forEach(y => { let fm = fc * y[0] * this.mul; let mod = sin(TAU*this.t*fm + fb) * (Math.exp(tick * -16)*0.4+1); x += sin(TAU*this.t*fc*y[0] + mod) * y[1]; }); this.last = x; return (x / this.coeff.length) * env.value; } process(note, env, tick, options) { // forward time this.t += ditty.dt; this.i += ditty.dt; const gain = 0.5; let osc = this.add_fm(note, env, tick) * gain; osc *= env.value; return [osc, osc]; // left, right } }); input.wide = 0.8; // min=0, max=1, step=0.01 const AMP = 0.5; const DUR = 0.025; const wide = filter.def(class { constructor(options) { this.size = 2* options.delay || 256; this.hist = new Float32Array(this.size); this.write = options.delay; this.read = 0 } process(input, options) { if (input) { // save current sample const x = input[0]; this.hist[this.write++ % this.size] = x // add history to current const wide = options.inp.wide; const L = x + this.hist[this.read % this.size]; const R = x + this.hist[(options.delay + this.read) % this.size]; this.read++; const L_out = L; const R_out = wide*R + (1.0-wide)*L; debug.probe( "L", L_out, AMP, DUR); debug.probe( "R", R_out, AMP, DUR); return[L_out, R_out]; } } }, { inp: input }); /* Building blocks */ class Track { constructor(options) { this.patterns = options.patterns; this.pos = options.start || 0; this.base = options.base || 0; this.read = true; this.duration = 0.25; this.stop = false; } process(lc) { this.sleep = true; if (this.read) { if (this.stop) { sleep(1); return; } this.read = false; this.part = this.patterns[this.pos++]; if (this.pos==this.patterns.length) this.pos = 0; //this.stop = true; this.i = 0; } let x = this.part[this.i++]; if (typeof x === 'number') { if (x > 24) { this.base = x; x = this.part[this.i++]; } if (x < 0) { this.duration = -x; x = this.part[this.i++]; } if (typeof x === 'number') x += this.base; } if (this.i==this.part.length) { this.read = true; } this.play(x); if (this.sleep) sleep(this.duration); // sleep in ticks } } class ElectricGuitar extends Track { constructor(options) { super(options); this.mul = options.mul || 0.503; this.pan = options.pan || 0; this.transpose = options.transpose || 0; } strum(note, a, d, r) { Strat.play(note + this.transpose, { attack: a || 0.01, duration: d || this.duration, release: r || 0.15, pan: this.pan, amp: 1, mul: this.mul }); } chord(notes) { let string = 0; for (let x of notes) { if (x > 0) { this.strum(this.base + string + x); } else if (x == 0) { // palm mute strings this.strum(this.base + string, 0.01, 0.05, 0.05); } string += 5; } } play(note) { if (note === null) return; // pause if (Array.isArray(note)) { this.chord(note); } else if (note > 0) { this.strum(note); } // sleep is done in the Track class! } } /* Main guitar */ const gp17 = [ e3,-1, [5,7,7], a3,-0.25, 10,12,0,10, 0,7,0,8, 8,7,5,7 ]; const gp18 = [ e3,-0.25, 0,0,10,0, 10,12,0,15, 0,12,0,15, 15,12,15,a3,12 ]; const gp19 = [ d4,-0.25,0,0,10,0, 10,12,0,10, 0,10,9,0, 9,8,0,8 ]; const gp20 = [ d3,-0.25,7,a3,0,10,0, 10,12,0,10, 0,7,0,8, 8,7,5,7 ]; const main_guitar = [ gp17,gp18,gp19,gp20, ]; loop(ElectricGuitar, { name: 'Main guitar', patterns: main_guitar, start: START, amp: 1.2, pan: 0, mul: 0.495, feedback: 3 }) .connect(wide.create({ delay: 2000 })) .connect(amp.create({ gain: 47.456 }));