Ultrasonic Modem

Uses Web Audio API to emit a FM ultrasonic signal, and demodulates it using a pair of Goertzel filters.

HTML

<p>Microphone permission is required for this demo.</p>
<p>Open your browser's console (ctrl+shift+J for Chrome, ctrl+shift+K for Firefox), and click on the "Pulse" button. It will output an ultrasonic sound through your speakers, which will captured by your microphone, decoded to binary using JavaScript, and logged to the console.</p>

CSS

body {
    background-color: #333;
    color: #eee;
    font-family: Verdana, sans-serif;
}

JavaScript

window.AudioContext = window.AudioContext || window.webkitAudioContext;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

function createGoertzelFilter(context, freq) {
    
    if (typeof freq !== "number") throw new Error("Need frequency!");
    
    var
    w = 2*Math.PI * freq / context.sampleRate,
    wr = Math.cos(w),
    wi = Math.sin(w),
    coeff = 2 * wr,
    g = context.createScriptProcessor(256, 1, 1);
    
    g.wr = wr;
    g.wi = wi;
    g.coeff = coeff;
    
    g.onaudioprocess = function(e) {
        var
        inp = e.inputBuffer.getChannelData(0),
        N = inp.length,
        coeff = this.coeff,
        s1 = 0,
        s2 = 0;
        
        for (var n = 0; n < N; n+=2) {
            s2 = inp[n] + coeff * s1 - s2;
            s1 = inp[n+1] + coeff * s2 - s1;
        }
        
        var
        XKr = s1 * this.wr - s2,
        XKi = s1 * this.wi,
        res = (XKr*XKr + XKi*XKi) / N;
        
        this.res = res;
    };

    return g;
}

var fCutoff = 10000;
var fLow = 11000;
var fHigh =11375;
var dtms = 50;

var context = new AudioContext;
var filter = context.createBiquadFilter();
var gLow = createGoertzelFilter(context, fLow);
var gHigh = createGoertzelFilter(context, fHigh);

filter.type = "highpass";
filter.frequency.value = fCutoff;

navigator.getUserMedia({audio: true, video: false},
    function(stream) {
        var mic = context.createMediaStreamSource(stream);
        mic.connect(filter);
    },
    (console.warn || console.log).bind(console)
);

filter.connect(gLow);
filter.connect(gHigh);
gLow.connect(context.destination);
gHigh.connect(context.destination);

var osc = context.createOscillator();

osc.frequency.value = 0;
osc.start(0);
osc.connect(context.destination);

function demodValue() {
    var val = -1;
    var thresh = 3.e-6;
    if (gLow.res > thresh || gHigh.res > thresh) {
        val = gHigh.res > gLow.res ? 1 : 0;
        console.log(val);
   ...