JSFiddle - React, Tailwind, and code Playground

HTML

<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css">
<div id="error" style="display:none">Sorry, AudioContext is not supported by your browser, it should work on Google Chrome.</div>
<div id="failure" style="display:none">Your browser failed to start the AudioContext, probably because you have too much web app using it. <a href="">Try again</a></div>

<div id="overlay" style="display:none" class="play">
Press here <i href="#" class="icon-play"></i> to start
</div>

<header>
  <h1>
    Envelope
    
    <span style="float: right">
    <a href="#" class="play">
        play
        <i class="icon-play"></i>
    </a>
    <a href="#" class="stop">
        stop
        <i class="icon-stop"></i>
    </a>
    </span>
  </h1>
  <h2></h2>
</header>

<div class="modules">
<div id="left-module" class="module">
    <h3>Modulator</h3>
    <div class="infos"></div>
    <div class="viewport">
    <canvas width="200" height="150"></canvas>
    <canvas width="200" height="150"></canvas>
    </div>
    <div class="controls">
        <p>
            <label>frequency</label>
            <span class="value-container">
                <span class="value"></span>
                Hz
            </span>
            <input type="range" data-bind="modulator.osc.frequency" data-mapValue="val * NOTE_VAL.value / 4" min="1" max="20" step="1" value="1" />
        </p>
    </div>
</div>
<div id="right-module" class="module">
    <h3>Carrier</h3>
    <div class="infos"></div>
    <div class="viewport">
    <canvas width="200" height="150"></canvas>
    <canvas width="200" height="150"></canvas>
    </div>
    <div class="controls">
        <p>
            <label>frequency</label>
            <span class="value-container">
                <span class="value"></span>
                Hz
            </span>
            <input type="range" data-bind="NOTE_VAL" min="20" max="1000" step="1" value="220" />
        </p>
        <p>
            <label>Play a...

CSS

body {
  min-width: 400px;
  overflow: hidden;
}
footer {
    clear: left;
}
#failure, #error {
    color: #f00;
}
#overlay {
    position: fixed;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    opacity: 0.8;
    background: #fff;
    color: #468;
    text-align: center;
    padding-top: 10%;
    font-size: 3em;
    cursor: pointer;
    z-index: 1000;
    font-weight: bold;
}
#overlay:hover {
    opacity: 0.9;
}

a {
    color: #49c;
    text-decoration: none;
    -webkit-transition: 200ms;
}
a:hover {
    text-decoration: none;
    color: #169;
}

h1 {
  color: #9bd;
  font-family: Helvetica, sans-serif;
}
h3 {
  font-size: 1.0em;
  margin: 0.2em;
  color: #369;
}

canvas {
    width: 100%;
    background: #fff;
}

.module {
    float: left;
    width: 47%;
    margin: 0.5%;
    padding: 1%;
    background: #f3f7fc;
    border: 1px solid #9cf;
    border-radius: 3px;
    box-sizing: border-box;
    -webkit-box-sizing: border-box;
}
#left-module {
    margin-right: 1%;
    position: relative;
}
#right-module {
    margin-left: 1%;
}
#left-module:after {
    position: absolute;
    top: -15px;
    right: -5%;
    width: 5%;
    padding-bottom: 10px;
    border-bottom: 2px solid #369;
    color: #369;
    content: "freq";
    font-size: 0.8em;
    font-family: monospace;
}
.module .viewport {
    position: relative;
    box-sizing: border-box;
    -webkit-box-sizing: border-box;
    overflow: auto;
    margin-bottom: 5px;
    clear: both;
}
.module canvas {
    width: 49%;
}
.module canvas:first-child {
    float: left;
}
.module canvas:last-child {
    float: right;
}


.module h3 {
    float: left;
}
.module .controls {
    clear: both;
}
.module .controls p input {
    width: 100%;
}
.module .controls label {
    color: #c06;
}
.module .controls .value-container {
    float: right;
    color: #c06;
}
.module .controls .value {
    font-weight: bold;
}
.module .infos {
    color: #666;
    font-family: monospace;
    font-size: 0.8em;
   ...

JavaScript

REFRESH_RATE = 30;
VOLUME = 0.5;

var AudioContext = window.AudioContext; // only webkitAudioContext supports Oscillator for now
if (!AudioContext) {
    $('#error').show();
    return;
}

NOTE_MUL = { value: 1 };
NOTE_VAL = { value : 0 };

var audioCtx;
var destination;
var carrier, modulator, lspectrum, rspectrum, lwaveform, rwaveform;
function demo () {
    try {
      audioCtx = new AudioContext();
    }
    catch (e) {
      $('#failure').show();
      return;
    }
    
    destination = audioCtx.createGain();
    destination.gain.value = VOLUME;
    destination.connect(audioCtx.destination);
    
    // Config of the demo
    carrier = new Carrier("sine", 220);
    modulator = new Modulator("sine", 800);
    lspectrum = new SpectrumAnalyzer(modulator.osc, 0, 10000);
    rspectrum = new SpectrumAnalyzer(carrier.gain, 0, 10000);
    lwaveform = new WaveformAnalyzer(modulator.osc);
    rwaveform = new WaveformAnalyzer(carrier.gain);
    modulator.gain.connect(carrier.osc.frequency);
    carrier.gain.gain.value = 0;
    carrier.gain.connect(destination);
}

function noteOn (freq, time) {
    carrier.noteOn(freq, time);
    modulator.noteOn(freq, time);
}

function noteOff (time) {
    carrier.noteOff(time);
    modulator.noteOff(time);
}

function envelope (gainNode, time, volume, attackDuration, decayDuration, sustain) {
    gainNode.gain.cancelScheduledValues(time);
    gainNode.gain.value = volume;
    gainNode.gain.setValueAtTime(0, time);
    gainNode.gain.linearRampToValueAtTime(volume, time + attackDuration);
    gainNode.gain.linearRampToValueAtTime(volume * sustain, time + attackDuration + decayDuration);
}

function envelopeRelease (gainNode, time, releaseTime) {
    gainNode.gain.cancelScheduledValues(0);
    gainNode.gain.setValueAtTime(gainNode.gain.value, time);
    gainNode.gain.linearRampToValueAtTime(0, time + releaseTime);
}

var renderInterval;

// A modulator have a oscillator and a gain
function Modulator (type, freq) {
  this.osc =...