Skip to content

Commit

Permalink
first commit - moved project over from "guy"
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcoles committed May 16, 2012
0 parents commit 6ae8ae7
Show file tree
Hide file tree
Showing 6 changed files with 1,211 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
HTML5 JavaScript Piano
======================

This is an HTML5 synth piano that generates all of its audio files on the fly. See the [HTML5 JavaScript Piano](http://mrcoles.com/piano/) live.
198 changes: 198 additions & 0 deletions audio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@

// paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
window.log = function f(){ log.history = log.history || []; log.history.push(arguments); if(this.console) { var args = arguments, newarr; args.callee = args.callee.caller; newarr = [].slice.call(args); if (typeof console.log === 'object') log.apply.call(console.log, console, newarr); else console.log.apply(console, newarr);}};
// make it safe to use console.log always
(function(a){function b(){}for(var c="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","),d;!!(d=c.pop());){a[d]=a[d]||b;}})
(function(){try{console.log();return window.console;}catch(a){return (window.console={});}}());


(function() {

function asBytes(value, bytes) {
// Convert value into little endian hex bytes
// value - the number as a decimal integer (representing bytes)
// bytes - the number of bytes that this value takes up in a string

// Example:
// asBytes(2835, 4)
// > '\x13\x0b\x00\x00'
var result = [];
for (; bytes>0; bytes--) {
result.push(String.fromCharCode(value & 255));
value >>= 8;
}
return result.join('');
}

var DataGenerator = $.extend(function(styleFn, volumeFn, cfg) {
cfg = $.extend({
freq: 440,
volume: 32767,
sampleRate: 2024, // Hz
seconds: .5,
channels: 1
}, cfg);

var data = [];
var maxI = cfg.sampleRate * cfg.seconds;
for (var i=0; i < maxI; i++) {
for (var j=0; j < cfg.channels; j++) {
data.push(
asBytes(
volumeFn(
styleFn(cfg.freq, cfg.volume, i, cfg.sampleRate, cfg.seconds, maxI),
cfg.freq, cfg.volume, i, cfg.sampleRate, cfg.seconds, maxI
), 2
)
);
}
}
return data;
}, {
style: {
wave: function(freq, volume, i, sampleRate, seconds) {
// wave
// i = 0 -> 0
// i = (sampleRate/freq)/4 -> 1
// i = (sampleRate/freq)/2 -> 0
// i = (sampleRate/freq)*3/4 -> -1
// i = (sampleRate/freq) -> 0
return Math.sin((2 * Math.PI) * (i / sampleRate) * freq);
},
squareWave: function(freq, volume, i, sampleRate, seconds, maxI) {
// square
// i = 0 -> 1
// i = (sampleRate/freq)/4 -> 1
// i = (sampleRate/freq)/2 -> -1
// i = (sampleRate/freq)*3/4 -> -1
// i = (sampleRate/freq) -> 1
var coef = sampleRate / freq;
return (i % coef) / coef < .5 ? 1 : -1;
},
triangleWave: function(freq, volume, i, sampleRate, seconds, maxI) {
return Math.asin(Math.sin((2 * Math.PI) * (i / sampleRate) * freq));
},
sawtoothWave: function(freq, volume, i, sampleRate, seconds, maxI) {
// sawtooth
// i = 0 -> -1
// i = (sampleRate/freq)/4 -> -.5
// i = (sampleRate/freq)/2 -> 0
// i = (sampleRate/freq)*3/4 -> .5
// i = (sampleRate/freq) - delta -> 1
var coef = sampleRate / freq;
return -1 + 2 * ((i % coef) / coef);
}
},
volume: {
flat: function(data, freq, volume) {
return volume * data;
},
linearFade: function(data, freq, volume, i, sampleRate, seconds, maxI) {
return volume * ((maxI - i) / maxI) * data;
},
quadraticFade: function(data, freq, volume, i, sampleRate, seconds, maxI) {
// y = -a(x - m)(x + m); and given point (m, 0)
// y = -(1/m^2)*x^2 + 1;
return volume * ((-1/Math.pow(maxI, 2))*Math.pow(i, 2) + 1) * data;
}
}
});
DataGenerator.style.default = DataGenerator.style.wave;
DataGenerator.volume.default = DataGenerator.volume.linearFade;


function toDataURI(cfg) {

cfg = $.extend({
channels: 1,
sampleRate: 2024, // Hz
bitDepth: 16, // bits/sample
seconds: .5,
volume: 20000,//32767,
freq: 440
}, cfg);

//
// Format Sub-Chunk
//

var fmtChunk = [
'fmt ', // sub-chunk identifier
asBytes(16, 4), // chunk-length
asBytes(1, 2), // audio format (1 is linear quantization)
asBytes(cfg.channels, 2),
asBytes(cfg.sampleRate, 4),
asBytes(cfg.sampleRate * cfg.channels * cfg.bitDepth / 8, 4), // byte rate
asBytes(cfg.channels * cfg.bitDepth / 8, 2),
asBytes(cfg.bitDepth, 2)
].join('');

//
// Data Sub-Chunk
//

var sampleData = DataGenerator(
cfg.styleFn || DataGenerator.style.default,
cfg.volumeFn || DataGenerator.volume.default,
cfg);
var samples = sampleData.length;

var dataChunk = [
'data', // sub-chunk identifier
asBytes(samples * cfg.channels * cfg.bitDepth / 8, 4), // chunk length
sampleData.join('')
].join('');

//
// Header + Sub-Chunks
//

var data = [
'RIFF',
asBytes(4 + (8 + fmtChunk.length) + (8 + dataChunk.length), 4),
'WAVE',
fmtChunk,
dataChunk
].join('');

return 'data:audio/wav;base64,' + btoa(data);
}

function noteToFreq(stepsFromMiddleC) {
return 440 * Math.pow(2, (stepsFromMiddleC+3) / 12);
}

var Notes = {
sounds: {},
getDataURI: function(n, cfg) {
cfg = cfg || {};
cfg.freq = noteToFreq(n);
return toDataURI(cfg);
},
getCachedSound: function(n, data) {
var key = n, cfg;
if (data && typeof data == "object") {
cfg = data;
var l = [];
for (var attr in data) {
l.push(attr);
l.push(data[attr]);
}
l.sort();
key += '-' + l.join('-');
} else if (typeof data != 'undefined') {
key = n + '.' + key;
}

var sound = this.sounds[key];
if (!sound) {
sound = this.sounds[key] = new Audio(this.getDataURI(n, cfg));
}
return sound;
},
noteToFreq: noteToFreq
};

window.DataGenerator = DataGenerator;
window.Notes = Notes;
})();
64 changes: 64 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>Piano!</title>
<link rel="stylesheet" type="text/css" href="piano.css" />
</head>
<body>
<div id="content">
<div id="content-inner">
<div id="piano">
<h1>Javascript Piano</h1>
<div class="help show" tabindex="1">
<div class="help-inner">
<div id="synth-settings"></div>
<div class="opts">
<p><strong>Controls:</strong></p>
<p>play using home row &amp; above &nbsp; /</p>
<p>change playable keys: “,” &amp; “.” &nbsp; /</p>
<p>shift keyboard: ← &amp; → <span id="shift"></span></p>
</div>
<div class="opts">
<p><strong>Extras:</strong></p>
<p class="toggle-color toggle hold">Color - c &nbsp; /</p>
<p class="toggle-demo toggle">Demo - m &nbsp; /</p>
<p class="toggle-animate toggle">Visual mode - 8 &nbsp; /</p>
<p class="toggle-looper">Looper - 9 &nbsp; /</p>
<p>Help - 0</p>

<!--
<p class="toggle-looper">Looper - 9 &nbsp; /</p>
<p class="toggle-animate toggle">Visual mode - 8 &nbsp; /</p>
<p class="toggle-demo toggle">Demo - m &nbsp; /</p>
<p class="toggle-color toggle hold">Color - c</p>
-->
</div>
</div>
</div>
<div class="loop" tabindex="2">loop</div>
</div>
</div>
</div>
<div id="below">
<p id="info">info</p>
<p id="top"></p>
<div id="below-inner">
<h2>HTML5 Javascript Piano</h2>
<p>This synth piano is written <em>solely</em> using HTML, JS, and CSS. It contains a small handful of synths with configurable decays, a looper, demo mode, visual mode, and a variety of colors. View controls by hitting the top-right button on the piano.</p>
<p>
It doesn't use a single static audio file, instead it generates them on the fly at the byte level and then converts them to files using the data URI schema. On a similar note, it doesn't use a single image either, just CSS.
You can make pretty much any kind of file with a data URI, such as a
<a href="http://mrcoles.com/low-res-paint/">bitmap file</a> or a
<a href="http://mrcoles.com/favicon-generator/">favicon</a>.
</p>
<p><span title="May 16, 2012">Currently</span> iOS doesn’t support HTML5 audio well enough in the browser to make this work. So, no go on iPhone and iPad, for now.</p>
<p>Various people have been experimenting with audio files and data URIs for a while now. I first came across <a href="http://www.sk89q.com/playground/jswav/">this implementation</a> by sk89q, which was helpful in getting my audio files to work. For future projects, I will probably use an audio api, but currently it’s just <a href="http://caniuse.com/audio-api">firefox &amp; chrome</a>. For further reading, here is a good overview of <a href="http://matt.west.co.tt/music/jasmid-midi-synthesis-with-javascript-and-html5-audio/">the state of HTML5 audio</a> from December 2010.</p>
<p>By <a href="http://mrcoles.com">Peter Coles</a> – May 16, 2012</p>
</div>
</div>
<script src="jquery-1.7.1.min.js"></script>
<script src="audio.js"></script>
<script src="piano.js"></script>
</body>
</html>
4 changes: 4 additions & 0 deletions jquery-1.7.1.min.js

Large diffs are not rendered by default.

Loading

0 comments on commit 6ae8ae7

Please sign in to comment.