Playing notes with the Web Audio API Part 2 - Polyphonic Synthesis

June 10, 2013

In my last post, I talked about playing notes with the Web Audio API using techniques inspired by monophonic synthesizers, such as the Minimoog. The advantage of this approach was in its simplicity and that by using a single oscillator as our source of sound we could easily slide between notes when playing.

The principle disadvantage was, of course, that with a single oscillator as our source of sound we could only play one note at a time. Fine for throaty, analogue sounding synth lines, or a flute simulator perhaps, but not so useful for creating lush string pads or simulating a piano or Hammond organ.

What is polyphony?

Simply put, polyphony is the simultaneous playing of independent melodies. A string quartet for example has 4 instruments playing at the same time. In synthesis, polyphony describes the ability of a single instrument to play multiple notes simultaneously, allowing you to play a chord or an arpeggio with overlapping notes.

A polyphonic synthesizer

Early synthesizer pioneers were incredibly creative at getting around the limitations and reliability of analogue circuits to produce a range of polyphonic, and nearly polyphonic synths. This great ‘Synth Secrets’ article on polyphony from Sound on Sound magazine gives a lot of historical detail of the development of various approaches. For our purposes though, let’s consider the synthesizers of Oberheim.

This is an Oberheim SEM monosynth module.

The control panel looks very much like the simple “monosynth” we talked about last time - an Oscillator (VCO) or two with something to control the amplitude, marked here ENV, for envelope, but also known as a VCA (Voltage Controlled Amplifier).

If you bought one of these modules and connected it to a standalone keyboard, you had a monosynth. But what if you could attach a module to each individual key on the keyboard? Then you’d be able to play multiple notes simultaneously: polyphony!

In practice, at the time, a synthesizer with so many modules would have been very expensive and unreliable. What Oberheim chose to do instead was to build synthesizers with a smaller number of these modules, and then use a bit of clever circuitry called a Voice Allocation Unit to allocate a key press to one of the individual modules - whichever one was “free” at the time. The Oberheim 2-Voice had two SEM modules, the 4-Voice four and the 8-Voice, well, you get the idea.

Polyphony with the Web Audio API

Nowadays with digital technology and the Web Audio API in particular, we are not limited to a fixed number of voices for cost or reliability reasons. Oscillators and Amplifiers are cheap to create, so we can in effect create a new Voice each time a key is pressed. We’ll use that example to convert our synth from the previous article into a polyphonic one.

To begin with let’s take the VCO and VCA from the previous article and wrap them in a “class” called Voice

  var Voice = (function(context) {
    function Voice(frequency){
      this.frequency = frequency;
    };

    Voice.prototype.start = function() {
      /* VCO */
      var vco = context.createOscillator();
      vco.type = vco.SINE;
      vco.frequency.value = this.frequency;

      /* VCA */
      var vca = context.createGain();
      vca.gain.value = 0.3;

      /* connections */
      vco.connect(vca);
      vca.connect(context.destination);

      vco.start(0);
    };

    return Voice;
  })(context);

This class takes a frequency parameter in its constructor and when the “start” method is called, constructs the =OscillatorNode= and GainNode from the monosynth example and connects them to the destination. If we modify the keyDown function to create a new Voice on each keypress, we have basic polyphony.

keyboard.keyDown(function (_, frequency) {
  var voice = new Voice(frequency);
  voice.start()
});

If you try this example you’ll notice straight away that we can now play multiple simultaneous notes on the keyboard. Now we just need to be able to stop them! This isn’t quite as straightforward as before as we don’t have a single VCA we can set to zero. Just as with the Oberheim SEM modules that are our inspiration, for each note we now have to control the parameters for that individual Voice.

We start by adding a stop method to Voice. This simply needs to stop() all of the active oscillators for this Voice. To achieve this, we need to keep track of the oscillators we add to the graph

var Voice = (function(context) {
  function Voice(frequency){
    this.frequency = frequency;
    this.oscillators = [];
  };

  Voice.prototype.start = function() {
    /* ... */

    vco.start(0);

    /* Keep track of the oscillators used */
    this.oscillators.push(vco);
  };

  /* ... */

And then to stop the Voice we can call stop() on each of the oscillators

/* ... */

Voice.prototype.stop = function() {
  this.oscillators.forEach(function(oscillator, _) {
    oscillator.stop();
  });
};

/* ... */

Then, if we keep track of which Voice is associated with each key pressed, we can call stop() when the key is released. This is straightforward if we store each Voice in an object

  active_voices = {};

  keyboard.keyDown(function (note, frequency) {
    var voice = new Voice(frequency);
    active_voices[note] = voice;
    voice.start();
  });

  keyboard.keyUp(function (note, _) {
    active_voices[note].stop();
    delete active_voices[note];
  });

So there we have it, a very simple polyphonic synthesizer. You’ll notice some clicking when the keys are pressed and also some distortion when too many notes are played. I’ll address these issues, and look at how to synthesise more exciting sounds in later posts.

Published