JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://mohayonao.github.io/stereo-panner-node/build/stereo-panner-node.js"></script>
<script src="https://mohayonao.github.io/stereo-analyser-node/build/stereo-analyser-node.js"></script>
<button id="play">play</button>
<canvas id="canvas"></canvas>

CSS

#canvas {
  display: block;
  margin: 10px;
  width: 360px;
  height: 240px;
  background: #999;
}

JavaScript

StereoPannerNode.polyfill();

var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext();
var analyser = new StereoAnalyserNode(audioContext);
var timerId = 0;

function loop() {
  var destination = analyser;
  var playbackTime = audioContext.currentTime;
  var duration = tdur(20, sample([
    1, 1, 2, 2, 2, 4, 4, 8,
  ])) * dotn(sample([ 0, 0, 1, 2 ]));

  var notes = Array.from({ length: 4 }, function() {
    return {
      frequency: mtof(sample([
        48,
        60, 64, 65, 69,
        72, 74, 77, 79,
      ])),
      rate: rand(6),
      offset: rand2(0.9),
    };
  });
  notes.duration = duration;

  chord(destination, playbackTime, notes);

  timerId = setTimeout(loop, duration * 1000);
}

function chord(destination, playbackTime, notes) {
  var duration = notes.duration;
  var t0 = playbackTime;
  var t1 = t0 + duration;
  var t2 = t1 + duration * 0.5;
  var audioContext = destination.context;
  var gain = audioContext.createGain();

  notes.forEach(function(note) {
    var oscillator = audioContext.createOscillator();
    var panner = audioContext.createStereoPanner();

    oscillator.frequency.value = note.frequency;
    oscillator.start(t0);
    oscillator.stop(t2);
    oscillator.connect(panner);

    for (var tx = t0; tx < t2; tx += 0.01) {
      var panValue = sample([ -1, +1 ]) * note.offset + rand2(0.1);
      panner.pan.setTargetAtTime(panValue, tx, 0.0005);
      tx += rand(0.2);
    }
    panner.connect(gain);
  });

  gain.gain.setValueAtTime(0.15, t0);
  gain.gain.linearRampToValueAtTime(0.10, t1);
  gain.gain.linearRampToValueAtTime(0.00, t2);
  gain.connect(destination);
}

function mtof(midi) {
  return 440 * Math.pow(2, (midi - 69) / 12);
}

function tdur(tempo, length) {
  return (60 / tempo) * (4 / length);
}

function dotn(n) {
  return 2 - Math.pow(2, -n);
}

function rand(value) {
  return Math.random() * value;
}

function rand2(value) {
  return (Math.random() * 2 - 1) *...