For this lab you will build your own keyboard on the web using WebAudio. This assignment mostly serves give you a familiarity with the basic workflow and structure of working with WebAudio.
This lab should be turned in on Canvas by Sunday at 8pm EST.
To get started, we will build a simple keyboard interface that plays a single note.
To start, we initialize an audio context. We setup a gain node, and give ourselves a bit of room to avoid clipping
document.addEventListener("DOMContentLoaded", function(event) {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
We need a map from keys to frequencies. The one provided below is a starting point, though you might wish to customize the mapping.
const keyboardFrequencyMap = {
'90': 261.625565300598634, //Z - C
'83': 277.182630976872096, //S - C#
'88': 293.664767917407560, //X - D
'68': 311.126983722080910, //D - D#
'67': 329.627556912869929, //C - E
'86': 349.228231433003884, //V - F
'71': 369.994422711634398, //G - F#
'66': 391.995435981749294, //B - G
'72': 415.304697579945138, //H - G#
'78': 440.000000000000000, //N - A
'74': 466.163761518089916, //J - A#
'77': 493.883301256124111, //M - B
'81': 523.251130601197269, //Q - C
'50': 554.365261953744192, //2 - C#
'87': 587.329535834815120, //W - D
'51': 622.253967444161821, //3 - D#
'69': 659.255113825739859, //E - E
'82': 698.456462866007768, //R - F
'53': 739.988845423268797, //5 - F#
'84': 783.990871963498588, //T - G
'54': 830.609395159890277, //6 - G#
'89': 880.000000000000000, //Y - A
'55': 932.327523036179832, //7 - A#
'85': 987.766602512248223, //U - B
}
Next we add listeners to the keys. These will add and remove activeOscillators
.
window.addEventListener('keydown', keyDown, false);
window.addEventListener('keyup', keyUp, false);
activeOscillators = {}
function keyDown(event) {
const key = (event.detail || event.which).toString();
if (keyboardFrequencyMap[key] && !activeOscillators[key]) {
playNote(key);
}
}
function keyUp(event) {
const key = (event.detail || event.which).toString();
if (keyboardFrequencyMap[key] && activeOscillators[key]) {
activeOscillators[key].stop();
delete activeOscillators[key];
}
}
Now we need a way to playNote(key)
, which will actually start the sound. For this, we start an oscillator, set the desired properties, and connect the new oscillator to the the audioCtx.destination
.
function playNote(key) {
const osc = audioCtx.createOscillator();
osc.frequency.setValueAtTime(keyboardFrequencyMap[key], audioCtx.currentTime)
osc.type = 'sine' //choose your favorite waveform
osc.connect(audioCtx.destination)
osc.start();
activeOscillators[key] = osc
}
If you would like further details, refer to the below tutorials. Be warned, these are good for reference, but may lead you a bit astray for the purposes of this lab:
https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Simple_synth
This will give you a really awful keyboard made with WebAudio.
Once you have the basic keyboard working, you have a few extra challenges to pursue
1) Allow the user to choose a waveform from between sine and sawtooth, at least. (1 pt)
2) Implement ADSR envelopes for your notes you you don’t get zero-ing clicks. You will need to add a gain node for this. It will be in between the osc and the audioCtx.
const globalGain = audioCtx.createGain();
globalGain.gain.setValueAtTime(0.8, audioCtx.currentTime)
globalGain.connect(audioCtx.destination);
//...
osc.connect(gainNode).connect.(audioCtx.destination)
In practice, it is the release part of ADSR that causes the most problems, so focus on that first, then tackle the rest of the envelope. The shape of the envelope can be hard-coded. You will want to explore the exponentialRampToValueAtTime()
function (HINT: read the documentation carefully. If you want a reference for a gorgeous front end keyboard that does not address this issue, see: https://oscillator.js.org/ As an expectation calibration, this course will not teach or expect anything like this frontend, but will demand a higher standard of audio. (3 pts)
3) Try playing two notes at once. It sounds awful right? Now we enable polyphonic mode - allowing the user to play two keys at once. To do this, we need to make sure you are not “clipping” (no amp levels >1). (HINT: you may need to control the gain of each oscillator independently). You do not need to support more than 2 voices, but it is likely a good solution will be easy generalize anyway. (3 pts)
Do something interesting with your keyboard. This is the creative part. Maybe playing a note sometimes randomly plays 2 notes at once? Maybe the notes you play change the background color of the page? The sky is the limit - make this your own.
NOTE: The 1 point for this part of the assignment is awarded completely subjectively. You are working in a creative domain - get comfortable working with underspecified constraints and arbitrary judgement.