Break down the breakdown

Paul Adenot (@padenot) - Mozilla - Ableton Loop'17

Today !

      Web Audio API !?
      A few examples
      Jumping in !

Web Audio API ?!

Just web pages !

Any browser !

Any device !

  • Oscillators (classive waveforms, FM, additive)
  • Gain
  • Filters (biquad, IIR)
  • Convolution
  • Delay
  • Wave shaping
  • Channel splitting/merging
  • Panning (stereo, 3D, HRTF)
  • Sample playback (compressed & wav)
  • Compressor
  • Analyser (time & frequency domain)
  • Offline rendering
  • Direct sample manipulation via JavaScript
  • Low latency

var ac = new AudioContext

Web Audio API 101

AudioContext + AudioNode + AudioParam

    partial interface AudioContext {
      readonly attribute AudioNode destination;
      readonly attribute double currentTime;
      // change volume 
      GainNode createGain();
      // play simple wave forms (sine, square, etc.) 
      OscillatorNode createOscillator();
      // filter the sounds 
      BiquadFilterNode createBiquadFilter();
    }
            
            partial interface OscillatorNode {
              AudioNode connect(AudioNode node);
              void disconnect(AudioNode node);

              readonly AudioParam frequency;
              readonly AudioParam detune;
              enum OscillatorType type;

              void start(double startTime);
              void stop(double stopTime);
            }
            


    partial interface AudioParam {
      attribute float value;

      void setValueAtTime(float value, double time);
      void exponentialRampToValueAtTime(float value, double time);
      void linearRampToValueAtTime(float value, double time);
      void setTargetAtTime(float value, double time, float constant);
    }

            

Basic waveforms

            var osc = ac.createOscillator(); // VCO
            var gain = ac.createGain(); // VCA

            osc.type = "sine";
            osc.frequency.value = 440.; // A4

            input1.oninput = (e) => osc.frequency.value = e.target.value
            input2.oninput = (e) => gain.gain.value = e.target.value

            osc.connect(gain); // Patch cable
            gain.connect(ac.destination); // Patch cable to output

            osc.start();
            

Volume envelopes

        var osc = ac.createOscillator(); // VCO
        osc.frequency.value = 100; // Set the fundamental frequency

        var gain = ac.createGain(); // VCA
        osc.connect(gain); // Patch cable
        gain.connect(ac.destination) // Patch cable to audio out

        osc.start(); // Start oscillating

        setInterval(function() {
          gain.gain.setValueAtTime(1.0, ac.currentTime);
          gain.gain.setTargetAtTime(0.0, ac.currentTime, 0.3);
        }, 500);
          

Pitch and gain envelopes: simple kick

        var osc = ac.createOscillator(); // VCO
        var gain = ac.createGain(); // VCA

        osc.connect(gain); // Patch cable
        gain.connect(ac.destination) // Patch cable to audio out

        osc.start(); // Start oscillating

        setInterval(function() {
          gain.gain.setValueAtTime(1.0,  ac.currentTime);
          gain.gain.setTargetAtTime(0.0, ac.currentTime, 0.12);

          osc.frequency.setValueAtTime(100.0, ac.currentTime);
          osc.frequency.setTargetAtTime(45.0, ac.currentTime, 0.07);
        }, 300);
          

Pseudo-reese

        var osc1 = ac.createOscillator();
        var osc2 = ac.createOscillator();

        osc1.type = "sawtooth";
        osc2.type = "sawtooth";
        osc1.frequency.value = osc2.frequency.value = 80;

        input1.oninput = (e) => osc2.detune.value = e.target.value
        input2.oninput = (e) => osc1.frequency.value = osc2.frequency.value = e.target.value

        osc1.connect(ac.destination);
        osc2.connect(ac.destination);

        osc1.start();
        osc2.start();
            

Filtering

            var osc = ac.createOscillator(); var osc2 = ac.createOscillator();
            var gain = ac.createGain();
            var biquad = ac.createBiquadFilter();

            osc.type = "sawtooth";
            osc.frequency.value = osc1.frequency.value = 110.; // A2
            osc2.detune.value = 30;
            biquad.type = "lowpass";

            input1.oninput = (e) => biquad.frequency.value = e.target.value
            input2.oninput = (e) => biquad.gain.value = e.target.value
            input3.oninput = (e) => biquad.Q.value = e.target.value
            option.onchange = (e) => biquad.type = e.target.value

            osc.connect(gain).connect(biquad).connect(ac.destination);
            osc.start(); osc2.start();
            

Gaussian noise

            var source = ac.createBufferSource();
            var buffer = ac.createBuffer(1, 1 * ac.sampleRate, ac.sampleRate);
            var channel = buffer.getChannelData(0);

            /* [0; 1] => [-1, 1] */
            for (var i = 0; i < channel.length; i++) {
              channel[i] = Math.random() * 2 - 1;
            }

            source.loop = true;
            source.buffer = buffer;
            source.connect(ac.destination);
            source.start();
          

Let's make a tune

Classic elements

  • a score
  • sequencer (16ths)
  • kick/hats/clap (x0x inspired)
  • bass (trivial two oscillator substractive synth)
  • sends: big reverb, ping-pong delay (+dry/wet controls)

A toy score

Set the tempo, decide on instruments, write two bars (eight fourth)
                                              var track = {
                                                tempo: 131,
                                                tracks: {
                                                 Kick: [ 1, 0,  0, 0, 1, 0, 0,  0],
                                                 Hats: [ 0, 0,  1, 0, 0, 0, 1,  0],
                                                 Clap: [ 0, 0,  0, 0, 1, 0, 0,  0],
                                                 Bass: [ 0, 0, 24, 0, 0, 0, 0, 24]
                                                }
                                              };
          

A toy sequencer

  1. Every x milliseconds:
    1. Get the current time
    2. Convert to fractional nth beat
    3. For each instruments:
      1. For this beat, if the value in the array is non zero:
        1. Call this instrument, passing in the time and the note

Routing

Sends: reverb

Sends: ping-pong delay

Kick drum

Hi-hats (or maracas ?)

Clap

Bass

Thanks !

{Twitter, GitHub}: @padenot

<padenot@mozilla.com>