Last active
August 29, 2015 14:04
-
-
Save seutje/801b5c4137993176fa94 to your computer and use it in GitHub Desktop.
Techwobbles
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* | |
* stagas - mind swift (seutje techwobbles mix) | |
* | |
*/ | |
var bpm = 130; | |
var tuning = 440; | |
var transpose = 11; | |
var hardcodedTimer = 7.4; | |
var sampleRate = 44100; | |
// constants | |
var tau = 2 * Math.PI; | |
// time coefficients | |
var t, tt; | |
function Oscillator(type, size, alias) { | |
if (!(this instanceof Oscillator)) return new Oscillator(type, size, alias); | |
this.pos = 0; | |
this.size = size || sampleRate; | |
this.coeff = this.size / sampleRate; | |
this.table = new Float32Array(this.size); | |
this.alias = alias === false ? false : true; | |
this.build(type); | |
} | |
Oscillator.prototype.build = function(type) { | |
switch (type) { | |
case 'sin': | |
var scale = 2 * Math.PI / this.size; | |
for (var i = 0; i < this.size; i++) { | |
this.table[i] = Math.sin(i * scale); | |
} | |
break; | |
case 'saw': | |
for (var i = 0; i < this.size; i++) { | |
var x = (i / this.size); | |
this.table[i] = +2.0 * (x - Math.round(x)); | |
} | |
break; | |
case 'ramp': | |
for (var i = 0; i < this.size; i++) { | |
var x = (i / this.size); | |
this.table[i] = -2.0 * (x - Math.round(x)); | |
} | |
break; | |
case 'tri': | |
for (var i = 0; i < this.size; i++) { | |
var x = (i / this.size) - 0.25; | |
this.table[i] = 1.0 - 4.0 * Math.abs(Math.round(x) - x); | |
} | |
break; | |
case 'sqr': | |
var half = this.size / 2; | |
for (var i = 0; i < this.size; i++) { | |
this.table[i] = i < half ? +1 : -1; | |
} | |
break; | |
} | |
}; | |
Oscillator.prototype.play = function(freq) { | |
this.pos += freq * this.coeff; | |
if (this.pos >= this.size) this.pos -= this.size; | |
this.index = this.pos | 0; | |
if (!this.alias) return this.table[this.index]; | |
this.alpha = this.pos - this.index; | |
this.next = this.table[this.index == this.size - 1 ? 0 : this.index + 1]; | |
this.curr = this.table[this.index]; | |
return this.curr + (this.next - this.curr) * this.alpha; | |
}; | |
function clock(_t) { | |
t = _t; | |
t *= bpm / 120; | |
tt = tau * t; | |
} | |
function octave(o) { | |
return function(n) { | |
return n * o; | |
}; | |
} | |
function slide(measure, seq, speed) { | |
var pos = (t / measure / 2) % seq.length; | |
var now = pos | 0; | |
var next = now + 1; | |
var alpha = pos - now; | |
if (next == seq.length) next = 0; | |
return seq[now] + ((seq[next] - seq[now]) * Math.pow(alpha, speed)); | |
} | |
function sequence(measure, seq) { | |
return seq[(t / measure / 2 | 0) % seq.length]; | |
} | |
function arp(measure, x, y, z) { | |
var ts = t / 2 % measure; | |
return Math.sin(x * (Math.exp(-ts * y))) * Math.exp(-ts * z); | |
} | |
function sin(freq, phase) { | |
return Math.sin((t * freq + (2 - (phase || 0) / 2)) * tau); | |
} | |
function saw(freq) { | |
return 1 - 2 * (t % (1 / freq)) * freq; | |
} | |
function tri(freq) { | |
return Math.abs(1 - (2 * t * freq) % 2) * 2 - 1; | |
} | |
function sqr(freq) { | |
return sin(freq, t) > 0 ? 1 : -1; | |
} | |
function Noise() { | |
return Math.random() * 2 - 1; | |
} | |
function note(n, octave) { | |
return Math.pow(2, ( | |
n + transpose - 33 + (12 * (octave || 0))) / 12) * tuning; // A4 tuning | |
} | |
function clip(x) { | |
return x / (1 + Math.abs(x)); | |
} | |
function DiodeFilter() { | |
this.k = 0; | |
this.A = 0; | |
this.z = [0, 0, 0, 0, 0]; | |
this.ah; | |
this.bh; | |
this.fc; | |
this.set_q(0); | |
this.set_hpf(0.5); | |
this.set_fc(.5); | |
} | |
DiodeFilter.prototype.set_hpf = function(fc) { | |
var K = fc * Math.PI; | |
this.ah = (K - 2) / (K + 2); | |
this.bh = 2 / (K + 2); | |
}; | |
DiodeFilter.prototype.reset = function() { | |
if (this.k < 17) this.z = [0, 0, 0, 0, 0]; | |
}; | |
DiodeFilter.prototype.set_q = function(q) { | |
this.k = 20 * q; | |
this.A = 1 + 0.5 * this.k; | |
}; | |
DiodeFilter.prototype.set_fc = function(cutoff) { | |
cutoff = (cutoff * cutoff); | |
this.fc = cutoff <= 0 ? .02 : (cutoff >= 1.0 ? .999 : cutoff); | |
}; | |
DiodeFilter.prototype.run = function(x) { | |
var a = Math.PI * this.fc; | |
a = 2 * Math.tan(0.5 * a); // dewarping, not required with 2x oversampling | |
var ainv = 1 / a; | |
var a2 = a * a; | |
var b = 2 * a + 1; | |
var b2 = b * b; | |
var c = 1 / (2 * a2 * a2 - 4 * a2 * b2 + b2 * b2); | |
var g0 = 2 * a2 * a2 * c; | |
var g = g0 * this.bh; | |
// current state | |
var s0 = (a2 * a * this.z[0] + a2 * b * this.z[1] + this.z[2] * (b2 - 2 * a2) * a + this.z[3] * (b2 - 3 * a2) * b) * c; | |
var s = this.bh * s0 - this.z[4]; | |
// solve feedback loop (linear) | |
var y5 = (g * x + s) / (1 + g * this.k); | |
// input clipping | |
var y0 = clip(x - this.k * y5); | |
y5 = g * y0 + s; | |
// compute integrator outputs | |
var y4 = g0 * y0 + s0; | |
var y3 = (b * y4 - this.z[3]) * ainv; | |
var y2 = (b * y3 - a * y4 - this.z[2]) * ainv; | |
var y1 = (b * y2 - a * y3 - this.z[1]) * ainv; | |
// update filter state | |
this.z[0] += 4 * a * (y0 - y1 + y2); | |
this.z[1] += 2 * a * (y1 - 2 * y2 + y3); | |
this.z[2] += 2 * a * (y2 - 2 * y3 + y4); | |
this.z[3] += 2 * a * (y3 - 2 * y4); | |
this.z[4] = this.bh * y4 + this.ah * y5; | |
return this.A * y4; | |
}; | |
// patterns | |
var hat_pattern = [ | |
0,1, .5, 1, 0,1, .5, 1 ]; | |
var crash_pattern = [ | |
2, 1, .5, .25, 2, 1, .5, .25]; | |
var melody = [2, 9, 5, 8, 2, 9, 5, 2].map(function(n) { | |
return note(n, 2); | |
}); | |
melody = melody.concat(melody.slice().reverse()); | |
var hat_note = note(16, 6); | |
var bass_osc = Oscillator('sin', 512); | |
var bass2_osc = Oscillator('sin', 512); | |
var bass3_osc = Oscillator('sin', 512); | |
var osc = Oscillator('tri', 512); | |
var osc2 = Oscillator('tri', 512); | |
var hat_osc = Oscillator('ramp', 512); | |
var filter = new DiodeFilter(); | |
var filter2 = new DiodeFilter(); | |
filter.set_q(0.23); | |
filter.set_hpf(.0011); | |
function dsp(t) { | |
clock(t); | |
var noise = Noise(); | |
var n = slide(1 / 4, melody, 32); | |
var synth_osc = osc.play(n + tri(24) * 2) + tri(n / 10000); | |
var synth_osc2 = osc2.play(n + tri(24) * 2) + tri(n / 10000); | |
var synth = arp(1 / 16, synth_osc, 1000, 1); | |
var synth2 = arp(1 / 8, synth_osc, 100, 1); | |
var synth3 = arp(1 / n, synth_osc2, 100, 1); | |
filter.set_fc(0.5 + (tri(1 / 2) * 0.02)); | |
var b = slide(1 / 8, melody, 32); | |
var bass = bass_osc.play(b / 8); | |
bass = filter.run(bass * 2); | |
bass = clip(bass * 10); | |
bass = bass * (0.8 - ((tri(8) * 0.5))); | |
var bass2 = bass2_osc.play(b / 16); | |
bass2 = filter2.run(bass2 * 1); | |
bass2 = clip(bass2 * 2); | |
bass2 = bass2 * (.8 - ((tri(4) * .5))); | |
var bass3 = bass3_osc.play(b / 4); | |
bass3 = filter2.run(bass3 * 1); | |
bass3 = clip(bass3 * n); | |
bass3 = bass3 * (.8 - ((tri(8) * .5))); | |
var kick = arp(1 / 4, 50, 40, 0); | |
var kick2 = arp(1 / 4, 80, 80, 0); | |
var hat = sequence(1 / 8, hat_pattern) * arp(1 / 8, hat_osc.play(hat_note) + noise * 1.8, 2, 70); | |
var crash = sequence(1 / 16, crash_pattern) * arp(1 / 8, hat_osc.play(hat_note) + noise * 2, 20, 20); | |
var gotBass = (t - (hardcodedTimer * 2)) / hardcodedTimer; // start fading in the bass after we've been around twice | |
if (gotBass < 0) gotBass = 0; | |
if (gotBass > 1) gotBass = 1; | |
var gotKick = 0; | |
var gotSynth = 1; | |
var gotSynth2 = 0; | |
var gotSynth3 = (t - (hardcodedTimer * 4)) / hardcodedTimer; // Fade in extra crunch after 4 times | |
var gotHat = 0; // Fade in extra crunch after 4 times | |
if (t > hardcodedTimer) gotKick = 1; // wait once and add the kick | |
if (t > hardcodedTimer) gotSynth2 = 1; // wait once and add second synth track | |
if (t > hardcodedTimer) gotSynth = 0; // wait once and add second synth track | |
if (t > hardcodedTimer) gotHat = 1; // wait once and add second synth track | |
if (gotSynth3 < 0) gotSynth3 = 0; | |
if (gotSynth3 > 1) gotSynth3 = 1; | |
if (t > (hardcodedTimer * 6)) { // after 6 times, start alternating the tap and the crunch in the synth | |
if (Math.floor(t) % 2 === 0 ) { | |
gotSynth3 = (t%2)/2; | |
} | |
else { | |
gotSynth3 = 1-(t%2)/2; | |
} | |
} | |
return 0.5 * ( | |
2 * (synth2 * gotSynth2) | |
+ 1 * (synth3 * gotSynth3) | |
+ 2 * (synth * gotSynth) | |
+ 3 * (kick * gotKick) | |
// + 2 * (kick2 * gotKick) | |
+ 0.35 * (hat * gotHat) | |
+ .5 * (bass * gotBass) | |
+ 2 * (bass2 * gotBass) | |
+ .2 * (bass3 * gotBass) | |
+ .10 * (crash * gotBass) | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment