Theremin

a wave-morphing theremin

by Wray Bowling

HTML

<svg>
    <circle cx="50%" cy="50%" r="20%"/>
    <circle cx="50%" cy="50%" r="40%"/>
    <circle cx="50%" cy="50%" r="60%"/>
    <polyline id="scope" points=""/>
    <text id="frequency" x="50%" y="50%">000000000 Hz</text>
</svg>

CSS

body{
    margin:0;
    background-color:black;
}
html,body,svg{
    width:100%;
    height:100%;
}
polyline,circle{
    stroke-width:1px;
    stroke:lime;
    fill:none;
}
text{
    fill:lime;
    font-family:monospace;
    text-anchor:middle;
}

JavaScript

var room = new window.AudioContext() || window.webkitAudioContext() || window.webAudioContext();

		// oscillators
		var osc1 = room.createOscillator();
		osc1.type = 'square';

		var osc2 = room.createOscillator();
		osc2.type = 'sawtooth';

		var osc3 = room.createOscillator();
		osc3.type = 'sine';

		var osc4 = room.createOscillator();
		osc4.type = 'triangle';

        var f = 0;

		// volume
		var amp1 = room.createGain();
		var amp2 = room.createGain();
		var amp3 = room.createGain();
		var amp4 = room.createGain();
		var mix = room.createGain();
		mix.gain.value = 0;

		// wires
		osc1.connect(amp1);
		osc2.connect(amp2);
		osc3.connect(amp3);
		osc4.connect(amp4);
		amp1.connect(mix);
		amp2.connect(mix);
		amp3.connect(mix);
		amp4.connect(mix);
		mix.connect(room.destination);

		// mixing
		function mixing(x,y){
			f = Math.sqrt(Math.pow(x,2) + Math.pow(y,2))*400 + 10;
			osc1.frequency.value = f/2;
			osc2.frequency.value = f/3;
			osc3.frequency.value = f*3;
			osc4.frequency.value = f*2;

			var right = Math.atan2(y,x);
			var left = Math.atan2(y,-x);
			var a;

			// lower right half-sector
			a = (right + Math.PI*0.25)/Math.PI;
			a = Math.min(1.0,a);
			a = Math.max(0.0,a);
			a = 1 - Math.abs((a - 0.5)*2);
			amp1.gain.value = a;

			// top right half-sector
			a = (right - Math.PI*0.25)/Math.PI;
			a = Math.min(0.0,a);
			a = Math.max(-1.0,a);
			a = 1 - Math.abs((a + 0.5)*2);
			amp2.gain.value = a;

			//top left half-sector
			a = (left - Math.PI*0.25)/Math.PI;
			a = Math.min(0.0,a);
			a = Math.max(-1.0,a);
			a = 1 - Math.abs((a + 0.5)*2);
			amp3.gain.value = a;

			// lower right half-sector
			a = (left + Math.PI*0.25)/Math.PI;
			a = Math.min(1.0,a);
			a = Math.max(0.0,a);
			a = 1 - Math.abs((a - 0.5)*2);
			amp4.gain.value = a;
		}
        
        var neverStarted = true;
        function startup(){
            if(neverStarted){
                osc1.start(0);
                osc2.start(0);
                osc3.start(0);
         ...