In part seven, we’ll be adding an another oscillator to the synthesizer. We’ll end up with two, but you can use this logic to add as many oscillators as you want. Having multiple oscillators creates a lot of sonic potential for the instrument.
This tutorial is part seven in a series that will show you how to create a virtual synthesizer using JavaScript and the Web Audio API. Click here to clone or download the working code examples from the GitHub repository. Click here to view the finished product that uses the concepts and code discussed in this series. To get a better idea of the complete process, checkout the overview.
Add Second Oscillator Elements
First we’re going to replace the HTML that contained the single oscillator with new code that has two oscillator dropdowns. This will add the on/off toggle and waveforms select elements that allow the user to change the oscillator options. We’re going to default them to different waveforms, so that the synth’s initial sound is more interesting.
<div class="row">
<div class="col-12 col-sm-6">
<div class="section osc">
<p class="label">Oscillator 1<input class="oscOn" data-osc="osc1" type="checkbox" checked data-toggle="toggle" data-size="xs"></p>
<div class="input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text" for="waveType1">Waveform</label>
</div>
<select class="custom-select waveType" id="waveType1">
<option value="sine">Sine</option>
<option value="square">Square</option>
<option value="triangle">Triangle</option>
<option value="sawtooth" selected>Sawtooth</option>
</select>
</div>
</div>
</div>
<div class="col-12 col-sm-6">
<div class="section osc">
<p class="label">Oscillator 2<input class="oscOn" data-osc="osc2" type="checkbox" data-toggle="toggle" data-size="xs" checked></p>
<div class="input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text" for="waveType2">Waveform</label>
</div>
<select class="custom-select waveType" id="waveType2">
<option value="sine">Sine</option>
<option value="square" selected>Square</option>
<option value="triangle">Triangle</option>
<option value="sawtooth">Sawtooth</option>
</select>
</div>
</div>
</div>
</div>
Add Bootstrap Toggle Dependencies
This synthesizer uses Bootstrap Toggle to create the oscillator on/off buttons. We need to the add the CSS and JavaScript dependencies:
<link href="https://cdn.jsdelivr.net/gh/gitbrent/[email protected]/css/bootstrap4-toggle.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/gitbrent/[email protected]/js/bootstrap4-toggle.min.js" crossorigin="anonymous"></script>
Update the Synthesizer JavaScript
Now we need to update the synthesizer JavaScript to use this second oscillator.
Add Second Oscillator to Synth Settings Object
Currently, the synth settings object looks like this:
// Synth settings object
var synth = {
osc: {
on: true,
type: 'sawtooth',
},
octave: 3,
// Values in Seconds
attack: .005,
hold: .15,
decay: .1,
sustain: 40, // Gain
release: 1,
}
We need to add a second oscillator and on/off state for each oscillator. The new settings object looks like this:
// Synth settings object
var synth = {
osc1: {
on: true,
type: 'sawtooth',
detune: 0 // In cents
},
osc2: {
on: true,
type: 'square',
detune: 10 // In cents
},
octave: 3,
// Values in Seconds
attack: .005,
hold: .15,
decay: .1,
sustain: 40, // Gain
release: 1,
}
Update the Voice Class
Now we need to update the constructor, start, and stop functions of the Voice class.
Constructor
First we’ll change the constructor. Currently, the oscillator is stored as a property of the Voice object – this.oscillator. We’re going to to do the same thing for each oscillator, e.g. this.oscillator1, this.oscillator2, this.oscillator3. Like I mentioned before, you can add as many oscillators as you’d like.
We’re also going to set the frequency of the oscillators in the start function instead of the constructor. This is just to group together all the logic that uses the now variable. The new constructor looks like this:
constructor(rawNote, kbNote) {
this.frequency = 0;
var octave = synth.octave;
if (octave === 1) {
this.frequency = hzs[rawNote];
} else if (octave === 2) {
this.frequency = hzs[rawNote] * 2;
} else if (octave === 3) {
this.frequency = hzs[rawNote] * 4;
} else if (octave === 4) {
this.frequency = hzs[rawNote] * 8;
} else if (octave === 5) {
this.frequency = hzs[rawNote] * 16;
} else if (octave === 6) {
this.frequency = hzs[rawNote] * 32;
} else if (octave === 7) {
this.frequency = hzs[rawNote] * 64;
}
this.kbNote = kbNote;
this.oscillator1 = audioCtx.createOscillator();
this.oscillator2 = audioCtx.createOscillator();
this.oscillator1.type = synth.osc1.type;
this.oscillator2.type = synth.osc2.type;
}
Start
In the start function, we set the frequency of each oscillator and connect it to the gain node. Lastly, we’ll call the start function of each oscillator. Here’s the fully updated start function.
start() {
var now = audioCtx.currentTime;
this.voiceGain = audioCtx.createGain();
this.oscillator1.frequency.setValueAtTime(this.frequency, now); // value in hertz
this.oscillator2.frequency.setValueAtTime(this.frequency, now); // value in hertz
this.oscillator1.connect(this.voiceGain);
this.oscillator2.connect(this.voiceGain);
this.oscillator1.connect(this.voiceGain);
this.oscillator2.connect(this.voiceGain);
this.voiceGain.gain.setValueAtTime(0, now);
// Attack
this.voiceGain.gain.linearRampToValueAtTime(1, now + synth.attack);
this.voiceGain.gain.setValueAtTime(1, now + synth.attack);
// Hold
this.voiceGain.gain.linearRampToValueAtTime(1, now + synth.attack + synth.hold);
// Decay and sustain
this.voiceGain.gain.linearRampToValueAtTime(synth.sustain, now + synth.attack + synth.hold + synth.decay);
this.voiceGain.connect(pregainNode); // Connecting to output
if (synth.osc1.on) this.oscillator1.start(0);
if (synth.osc2.on) this.oscillator2.start(0);
}
Stop
In the stop function, we just need to update the function to call stop on all the oscillators:
stop() {
this.voiceGain.gain.linearRampToValueAtTime(.01, audioCtx.currentTime + synth.release);
if (synth.osc1.on) this.oscillator1.stop(audioCtx.currentTime + synth.release);
if (synth.osc2.on) this.oscillator2.stop(audioCtx.currentTime + synth.release);
}
Detect On/Off Status of Oscillators
Now that there are two oscillators, we need to detect if one, both, or neither are on. To do that, we’ll add an event listener to the on/off toggle input elements, and then pass those values to the synth settings object. The names of the oscillators (‘osc1’ and ‘osc2’) are stored as the ‘data-osc’ attribute on the on/off toggle elements.
$('.oscOn').change(function() {
// Set the osc on/off to true/false based on which toggle was clicked
synth[$(this).attr('data-osc')].on = this.checked;
});
The last thing to do is update the waveform of the oscillators on the synth settings object when the user changes the waveform.
$('.waveType').change(function() {
synth.osc1.type = $(this).val();
synth.osc2.type = $(this).val();
});
Having more than one oscillator on your synth gives you much more potential in the variety of sounds you can create. Enjoy!