-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
first commit - moved project over from "guy"
- Loading branch information
0 parents
commit 6ae8ae7
Showing
6 changed files
with
1,211 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 & above /</p> | ||
<p>change playable keys: “,” & “.” /</p> | ||
<p>shift keyboard: ← & → <span id="shift"></span></p> | ||
</div> | ||
<div class="opts"> | ||
<p><strong>Extras:</strong></p> | ||
<p class="toggle-color toggle hold">Color - c /</p> | ||
<p class="toggle-demo toggle">Demo - m /</p> | ||
<p class="toggle-animate toggle">Visual mode - 8 /</p> | ||
<p class="toggle-looper">Looper - 9 /</p> | ||
<p>Help - 0</p> | ||
|
||
<!-- | ||
<p class="toggle-looper">Looper - 9 /</p> | ||
<p class="toggle-animate toggle">Visual mode - 8 /</p> | ||
<p class="toggle-demo toggle">Demo - m /</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 & 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> |
Large diffs are not rendered by default.
Oops, something went wrong.
Oops, something went wrong.