Creating a simple synth

  • Revision slug: Creating_a_simple_synth
  • Revision title: Creating a simple synth
  • Revision id: 95522
  • Created:
  • Creator: quinnirill
  • Is current revision? No
  • Comment 63 words added, 1 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, audiodevice.js). Osc.js is a library that describes a class for an oscillator, and audiodevice.js describes a class that interacts with the mozAudio API in a way that allows for using callbacks to write the audio.

Let's start with a plan, and go with as simple as it gets: a synth with a virtual midi keyboard, waveshape options and maybe some noise modulation for an extra spicyness.

The code, HTML

So the html (simplesynth.html) should include all our scripts, an iframe for the keyboard, and an option tag to change the waveform.

<!DOCTYPE html>
<html>
	<head>
		<title>Simple synth using the audio api</title>
		<script src="audiodevice.js"></script>
		<script src="osc.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: (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 mush instantiate an AudioDevice with a callback that fill the buffer with data from the Oscillator. Then we need to bind the midi data to control the frequency of the oscillator, bind an event listener to the select to change the waveform and that's pretty much it.

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 define a function we'll use as the callback for the AudioDevice. In it, we iterate through the buffer given and fill it with data from the oscillator, modulated with some random noise.

	function processAudio(buffer){
		var i, l = buffer.length;
		for (i=0; i<l; i++){ // Iterate through the buffer
			oscillator.generate((Math.random() * 2 - 1) * 0.3); // Advance the oscillator angle, add some flavor with Math.random noise.
			buffer[i] = buffer[++i] = oscillator.getMix() * 0.2; // 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.
		}
	}

Next, let's define the midi handling operation for the keyboard. Let's do this in a manner that sets the oscillator's frequency to the previous key pressed.

	function onmidi(e){
		var i;
		if (e.status === 9){ // 0x9, KEYDOWN
			pressedKeys.unshift(e.data1); // Add the newest key to be first in the array.
		} else if (e.status === 8){ // 0x8, KEYUP
			for (i=0; i<pressedKeys.length; i++){ // Iterate through the keys...
				if (pressedKeys[i] === e.data1){
					pressedKeys.splice(i--, 1); // And remove the matching keys.
				}
			}
		} else { // We don't understand anything else here, so we'll just leave.
			return;
		}
		if (pressedKeys.length){ // If there are any pressed keys.
			oscillator.frequency = 440 * Math.pow(1.059, pressedKeys[0] - 69); // Set the oscillator frequency to match the last key pressed
		} else {
			oscillator.frequency = 0;
		}
	};

Now we'll just assign everything, and we're ready to go.

	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

 

<meta http-equiv="content-type" content="text/html; charset=utf-8"/>

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 (osc.js, audiodevice.js). Osc.js is a library that describes a class for an oscillator, and audiodevice.js describes a class that interacts with the mozAudio API in a way that allows for using callbacks to write the audio.</p>
<p>Let's start with a plan, and go with as simple as it gets: a synth with a virtual midi keyboard, waveshape options and maybe some noise modulation for an extra spicyness.</p>
<h2>The code, HTML</h2>
<p>So the html (simplesynth.html) should include all our scripts, an iframe for the keyboard, and an 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="audiodevice.js"&gt;&lt;/script&gt;
		&lt;script src="osc.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>
<h2>CSS</h2>
<p>Then we'll drop in a little css to attach the keyboard to the bottom: (simplesynth.css)</p>
<pre class="brush: css">iframe
{
	position: absolute;
	bottom: 0px;
	width: 100%;
	left: 0px;
	border-width: 0px;
}
</pre>
<h2>JavaScript</h2>
<p>Now we need a plan for the script. We don't really need to do much, we mush instantiate an AudioDevice with a callback that fill the buffer with data from the Oscillator. Then we need to bind the midi data to control the frequency of the oscillator, bind an event listener to the select to change the waveform and that's pretty much it.</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 define a function we'll use as the callback for the AudioDevice. In it, we iterate through the buffer given 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;
		for (i=0; i&lt;l; i++){ // Iterate through the buffer
			oscillator.generate((Math.random() * 2 - 1) * 0.3); // Advance the oscillator angle, add some flavor with Math.random noise.
			buffer[i] = buffer[++i] = oscillator.getMix() * 0.2; // 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.
		}
	}
</pre>
<p>Next, let's define the midi handling operation for the keyboard. Let's do this in a manner that sets the oscillator's frequency to the previous key pressed.</p>
<pre class="brush: js">	function onmidi(e){
		var i;
		if (e.status === 9){ // 0x9, KEYDOWN
			pressedKeys.unshift(e.data1); // Add the newest key to be first in the array.
		} else if (e.status === 8){ // 0x8, KEYUP
			for (i=0; i&lt;pressedKeys.length; i++){ // Iterate through the keys...
				if (pressedKeys[i] === e.data1){
					pressedKeys.splice(i--, 1); // And remove the matching keys.
				}
			}
		} else { // We don't understand anything else here, so we'll just leave.
			return;
		}
		if (pressedKeys.length){ // If there are any pressed keys.
			oscillator.frequency = 440 * Math.pow(1.059, pressedKeys[0] - 69); // Set the oscillator frequency to match the last key pressed
		} else {
			oscillator.frequency = 0;
		}
	};
</pre>
<p>Now we'll just assign everything, and we're ready to go.</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></p>
<p>
 </p>&lt;meta http-equiv="content-type" content="text/html; charset=utf-8"/&gt;
Revert to this revision