Skip to content

Commit

Permalink
Some improvements on minimal synth.
Browse files Browse the repository at this point in the history
  • Loading branch information
severak2 committed Sep 28, 2024
1 parent 942eff0 commit 674f4c6
Showing 1 changed file with 24 additions and 15 deletions.
39 changes: 24 additions & 15 deletions minimal-webaudio-synth.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
</head>
<body>
<div class="box">
<form id="form">
<h1>Minimal WebAudio synth</h1>
<p><small>This is example implementation of minimal usable WebAudio synth without any external dependencies.</small></p>
<button id="power">POWER ON</button>
Expand All @@ -51,6 +52,7 @@ <h1>Minimal WebAudio synth</h1>
<option>triangle</option>
</select>
</div>
</form>
<div class="keyboard" id="keyboard">
<div class="key" data-midi="60"></div>
<div class="key black" data-midi="61"></div>
Expand Down Expand Up @@ -79,37 +81,38 @@ <h1>Minimal WebAudio synth</h1>
</div>
</div>
<script>
document.getElementById('form').reset(); // to reset form to initial state on each opening of page

// determines freq of current MIDI note
function midi2cps(midi) {
const A4 = 440;
return A4 * Math.pow(2, (midi - 69) / 12);
}

// only complex piece of - moving AudioParams
// only complex piece of machinery - slowly moving AudioParams
function moveAudioParamTo(ac, audioParam, target, time)
{
var curr = audioParam.value;
var now = ac.currentTime;
audioParam.cancelScheduledValues(now); // you need to stop any scheduled values frist
var curr = audioParam.value; // first getting current value of it
var now = ac.currentTime; // then current time
audioParam.cancelScheduledValues(now); // you need to stop any scheduled values first
audioParam.setValueAtTime(curr, now); // stop ENV at current point
audioParam.linearRampToValueAtTime(target, now + time); // move to target at now + time
}

document.getElementById('power').addEventListener('click', function (event){ // you need to click on something to be able to construct AudioContext
event.preventDefault(); // to not submit form
document.getElementById('power').setAttribute('disabled', true); // to prevent clicking it again
document.getElementById('power').setAttribute('disabled', 'disabled'); // to prevent clicking it again

let ac = new AudioContext(); // create audio context to work on

let osc = ac.createOscillator(); // oscillator
osc.frequency.value = 440;
osc.type = 'sawtooth';
osc.start(ac.currentTime); // start oscillator
osc.start(ac.currentTime); // start oscillator so it makes some sound

let filter = ac.createBiquadFilter(); // filter
filter.type = 'lowpass';
filter.frequency.setValueAtTime(0, ac.currentTime);
filter.frequency.linearRampToValueAtTime(20000, ac.currentTime + 0.5);
filter.frequency.value = 20000;

let vca = ac.createGain(); // VCA
vca.gain.value = 0;
Expand All @@ -127,17 +130,24 @@ <h1>Minimal WebAudio synth</h1>
});

document.getElementById('keyboard').addEventListener('mouseover', function (event){ // user slide to another
if(event.buttons == 1 || event.buttons == 3){ // but only if they have pressed button before
if (event.buttons == 1 || event.buttons == 3){ // but only if they have pressed button before
if (!event.target.hasAttribute('data-midi')) return; // to prevent triggering from 1px left border with no data-midi
moveAudioParamTo(ac, osc.frequency, midi2cps(event.target.getAttribute('data-midi')), 0.001); // change OSC freq
}
});

document.getElementById('keyboard').addEventListener('mouseup', function (event){ // user depressed an key (mouse up)
moveAudioParamTo(ac, vca.gain, 0, 0.001); // close VCA
moveAudioParamTo(ac, vca.gain, 0, 0.5); // close VCA
});

document.getElementById('keyboard').addEventListener('mouseleave', function (event){ // user moved out of keyboard
moveAudioParamTo(ac, vca.gain, 0, 0.001); // close VCA
document.getElementById('keyboard').addEventListener('mouseleave', function (event){ // user moved mouse out of the keyboard
moveAudioParamTo(ac, vca.gain, 0, 0.5); // close VCA
});

document.getElementById('keyboard').addEventListener('mouseenter', function (event){ // user moved mouse into the keyboard
if(event.buttons == 1 || event.buttons == 3) { // so if they have pressed button before
moveAudioParamTo(ac, vca.gain, 1, 0.001); // open VCA
}
});

// volume handling
Expand All @@ -147,9 +157,8 @@ <h1>Minimal WebAudio synth</h1>

// moving filter
document.getElementById('cutoff').addEventListener('input', function (event){

// filter.frequency.setValueAtTime(event.target.value, ac.currentTime); // <- this should work, but doesn't. Why?
moveAudioParamTo(ac, filter.frequency, event.target.value, 1); // This only works when time > 1, else filter is stuck. Why?
// filter.frequency.setValueAtTime(event.target.value, ac.currentTime); // <- This should work in Chrome 109 but doesn't. Why? In Firefox 115 this is OK.
moveAudioParamTo(ac, filter.frequency, event.target.value, 1); // This only works but only when time > 1 or filter is stuck. Why?
});

// changing waveform
Expand Down

0 comments on commit 674f4c6

Please sign in to comment.