web-audio-synth-1

by mohayonao

HTML

<button id="play">Play</button> : click 2..4 times
<h4>detune series</h4>
<ul>
  <li>
    <label><input type="radio" name="detunes" value="[ 0 ]"> [ 0 ]</label>
  </li>
  <li>
    <label><input type="radio" name="detunes" value="[ -4, +4 ]"> [ -4, +4 ]</label>
  </li>
  <li>
    <label><input type="radio" name="detunes" value="[ -8, +8 ]" checked> [ -8, +8 ]</label>
  </li>
  <li>
    <label><input type="radio" name="detunes" value="[ -10, -4, +4, +10 ]"> [ -12, -4, +4, +12 ]</label>
  </li>
  <li>
    <label><input type="radio" name="detunes" value="[ -32, -20, +20, +32 ]"> [ -32, -16, +16, +32 ]</label>
  </li>
  <li>
    <label><input type="radio" name="detunes" value="[ -64, -48, -16, +16, +48, +64 ]"> [ -64, -48, -16, +16, +48, +64 ]</label>
  </li>
  <li>
    <label><input type="radio" name="detunes" value="[ -128, -48, -24, +24, +48, +128 ]"> [ -128, -48, -24, +24, +48, +128 ]</label>
  </li>
</ul>

JavaScript

window.AudioContext = window.AudioContext || window.webkitAudioContext;

document.getElementById("play").addEventListener("click", function() {
  play(getDetuneParams());
});

var audioContext = new AudioContext();

function play(detuneParams) {
  synth({
    playbackTime: audioContext.currentTime,
    args: {
      out: audioContext.destination,
      midi: sample([ 60, 62, 64, 65, 67, 69, 71, 72 ]),
      detuneParams: detuneParams,
      dur: 12
    }
  });
}

function synth(e) {
  var t0 = e.playbackTime;
  var t1 = t0 + e.args.dur * 0.4;
  var t2 = t1 + e.args.dur * 0.6;
  var gain = audioContext.createGain();
  
  var oscs = e.args.detuneParams.map(function(detune) {
    var osc = audioContext.createOscillator();
    
    osc.frequency.value = mtof(e.args.midi);
    osc.detune.setValueAtTime(detune / 8, t0);
    osc.detune.linearRampToValueAtTime(detune, t2);
    osc.start(t0);
    osc.stop(t2);
    osc.connect(gain);
    
    return osc;
  });
  
  gain.gain.setValueAtTime(0, t0);
  gain.gain.linearRampToValueAtTime(0.025, t1);
  gain.gain.linearRampToValueAtTime(0.000, t2);
  gain.connect(e.args.out);
  
  oscs[0].onended = function() {
    oscs.forEach(function(osc) {
      osc.disconnect();
    });
    gain.disconnect();
    oscs = gain = null;
  };
}


// utilities
function sample(list) {
  return list[(Math.random() * list.length)|0];
}

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

function getDetuneParams() {
  var detunes = [].slice.call(document.getElementsByName("detunes"));

  return JSON.parse(detunes.filter(function(input) {
    return input.checked;
  })[0].value.replace(/\+/g, ""));
}