var bufferSize = 4096; // 65536 / 2; var sampleRate = 44100; var latency = 1000 * bufferSize / sampleRate; var audioletReady = false; var samples = [ 'KickDrum0001.wav', 'Clap.wav', 'Closed Hihat0001.wav', 'Open Hihat0001.wav', 'SnareDrum0001.wav', 'Clav.wav', 'Mid Tom0001.wav', 'Rimshot.wav', ]; function Sampler (audiolet, url) { var base = this; this.audiolet = audiolet; // first get the sample and load it into a buffer this.buffer = new AudioletBuffer(1, 0); this.buffer.load(url, true, function(){ // connect the buffer to a player and set up the objects we need // TriggerControl: [initial trigger state = 0] base.trigger = new TriggerControl(base.audiolet, 0); // BufferPlayer: [buffer] [playbackRate = 1] [startPosition = 0] [loop = 0] [onComplete] base.player = new BufferPlayer(base.audiolet, base.buffer, 1, 0, 0); base.player.playing = false; base.gain = new Gain(base.audiolet, 0.5); // trigger -> player -> gain -> OUTPUT base.gain.connect(base.audiolet.output, 0); base.player.connect(base.gain, 0); base.trigger.connect(base.player, 0, 1); }); this.mute = function(){ base.gain.setValue(0); } this.unmute = function(){ base.gain.setValue(0.5); } } function Sequencer (audiolet, instrument) { var base = this; this.audiolet = audiolet; this.instrument = instrument; this.pattern = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]; // building a sequencer ... // this makes each note a sixteenth note, and sets up an empty bar var durations = new PSequence([0.30, 0.20], Infinity); var sequence = new PSequence(this.pattern, Infinity); this.audiolet.scheduler.play([sequence], durations, function (note, duration) { if (note == 1) { base.instrument.player.playing = true; base.instrument.trigger.trigger.setValue(1); } }); } function AudioletApp () { this.audiolet = new Audiolet(sampleRate, 2, bufferSize); this.audiolet.scheduler.setTempo(90); this.sequencers = []; for (var i in samples) { var sample = "/wav/" + samples[i]; var sampler = new Sampler(this.audiolet, sample); var sequencer = new Sequencer(this.audiolet, sampler); sequencer.sample = sample; this.sequencers.push(sequencer); } } var Audio = new AudioletApp(); function Grid (app){ var base = this; function setNote (data) { Audio.sequencers[data.channel].pattern[data.step] = data.state; drawNote(data.step, data.channel); }; base.setBeat = function(beatId) { beat = beatId; }; base.toggle = toggle; var playing = false; var playingInterval = false; var tick; var tempo = 125; setTempo(tempo); var activecoloron = 'rgb(255,255,255)'; var activecoloroff = 'rgb(202,202,202)'; var inactivecoloron = 'rgb(52,52,54)'; var inactivecoloroff = 'rgb(28,28,38)'; var gridSize = Math.floor(600/16); var lastStep = 16; var channelCount = 8; var beat = lastStep-1; var inset = 5; var canvas = document.getElementById('canvas'); //.offset(); var ctx = canvas.getContext('2d'); init(); function init(){ bind(); makeGrid(); } // Bind events function bind(){ // Server events app.receive("event-grid", loadGrid); app.receive("event-note", setNote); // UI events $('#start').click(start); $('#stop').click(stop); $('#tempo').change(function() { setTempo( $('#tempo').val() ) }); // Clicking the canvas $('#canvas').click(canvasClick); } // Click a square on the canvas to toggle it. function canvasClick (e){ var x = e.pageX-canvas.offsetLeft-10; var y = e.pageY-canvas.offsetTop-10; var step = Math.floor(x / gridSize); var channel = Math.floor(y / gridSize); toggleNote(channel, step); // Let the other clients know the new state app.send("event-note", { 'step': step, 'channel': channel, 'state': Audio.sequencers[channel].pattern[step] }); } // Load the pattern from the server function loadGrid(data){ for (var channel = 0; channel < channelCount; channel++) { for (var step = 0; step < lastStep; step++) { Audio.sequencers[channel].pattern[step] = data.pattern[channel][step]; } } tempo = data.tempo; stop(); start(); } // Toggle on/off function toggle(){ playing ? stop() : start(); } // Start playing function start() { reset(); playing = true; makeGrid(); document.body.bgColor = "#000000"; playingInterval = setInterval(make, tick); } // Stop playing function stop() { reset(); playing = false; document.body.bgColor = "#444444"; } // Clear the canvas, reset everything to zero, stop the event loop function reset() { clearInterval(playingInterval); ctx.clearRect(0,0, canvas.width, canvas.height); beat = lastStep-1; } // Change the tempo function setTempo(tempo) { tempo = parseInt(tempo); if (tempo <= 30 || tempo >= 300) { tempo = 120; } Audio.audiolet.scheduler.setTempo(tempo); tick = (1000 / (tempo / 60)) / 4; $("#alertDisp").html( "Set tempo to " + tempo ); if (playing) { clearInterval(playingInterval); playingInterval = setInterval(make, tick); } } // Paint the current step that's playing, and reset the previous stamp // This is the old event loop function make() { beat += 1; if (beat == lastStep) { makeColumn(beat-1, activecoloroff, inactivecoloroff, false); makeColumn(0, activecoloron, inactivecoloron, true); beat = 0; } else { makeColumn(beat, activecoloron, inactivecoloron, true); makeColumn(beat-1, activecoloroff, inactivecoloroff, false); } } // Paint the rectangles in a column, if any of them are playing. function makeColumn(step, active, inactive, playing) { for (var channel = 0; channel < channelCount; channel += 1) { if (Audio.sequencers[channel].pattern[step] == 1) { if (playing === true) { ctx.fillStyle = random_color(); } else { ctx.fillStyle = active; } ctx.fillRect( (step*gridSize) + inset, (channel*gridSize) + inset, gridSize - 2*inset, gridSize - 2*inset ); } } } // Paint all the squares on the grid function makeGrid() { for (var step = 0; step < lastStep; step += 1) { for (var channel = 0; channel < channelCount; channel += 1) { if (Audio.sequencers[channel].pattern[step] == 1) { ctx.fillStyle = activecoloroff; } else { ctx.fillStyle = step % 4 ? inactivecoloroff : inactivecoloron; } ctx.fillRect((step*gridSize) + inset, (channel*gridSize) + inset, gridSize - 2*inset, gridSize - 2*inset); } } } // Repaint a square to toggle it function drawNote(channel, step) { if (Audio.sequencers[channel].pattern[step] == 1) { ctx.fillStyle = activecoloroff; } else { ctx.fillStyle = step % 4 ? inactivecoloroff : inactivecoloron; } ctx.fillRect((step*gridSize) + inset, (channel*gridSize) + inset, gridSize - 2*inset, gridSize - 2*inset); } // Actually toggle a note, and then redraw it function toggleNote(channel, step) { if (Audio.sequencers[channel].pattern[step] == 1) { Audio.sequencers[channel].pattern[step] = 0; } else { Audio.sequencers[channel].pattern[step] = 1; } drawNote(channel, step); } // Generate a random color function random_color() { var rint = Math.round(0xffffff * Math.random()); return 'rgb(' + (rint >> 16) + ',' + (rint >> 8 & 255) + ',' + (rint & 255) + ')'; } };