/ code

Create a Metronome with React

The AudioContext interface allows for the use of browser-generated sounds to be used in your web app. To demonstrate a basic application of the AudioContext, I'm going to show you how to make a metronome with React.

This May Not Work In All Browsers

States

The states are going to be used to store whether the metronome is enabled or not, the beat count, the BPM (beats-per-minute), and the time signature. If the beat count is 1, the metronome will play a higher pitch. This signals the start of a measure.

var App = React.createClass({
    getInitialState: function() {
        return ({
            bpm: "120",
            metronome: false,
            time_signature: [4,4],
            beat_count: 1
        });
    },
});

Now that we have our initial states, let's create some callbacks to update them:


    updateTimeSignatureTop: function(e){
        var time_sig = this.state.time_signature;
        time_sig[0] = e.target.value;
        this.setState({time_signature : time_sig});
    },
    updateTimeSignatureBottom: function(e){
        var time_sig = this.state.time_signature;
        time_sig[1] = e.target.value;
        this.setState({time_signature : time_sig});
    },
    updateBPM: function(e) {
        this.setState({bpm: e.target.value});
        if (this.state.metronome) {
            this.stopMetronome();
            this.startMetronome();
        }
    },

The Metronome

This gives us an AudioContext object to work with. This is what we will use to create our oscillator, the object that creates the beep noise.

    setupAudioContext: function(){
        context = new window.AudioContext();
    },

The beep function creates the oscillator, checks if we are on beat one, and plays the appropriate frequency.

    beep: function() {
        osc = context.createOscillator();
        if (this.state.beat_count == 1) {
            osc.frequency.value = 840;
        } else {
            osc.frequency.value = 440;
        }
        osc.start(context.currentTime);
        osc.connect(context.destination);
        osc.stop(context.currentTime + (150 / 1000))
        if(this.state.beat_count < this.state.time_signature[0]) {
            this.setState({beat_count: this.state.beat_count + 1});
        } else {
            this.setState({beat_count: 1});
        }
    },

These functions start and stop the metronome.

    startMetronome: function(){
        this.setupAudioContext();
        this.setState({metronome: true});
        this.setState({beat_count: 1});
        var component = this;
        metronome = setInterval(function(){component.beep()}, (1000 / (parseInt(this.state.bpm))) * 60)
    },
    stopMetronome: function(){
        this.setState({metronome: false});
        this.setState({beat_count: 1});
        clearInterval(metronome);
        context.close();
    },

UI

<div>
<label>BPM - {this.state.bpm}</label>
<br />
<input type="range" min="30" max="400" defaultValue={this.state.bpm} onChange={this.updateBPM}></input>
<label>Time Signature - {this.state.time_signature[0]}/{this.state.time_signature[1]}</label>
<br />
<input type="number" min="1" max="7" defaultValue={this.state.time_signature[0]} onChange={this.updateTimeSignatureTop}></input>
<br />
<input type="number" min="2" max="8" defaultValue={this.state.time_signature[1]} onChange={this.updateTimeSignatureBottom}></input>
<br />
<button className="metronome-btn" onClick={this.startMetronome}>Start Metronome</button>
<button className="metronome-btn" onClick={this.stopMetronome}>Stop Metronome</button>
</div>

As you can see, we read in our BPM and Time Signature states, allow the user to change them as they please, and we link our buttons to the appropriate functions.

Outcome

When everything is hooked together, we'll have a working metronome. See for yourself!