JSFiddle - React, Tailwind, and code Playground

by Ehsan Ziya

HTML

<p><strong>Please note that you need a recent Webkit browser for this to work.</strong></p>
<ol><li>Try to make a rhythm by clicking repeatedly on the left button: notice that it works. There is no perceptible delay.</li><li>Now try to do the same with the second button: there is too much delay!</li></ol>
<button onmousedown="this.innerHTML='Playing, release mouse to stop!'; playBuffer()" onmouseup="this.innerHTML='Play Buffer'; stopBuffer()">Play Buffer</button>
<button onmousedown="this.innerHTML='Playing, release mouse to stop!'; playLive();" onmouseup="this.innerHTML='Play Live'; stopLive()">Play Live</button>

JavaScript

var ac = new webkitAudioContext();

var Instrument = function(freq, ac)
{
    this.ac = ac;
    this.phase = 0;
    this.delta_phase = freq / ac.sampleRate;
    
    this.getSample = function()
    {
        return Math.sin(2*Math.PI*this.phase);
    }
    
    this.fillBuffer = function(buffer, length)
    {
        for(var i=0; i<length; i++)
        {
            buffer[i] = this.getSample();
            this.phase += this.delta_phase;
        }
    };
};

/*
* Build an instrument to play a 440Hz A note
*/

var sineStrument = new Instrument(440, ac);

var bufferLength = ac.sampleRate; // 1 second

var buffer = ac.createBuffer(1, bufferLength, ac.sampleRate);
sineStrument.fillBuffer(buffer.getChannelData(0), buffer.length);

/*
* Setup offline processing
* We build a buffer then play it when the user clicks on the "Play Buffer" button
*/

var source;

function playBuffer()
{
    source = ac.createBufferSource();
    source.buffer = buffer;
    source.connect(ac.destination);
    source.start(0);
};

function stopBuffer()
{
    source.stop(0);
};

/*
* Live processing
* We use the same algorithm to generate the tone, 
* but keep generating it as long as the button is pressed.
*/

var processor;

function playLive()
{
    processor = ac.createScriptProcessor(4096, 0, 1);
    processor.onaudioprocess = function(e)
    {
        sineStrument.fillBuffer(e.outputBuffer.getChannelData(0), e.outputBuffer.length);
    }
    processor.connect(ac.destination);
};

function stopLive()
{
    processor.disconnect();
};