diff --git a/clap.ogg b/clap.ogg new file mode 100644 index 0000000..d19b91f Binary files /dev/null and b/clap.ogg differ diff --git a/demo.html b/demo.html new file mode 100644 index 0000000..fa4bab4 --- /dev/null +++ b/demo.html @@ -0,0 +1 @@ + diff --git a/index.html b/index.html index 7a29fc9..cfb2862 100644 --- a/index.html +++ b/index.html @@ -34,7 +34,12 @@

litsynth

-

Introduction and scope

+ +

Introduction and scope

diff --git a/litsynth.js b/litsynth.js new file mode 100644 index 0000000..d05970e --- /dev/null +++ b/litsynth.js @@ -0,0 +1,164 @@ +function note2freq(note) { + return Math.pow(2, (note - 69) / 12) * 440; +} +function S(ac, clap, track) { + this.ac = ac; + this.clap = clap; + this.track = track; + this.rev = ac.createConvolver(); + this.rev.buffer = this.ReverbBuffer(); + this.sink = ac.createGain(); + this.sink.connect(this.rev); + this.rev.connect(ac.destination); + this.sink.connect(ac.destination); +} +S.prototype.NoiseBuffer = function() { + if (!S._NoiseBuffer) { + S._NoiseBuffer = this.ac.createBuffer(1, this.ac.sampleRate / 10, this.ac.sampleRate); + var cd = S._NoiseBuffer.getChannelData(0); + for (var i = 0; i < cd.length; i++) { + cd[i] = Math.random() * 2 - 1; + } + } + return S._NoiseBuffer; +} +S.prototype.ReverbBuffer = function() { + var len = 0.5 * this.ac.sampleRate, + decay = 0.5; + var buf = this.ac.createBuffer(2, len, this.ac.sampleRate); + for (var c = 0; c < 2; c++) { + var channelData = buf.getChannelData(c); + for (var i = 0; i < channelData.length; i++) { + channelData[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / len, decay); + } + } + return buf; +} +S.prototype.Kick = function(t) { + var o = this.ac.createOscillator(); + var g = this.ac.createGain(); + o.connect(g); + g.connect(this.sink); + g.gain.setValueAtTime(1.0, t); + g.gain.setTargetAtTime(0.0, t, 0.1); + o.frequency.value = 100; + o.frequency.setTargetAtTime(30, t, 0.15); + o.start(t); + o.stop(t + 1); + var osc2 = this.ac.createOscillator(); + var gain2 = this.ac.createGain(); + osc2.frequency.value = 40; + osc2.type = "square"; + osc2.connect(gain2); + gain2.connect(this.sink); + gain2.gain.setValueAtTime(0.5, t); + gain2.gain.setTargetAtTime(0.0, t, 0.01); + osc2.start(t); + osc2.stop(t + 1); +} +S.prototype.Hats = function(t) { + var s = this.ac.createBufferSource(); + s.buffer = this.NoiseBuffer(); + var g = this.ac.createGain(); + var hpf = this.ac.createBiquadFilter(); + hpf.type = "highpass"; + hpf.frequency.value = 5000; + g.gain.setValueAtTime(1.0, t); + g.gain.setTargetAtTime(0.0, t, 0.02); + s.connect(g); + g.connect(hpf); + hpf.connect(this.sink); + s.start(t); +} +S.prototype.Clap = function(t) { + var s = this.ac.createBufferSource(); + var g = this.ac.createGain(); + s.buffer = this.clap; + s.connect(g); + g.connect(this.sink); + g.gain.value = 0.5; + s.start(t); +} +S.prototype.Bass = function(t, note) { + var o = this.ac.createOscillator(); + var o2 = this.ac.createOscillator(); + var g = this.ac.createGain(); + var g2 = this.ac.createGain(); + o.frequency.value = o2.frequency.value = note2freq(note); + o.type = o2.type = "sawtooth"; + g.gain.setValueAtTime(1.0, t); + g.gain.setTargetAtTime(0.0, t, 0.1); + g2.gain.value = 0.5; + var lp = this.ac.createBiquadFilter(); + lp.Q.value = 25; + lp.frequency.setValueAtTime(300, t); + lp.frequency.setTargetAtTime(3000, t, 0.05); + o.connect(g); + o2.connect(g); + g.connect(lp); + lp.connect(g2); + g2.connect(this.sink); + o.start(t); + o.stop(t + 1); +} +S.prototype.clock = function() { + var beatLen = 60 / this.track.tempo; + return (this.ac.currentTime - this.startTime) / beatLen; +} +S.prototype.start = function() { + this.startTime = this.ac.currentTime; + this.nextScheduling = 0; + this.scheduler(); +} +S.prototype.scheduler = function() { + var beatLen = 60 / this.track.tempo; + var current = this.clock(); + var lookahead = 0.5; + if (current + lookahead > this.nextScheduling) { + var steps = []; + for (var i = 0; i < 4; i++) { + steps.push(this.nextScheduling + i * beatLen / 4); + } + for (var i in this.track.tracks) { + for (var j = 0; j < steps.length; j++) { + var idx = Math.round(steps[j] / ((beatLen / 4))); + var note = this.track.tracks[i][idx % this.track.tracks[i].length]; + if (note != 0) { + this[i](steps[j], note); + } + } + } + this.nextScheduling += (60 / this.track.tempo); + } + setTimeout(this.scheduler.bind(this), 100); +} +var track = { + tempo: 135, + tracks: { + Kick: [ 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0], + Hats: [ 0, 0, 1, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 1, 1, + 0, 0, 1, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 1, 0 ], + Clap: [ 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0], + Bass: [36, 0,38,36,36,38,41, 0, + 36,60,36, 0,39, 0,48, 0, + 36, 0,24,60,40,40,24,24, + 36,60,36, 0,39, 0,48, 0 ] + } +}; +fetch('clap.ogg').then((response) => { + response.arrayBuffer().then((arraybuffer) => { + var ac = new AudioContext(); + ac.decodeAudioData(arraybuffer).then((clap) => { + var s = new S(ac, clap, track); + s.start(); + }); + }); +});