var bufferSize = 4096; // 65536 / 2; var sampleRate = 44100; var latency = 1000 * bufferSize / sampleRate; var audioletReady = false; var samples = [ 'KickDrum0001.wav', 'Closed Hihat0001.wav', 'Open Hihat0001.wav', 'Clap.wav' // 'Clav.wav', 'Mid Tom0001.wav', 'Rimshot.wav', 'SnareDrum0001.wav' ]; function Sampler (audiolet, url) { var base = this; this.audiolet = audiolet; // first get the sample and load it into a buffer var buffer = new AudioletBuffer(1, 0); buffer.load(url, false); // connect the buffer to a player and set up the objects we need this.trigger = new TriggerControl(this.audiolet, 0); this.player = new BufferPlayer(this.audiolet, buffer, 1, 0, 0); this.gain = new Gain(this.audiolet, 0.50); // trigger -> player -> gain -> OUTPUT this.trigger.connect(this.player, 0, 1); this.player.connect(this.gain); this.gain.connect(this.audiolet.output); this.mute = function(){ base.gain.setValue(0); } this.unmute = function(){ base.gain.setValue(0.80); } } 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.25], Infinity); var sequence = new PSequence(this.pattern, Infinity); this.audiolet.scheduler.play([sequence], durations, trigger); function trigger (note, duration) { if (note == 1) { 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); this.sequencers.push(sequencer); } } 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 = 125; var activecoloron = 'rgb(255,255,255)'; var activecoloroff = 'rgb(202,202,202)'; var inactivecoloron = 'rgb(152,152,152)'; var inactivecoloroff = 'rgb(28,28,28)'; var gridSize = Math.floor(600/16); var lastStep = 16; var channelCount = 4; var beat = lastStep-1; var inset = 5; var Audio = new AudioletApp(); 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) { if (tempo <= 1 || tempo >= 800) { tempo = 120; } 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 = inactivecoloroff; } 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 = inactivecoloroff; } 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) + ')'; } };