Skip to content

Instantly share code, notes, and snippets.

@luciferous
Created July 7, 2011 01:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save luciferous/1068738 to your computer and use it in GitHub Desktop.
Save luciferous/1068738 to your computer and use it in GitHub Desktop.
Beep!

Make your browser beep

Step 1. Include beep.js

<script type="text/javascript" src="beep.js"></script>

Step 2. Beep!

<script type="text/javascript">
    new Beep(22050).play(1000, 1, [Beep.utils.amplify(8000)]);
</script>

Step 3. That's all!

Nothing here.

API

Read the source for more detail.

Constructor

new Beep(number:samplingrate) -> Beep

Makes Beeps.

Methods

#generate(number:samplingrate) -> [number:sample]

Generates a list of numbers representing samples of the sine wave.

#encode(number:frequency, number:duration, [function:filters]) -> string:wavencode

Repeats the sine samples to fulfill a specified duration and encodes them to a WAV format.

#play(number:frequency, number:duration, [function:filters])

Generates and encodes a sine wave and tries to play it with the <audio> element.

/**
* Copyright 2010 Neuman Vong. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*/
(function(ns) {
var utils = {
amplify: function(gain) {
return function(sample) {
return sample * gain;
};
},
ushort: function(sample) {
return String.fromCharCode(255 & sample,
255 & sample >> 8);
},
ulong: function(sample) {
return String.fromCharCode(255 & sample,
255 & sample >> 8,
255 & sample >> 16,
255 & sample >> 24);
},
gcd: function(a, b) {
while (b) {
var a_ = a;
a = b, b = a_ % b;
}
return a;
},
lcm: function(a, b) {
return Math.floor(a * b / utils.gcd(a, b));
},
lcmm: function() {
return utils.foldl(utils.lcm, Array.prototype.slice.call(arguments));
},
compose: function(fns) {
return function(a) {
for (var i = 0; i < fns.length; i++) {
a = fns[i](a);
}
return a;
};
},
map: function(fn, items) {
var result = [];
for (var i = 0; i < items.length; i++) {
result.push(fn.call(this, items[i]));
}
return result;
},
getattr: function(attr) {
return function(items) {
return items[attr];
};
},
zip: function() {
if (arguments.length == 0) return [];
var lists = Array.prototype.slice.call(arguments);
var result = [];
var min = Math.min.apply(null, utils.map(utils.getattr("length"), lists));
for (var i = 0; i < min; i++) {
result.push(utils.map(utils.getattr(i), lists));
}
return result;
},
sum: function(numbers) {
return utils.foldl(function(a, b) { return a + b; }, numbers);
},
bind: function(ctx, fn) {
return function() {
var args = Array.prototype.slice.call(arguments);
return fn.apply(ctx, args);
};
},
foldl: function(fn, items) {
if (items.length == 1) return items[0];
var result = fn(items[0], items[1]);
for (var i = 2; i < items.length; i++) {
result = fn(result, items[i]);
}
return result;
},
lmul: function(n, items) {
var result = [].concat(items);
for (var i = 1; i < n; i++) {
result = result.concat(items);
}
return result;
},
mulmod: function(a, b, c) {
return (a * b) % c;
},
range: function(len) {
var result = [];
for (var i = 0; i < len; i++) {
result.push(i);
}
return result;
}
};
function Beep(samplerate) {
if (!(this instanceof Beep)) return new Beep(samplerate);
if (typeof samplerate != "number" || samplerate < 1) return null;
this.channels = 1;
this.bitdepth = 16;
this.samplerate = samplerate;
this.sine = [];
var factor = (2 * Math.PI) / parseFloat(samplerate);
for (var n = 0; n < samplerate; n++) {
this.sine.push(Math.sin(n * factor));
}
}
Beep.prototype = {
generate: function(freqs) {
freqs = freqs instanceof Array ? freqs : [freqs];
var map = utils.bind(this, utils.map);
var periods = map(function(a) {
return utils.lcm(this.samplerate, a) / a; }, freqs);
var lcm = utils.foldl(utils.lcm, periods);
var sample = function(time) {
return function(freq) {
return this.sine[utils.mulmod(time, freq, this.samplerate)];
};
};
return map(function(t) { return utils.sum(map(sample(t), freqs)); },
utils.range(lcm));
},
encode: function(freq, duration, filters) {
freqs = freqs instanceof Array ? freqs : [freqs];
var transforms = utils.compose(
[utils.sum].concat(filters || []).concat([utils.ushort]));
var samples = utils.map(transforms, utils.zip.apply(null, this.generate(freqs)));
var reps = Math.ceil(duration * this.samplerate / samples.length);
var fulldata = new Array(reps + 1).join(samples.join(""));
var data = fulldata.substr(0, this.samplerate * duration * 2);
var fmtChunk = [
["f", "m", "t", " "].join(""),
utils.ulong(Beep.PCM_CHUNK_SIZE),
utils.ushort(Beep.LINEAR_QUANTIZATION),
utils.ushort(this.channels),
utils.ulong(this.samplerate),
utils.ulong(this.samplerate * this.channels * this.bitdepth / 8),
utils.ushort(this.bitdepth / 8),
utils.ushort(this.bitdepth)
].join("");
var dataChunk = [
["d", "a", "t", "a"].join(""),
utils.ulong(data.length * this.channels * this.bitdepth / 8),
data
].join("");
var header = [
["R", "I", "F", "F"].join(""),
utils.ulong(4 + (8 + fmtChunk.length) + (8 + dataChunk.length)),
["W", "A", "V", "E"].join("")
].join("");
return [header, fmtChunk, dataChunk].join("");
},
play: function(freq, duration, filters) {
filters = filters || [];
var data = btoa(this.encode(freq, duration, filters));
var audio = document.createElement("audio");
audio.src = "data:audio/x-wav;base64," + data;
audio.play();
}
};
Beep.LINEAR_QUANTIZATION = 1;
Beep.PCM_CHUNK_SIZE = 16;
Beep.utils = utils;
ns.Beep = Beep;
})(window["NS_BEEP"] || window);
@luciferous
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment