Simple Synthesis - VCO/VCA

An addendum to Emmett Corman's Modular Synthesis posts on www.keithmcmillen.com/blog/

by Keith McMillen

HTML

<script src="https://files.keithmcmillen.com/lib/js/OscilloscopeLabs.js"></script>
<script src="https://files.keithmcmillen.com/blog/simple-synthesis/js/Oscilloscope.js"></script>
<div id="content">
    <canvas id="oscilloscope"></canvas>
    <div id="controls">
        <label for='waveType'>VCO type
            <select name="waveType" class="wavetype">
                <option value="sine">sine</option>
                <option value="square">square</option>
                <option value="sawtooth">sawtooth</option>
                <option value="triangle">triangle</option>
            </select>
        </label>
        <br />
        <label for='portamento'>Portamento
            <input type='checkbox' class='portamento' name='portamento' checked />
        </label>
        <br>
        <label for='masterGain'>master gain <span class='master-gain-display'>0.5</span>

            <input class='master-gain' type='range' min='0' max='1' step='0.1' value='0.5' />
        </label>
        <hr>
    </div>
    <div id="controller">
        <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 296 203" enable-background="new 0 0 296 203" xml:space="preserve">
            <g id="kboard">
                <g>
                    <path fill="#282828" d="M277.2,135.9H18.8c-1.1,0-2-0.9-2-2V69.1c0-1.1,0.9-2,2-2h258.5c1.1,0,2,0.9,2,2v64.8
			C279.2,135,278.4,135.9,277.2,135.9z" />
                    <path class='key c0 key48' fill="#F1F1F2" d="M68.8,128.9H57.3c-0.3,0-0.6-0.3-0.6-0.6v-24.6c0-0.3,0.3-0.6,0.6-0.6h11.5c0.3,0,0.6,0.3,0.6,0.6v24.6
			C69.3,128.7,69.1,128.9,68.8,128.9z" />
                    <path class='key sharp c-0 key49' fill="#F1F1F2" d="M75.1,99.6H63.6c-0.3,0-0.6-0.3-0.6-0.6V74.4c0-0.3,0.3-0.6,0.6-0.6h11.5c0.3,0,0.6,0.3,0.6,0.6V99
			C75.7,99.3,75.4,99.6,75.1,99.6z" />
                    <path class='key d0 key50' fill="#F1F1F2"...

CSS

* {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

*::before,
*::after {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

body {
  background-color: rgb(250, 250, 250);
}

button {
  display: inline-block;
  margin-bottom: 20px;
}

label {
  display: block;
  font-family: monospace;
  line-height: 20px;
}

input {
  display: inline-block;
  float: right;
  width: 45%;
}

span {
  display: inline-block;
  width: 50px;
  text-align: center;
  font-weight: bold;
  border: 1px solid #efefef;
  border-radius: 3px;
  background-color: white;
}

hr {
  border-color: rgba(200, 200, 200, 0.1);
}

#content {
  position: relative;
  max-width: 800px;
  margin: 0 auto;
  padding-top: 30px;
}

#oscilloscope {
  margin-bottom: 20px;
}

#controller {
  overflow: hidden;
}

#controller svg {
  position: relative;
  top: -55px;
}

@media (min-width: 640px) {
  #controller svg {
    top: -125px;
  }
}

.key {
  cursor: pointer;
}

.key:hover,
.key.active {
  fill: #00ADEE;
}

JavaScript

/* 
 * Patch from the VCO output into the VCA signal input
 * Patch from the Keyboard note output into the VCO CV input
 * Patch from the VCA output into a speaker
 * Patch from the keyboard gate output into the VCA CV input
 */

// set up our Audio Context
var audioContext = new(window.AudioContext || window.webkitAudioContext);
console.log('audioContext', audioContext)
// set up an oscillator, gain, and master gain
var vco = audioContext.createOscillator(),
    vca = audioContext.createGain(),
    master = audioContext.createGain();

// keep track of active keys
var activeKeys = {};

// setup controls
var waveType = document.querySelector('.wavetype'),
    portamento = document.querySelector('.portamento'),
    enablePortamento = true,
    masterGain = document.querySelector('.master-gain'),
    masterGainDisplay = document.querySelector('.master-gain-display');

// connect all modules together and to the master gain, our speaker

// Step 1: Patch from the VCO output into the VCA signal input
vco.connect(vca); // connect oscillator to gain

// Step A (added): Patch from the VCA putput to the master VCA input
vca.connect(master); // connect gain to master gain

// Step 3: Patch from the master VCA output into a speaker
master.connect(audioContext.destination); // connect master gain to speakers

// start the vco
vco.start(0);

// set initial gain values
vca.gain.value = 0; // set gain to 0, a keypress will set it to 1
master.gain.value = 1; // master gain at full volume

// Controls
waveType.onchange = function () {
    changeWaveType(waveType.value);
}

portamento.onchange = function () {
    enablePortamento = portamento.checked;
}

masterGain.oninput = function () {
    changeMaster(masterGain.value);
}

function changeWaveType(type) {
    vco.type = type;
}

function changeMaster(vol) {
    master.gain.value = vol;
    masterGainDisplay.innerHTML = vol;
}

// helper functions
function frequencyFromNote(note) {
    return 440 * Math.pow(2, (note - 69) /...