mozilla

Revision 95526 of Creating a simple synth

  • Revision slug: Creating_a_simple_synth
  • Revision title: Creating a simple synth
  • Revision id: 95526
  • Created:
  • Creator: Sheppy
  • Is current revision? Yes
  • Comment clean up; 104 words added, 105 words removed

Revision Content

This article describes the creation of a simple synth, using existing tools, such as the Audio API, HTML5 Virtual MIDI Keyboard and some additional JavaScript libraries:

osc.js
A library describing a class for an oscillator.
audiodevice.js
A class that interacts with the Mozilla audio API in a way that lets you use callbacks to write the audio.

Let's start with a plan, and create a simple demonstration: a synthesizer with a virtual MIDI keyboard, waveform options, and some noise modulation for extra spicyness.

The code

HTML

The HTML (simplesynth.html) should include all our scripts, an {{ HTMLElement("iframe") }} for the keyboard, and an {{ HTMLElement("option") }} tag to change the waveform.

<!DOCTYPE html>
<html>
  <head>
    <title>Simple synth using the audio api</title>
    <script src="audiolib.js"></script>
    <script src="simplesynth.js"></script>
    <link rel="stylesheet" href="simplesynth.css" />
  </head>
  <body>
    <label for="waveshape">Waveshape</label><br />
    <select id="waveshape">
      <option value="0" selected="selected">Sine</option>
      <option value="1">Triangle</option>
      <option value="2">Pulse</option>
      <option value="3">Sawtooth</option>
      <option value="4">Invert Sawtooth</option>
      <option value="5">Square</option>
    </select>
    <iframe id="keyboard" src="virtualmidikb/index.html"></iframe>
  </body>
</html>

CSS

Then we'll drop in a little CSS to attach the keyboard to the bottom of the page; this is in simplesynth.css:

iframe {
  position: absolute;
  bottom: 0px;
  width: 100%;
  left: 0px;
  border-width: 0px;
}

JavaScript

Now we need a plan for the script. We don't really need to do much; we simply instantiate an AudioDevice with a callback that fills the buffer with data from the Oscillator. Then we need to bind the MIDI data to control the frequency of the oscillator and bind an event listener to the select to change the waveform.

First, let's define some variables.

var samplerate  = 22050,
    oscillator  = new Oscillator(samplerate), // Creates an instance of an oscillator.
    audiodev,
    pressedKeys = [];

    oscillator.frequency = 0;

Then we define a function we'll use as the callback for the AudioDevice. In it, we iterate through the given buffer and fill it with data from the oscillator, modulated with some random noise.

function processAudio(buffer){
  var i, l = buffer.length;

  // Iterate through the buffer

  for (i=0; i<l; i++){
    // Advance the oscillator angle, add some flavor with Math.random noise.

    oscillator.generate((Math.random() * 2 - 1) * 0.3);

    // Set the sample for both channels to oscillator's output,
    // and multiply that with 0.2 to lower the volume to a less
    // irritating/distorted level.

    buffer[i] = buffer[++i] = oscillator.getMix() * 0.2;
  }
}

Next, let's define the MIDI handling operation for the keyboard. This code sets the oscillator's frequency based on the last key that was pressed.

function onmidi(e){
  var i;

  // 0x9, KEYDOWN

  if (e.status === 9){
    // Add the newest key to be first in the array.
    pressedKeys.unshift(e.data1);
  } else if (e.status === 8){  // 0x8, KEYUP
    // Iterate through the keys...

    for (i=0; i<pressedKeys.length; i++){
      if (pressedKeys[i] === e.data1){
        // And remove the matching keys.
        pressedKeys.splice(i--, 1);
      }
    }
  // We don't understand anything else here, so we'll just leave.
  } else {
    return;
  }

  // If there are any pressed keys...

  if (pressedKeys.length){
    // Set the oscillator frequency to match the last key pressed
    oscillator.frequency = 440 * Math.pow(1.059, pressedKeys[0] - 69);
  } else {
    oscillator.frequency = 0;
  }
};

The start() function sets everything up by adding a listener to the wave shape {{ HTMLElement("option") }} element, which sets the waveform based on the user's selection, then creates the new audio device that will play the audio.

function start(){
  global.onmidi = onmidi;
  document.getElementById('waveshape').addEventListener('change', function(){
    oscillator.waveShape = Number(this.value);
  }, true);
  audiodev = new AudioDevice(samplerate, 2, processAudio);
}

global.addEventListener('load', start, true);

A live demo of this tutorial can be found from http://niiden.com/simplesynth/simplesynth.html and the full source code is on github at https://github.com/jussi-kalliokoski/simplesynth , audiolib can also be found on github: https://github.com/jussi-kalliokoski/simplesynth

Revision Source

<p>This article describes the creation of a simple synth, using existing tools, such as the Audio API, HTML5 Virtual MIDI Keyboard and some additional JavaScript libraries:</p>
<dl> <dt><code>osc.js</code></dt> <dd>A library describing a class for an oscillator.</dd> <dt><code>audiodevice.js</code></dt> <dd>A class that interacts with the Mozilla audio API in a way that lets you use callbacks to write the audio.</dd>
</dl>
<p>Let's start with a plan, and create a simple demonstration: a synthesizer with a virtual MIDI keyboard, waveform options, and some noise modulation for extra spicyness.</p>
<h2 id="The_code">The code</h2>
<h3 id="HTML">HTML</h3>
<p>The HTML (<code>simplesynth.html</code>) should include all our scripts, an {{ HTMLElement("iframe") }} for the keyboard, and an {{ HTMLElement("option") }} tag to change the waveform.</p>
<pre class="brush: html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Simple synth using the audio api&lt;/title&gt;
    &lt;script src="audiolib.js"&gt;&lt;/script&gt;
    &lt;script src="simplesynth.js"&gt;&lt;/script&gt;
    &lt;link rel="stylesheet" href="simplesynth.css" /&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;label for="waveshape"&gt;Waveshape&lt;/label&gt;&lt;br /&gt;
    &lt;select id="waveshape"&gt;
      &lt;option value="0" selected="selected"&gt;Sine&lt;/option&gt;
      &lt;option value="1"&gt;Triangle&lt;/option&gt;
      &lt;option value="2"&gt;Pulse&lt;/option&gt;
      &lt;option value="3"&gt;Sawtooth&lt;/option&gt;
      &lt;option value="4"&gt;Invert Sawtooth&lt;/option&gt;
      &lt;option value="5"&gt;Square&lt;/option&gt;
    &lt;/select&gt;
    &lt;iframe id="keyboard" src="virtualmidikb/index.html"&gt;&lt;/iframe&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre>
<h3 id="CSS">CSS</h3>
<p>Then we'll drop in a little CSS to attach the keyboard to the bottom of the page; this is in <code>simplesynth.css</code>:</p>
<pre class="brush: css">iframe {
  position: absolute;
  bottom: 0px;
  width: 100%;
  left: 0px;
  border-width: 0px;
}
</pre>
<h3 id="JavaScript">JavaScript</h3>
<p>Now we need a plan for the script. We don't really need to do much; we simply instantiate an <code>AudioDevice</code> with a callback that fills the buffer with data from the <code>Oscillator</code>. Then we need to bind the MIDI data to control the frequency of the oscillator and bind an event listener to the select to change the waveform.</p>
<p>First, let's define some variables.</p>
<pre class="brush: js">var samplerate  = 22050,
    oscillator  = new Oscillator(samplerate), // Creates an instance of an oscillator.
    audiodev,
    pressedKeys = [];

    oscillator.frequency = 0;
</pre>
<p>Then we define a function we'll use as the callback for the <code>AudioDevice</code>. In it, we iterate through the given buffer and fill it with data from the oscillator, modulated with some random noise.</p>
<pre class="brush: js">function processAudio(buffer){
  var i, l = buffer.length;

  // Iterate through the buffer

  for (i=0; i&lt;l; i++){
    // Advance the oscillator angle, add some flavor with Math.random noise.

    oscillator.generate((Math.random() * 2 - 1) * 0.3);

    // Set the sample for both channels to oscillator's output,
    // and multiply that with 0.2 to lower the volume to a less
    // irritating/distorted level.

    buffer[i] = buffer[++i] = oscillator.getMix() * 0.2;
  }
}
</pre>
<p>Next, let's define the MIDI handling operation for the keyboard. This code sets the oscillator's frequency based on the last key that was pressed.</p>
<pre class="brush: js">function onmidi(e){
  var i;

  // 0x9, KEYDOWN

  if (e.status === 9){
    // Add the newest key to be first in the array.
    pressedKeys.unshift(e.data1);
  } else if (e.status === 8){  // 0x8, KEYUP
    // Iterate through the keys...

    for (i=0; i&lt;pressedKeys.length; i++){
      if (pressedKeys[i] === e.data1){
        // And remove the matching keys.
        pressedKeys.splice(i--, 1);
      }
    }
  // We don't understand anything else here, so we'll just leave.
  } else {
    return;
  }

  // If there are any pressed keys...

  if (pressedKeys.length){
    // Set the oscillator frequency to match the last key pressed
    oscillator.frequency = 440 * Math.pow(1.059, pressedKeys[0] - 69);
  } else {
    oscillator.frequency = 0;
  }
};
</pre>
<p>The <code>start()</code> function sets everything up by adding a listener to the wave shape {{ HTMLElement("option") }} element, which sets the waveform based on the user's selection, then creates the new audio device that will play the audio.</p>
<pre class="brush: js">function start(){
  global.onmidi = onmidi;
  document.getElementById('waveshape').addEventListener('change', function(){
    oscillator.waveShape = Number(this.value);
  }, true);
  audiodev = new AudioDevice(samplerate, 2, processAudio);
}

global.addEventListener('load', start, true);
</pre>
<p>A live demo of this tutorial can be found from <a class=" external" href="http://niiden.com/simplesynth/simplesynth.html" rel="freelink">http://niiden.com/simplesynth/simplesynth.html</a> and the full source code is on github at <a class=" link-https" href="https://github.com/jussi-kalliokoski/simplesynth">https://github.com/jussi-kalliokoski/simplesynth</a> , audiolib can also be found on github: <a class=" link-https" href="https://github.com/jussi-kalliokoski/audiolib.js">https://github.com/jussi-kalliokoski/simplesynth</a></p>
Revert to this revision