This is simply a more complete / working demo of the one Mozilla have made, it acts as simple demostration of the WebAudio API.
The theremin is an oscillator for a given wave function, controlled by the dropdown. The gain (volume) and frequency of the oscillator are controlled by the X and Y position of the mouse pointer over the canvas below. The frequency and gain (X and Y axis) are then used to render and simple visualisation of the current sound onto the canvas.
The whole thing comes in at a little over 100 lines of pure js
and a couple of
lines HTML
.
Markup:
<button class="mute">
mute
</button>
<button class="reset">
reset
</button>
<select class="type">
<option value="sine">sine</option>
<option value="square">square</option>
<option value="sawtooth">sawtooth</option>
<option value="triangle">triangle</option>
</select>
<div>
<canvas style="border:1px solid #000000;"></canvas>
</div>
Code:
var audioCtx = new(window.AudioContext ||
window.webkitAudioContext)();
var oscillator = audioCtx.createOscillator(),
gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
function offset(side, el){
if(!el)
return 0;
return el[side] + offset(side, el.offsetParent);
}
var row = document.querySelector('div.row'),
canvas = document.querySelector('canvas'),
canvasCtx = canvas.getContext('2d'),
WIDTH = row.offsetWidth,
HEIGHT = window.innerHeight,
OFFSETLEFT = offset('offsetLeft', canvas),
OFFSETTOP = offset('offsetTop', canvas),
maxFreq = 6000,
maxVol = 1,
initialFreq = 3000,
initialVol = 0.5;
// set options for the oscillator
oscillator.type = 'sine';
oscillator.frequency.value = initialFreq; // value in hertz
oscillator.start();
gainNode.gain.value = initialVol;
canvas.width = WIDTH;
canvas.height = HEIGHT;
// Mouse pointer coordinates
var CurX;
var CurY;
// Get new mouse pointer coordinates when mouse is moved
// then set new gain and pitch values
canvas.onmousemove = updatePage;
function updatePage(e) {
CurX = ((window.Event) ? e.pageX : event.clientX +
(document.documentElement.scrollLeft ?
document.documentElement.scrollLeft : document.body.scrollLeft)) -
OFFSETLEFT;
CurY = ((window.Event) ? e.pageY : event.clientY +
(document.documentElement.scrollTop ?
document.documentElement.scrollTop : document.body.scrollTop)) -
OFFSETTOP;
oscillator.frequency.value = (CurX / WIDTH) * maxFreq;
gainNode.gain.value = (CurY / HEIGHT) * maxVol;
canvasDraw();
}
function random(number1, number2) {
var randomNo = number1 +
(Math.floor(Math.random() * (number2 - number1)) + 1);
return randomNo;
}
// Render the canvas animation
function canvasDraw() {
rX = CurX;
rY = CurY;
rC = Math.floor((gainNode.gain.value / maxVol) * 30);
canvasCtx.globalAlpha = 0.2;
for (i = 1; i <= 15; i = i + 2) {
canvasCtx.beginPath();
canvasCtx.fillStyle = 'rgb(' + 100 + (i * 10) + ',' +
Math.floor((gainNode.gain.value / maxVol) * 255) + ',' +
Math.floor((oscillator.frequency.value / maxFreq) * 255) + ')';
canvasCtx.arc(rX + random(0, 50), rY + random(0, 50), rC / 2 + i,
(Math.PI / 180) * 0, (Math.PI / 180) * 360, false);
canvasCtx.fill();
canvasCtx.closePath();
}
}
var mute = document.querySelector('button.mute');
mute.onclick = function(e) {
if (mute.id == "") {
gainNode.disconnect(audioCtx.destination);
mute.id = "activated";
mute.innerHTML = "Unmute";
} else {
gainNode.connect(audioCtx.destination);
mute.id = "";
mute.innerHTML = "Mute";
}
};
var type = document.querySelector('select');
type.onchange = function(e) {
oscillator.type = type.value;
};
var reset = document.querySelector('button.reset');
reset.onclick = function(e) {
oscillator.type = 'sine';
type.value = 'sine';
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
};