├── .gitignore ├── LICENSE ├── README.md ├── demo.html ├── demo.js ├── demo_require.html ├── demo_require.js ├── require.js ├── requireLICENSE └── tones.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-project 2 | *.sublime-workspace 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Keith Peters 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tones 2 | ===== 3 | 4 | The Web Audio API is pretty amazing. You can actually synthesize audio with JavaScript right in the browser. The only problem is that it's pretty low level and not exactly intuitive to work with. This library is meant to make it as simple as humanly possible to create pleasant sounding musical tones. 5 | 6 | See a few demos of tones in action here: 7 | 8 | http://bit101.github.io/tones/ 9 | 10 | The tones api is drop dead simple. One main method: `play`. 11 | 12 | The play method can be used in a few different ways: 13 | 14 | 1. Play a tone at a specific frequency. 15 | 2. Play a named note in the default 4th octave. 16 | 3. Play a named note in a specific octave. 17 | 18 | Examples: 19 | 20 | tones.play(440); // plays a 440 hz tone 21 | tones.play("c"); // plays a C note in the 4th octave. 22 | tones.play("c#"); // plays a C sharp note in the 4th octave. 23 | tones.play("eb"); // plays an E flat note in the 4th octave. 24 | tones.play("c", 2); // plays a C note in the 2nd octave. 25 | 26 | Named notes are case insensitive. "c" is the same as "C". 27 | 28 | There are also a few properties that affect the sound of the tone played. Set these before playing a tone and they will affect all future tones played. 29 | 30 | type // this is the wave form, a string of one of the following: "sine", "square", "sawtooth" or "triangle" 31 | attack // sets the attack of the envelope, or the time that the sound takes to reach full volume. 32 | release // sets the release of the envelope, or the time it takes for the sound to fade out. 33 | 34 | Attack and release are numbers which should generally be in the range of around 1 to 300. A low release, and mid-range release will give a bell sound. A long attack and release will sound more like a flute. Combined with different wave form types, this allows you to create all kinds of unique sounds. 35 | 36 | Note that attack and release values do not represent discrete times. But roughly speaking your sound will last about 10 times the total of these two in milliseconds. For example, an attack of 100 and release of 200 totals 300, so the tone will last around 3000 milliseconds or 3 seconds before completely fading out. 37 | 38 | There is also a `getTimeMS` function. This returns the current time reported by the audio context, which keeps a very accurate internal timer. Some of the examples use this to set up a sort of sequencer. 39 | 40 | There's a whole lot more that could be done to this, and I'll probably add features to this, while trying to make sure that the core functionality remains as simple as possible. 41 | 42 | It goes without saying that this library will only work in browsers that support the Web Audio API, which includes Chrome, Firefox and Safari (I think), but not Internet Explorer. 43 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AudioLib 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // ambient(); 4 | // scoredAmbient(); 5 | // song(); 6 | // interactive(); 7 | // physics(); 8 | piano(); 9 | 10 | 11 | //////////// 12 | // sounds 13 | //////////// 14 | 15 | /* 16 | tone types: 17 | "sine", 18 | "square", 19 | "sawtooth", 20 | "triangle", 21 | "custom" 22 | */ 23 | 24 | 25 | 26 | function interactive() { 27 | var types = ["sine", "square", "sawtooth", "triangle"]; 28 | var typeLabel = document.createElement("h3"); 29 | typeLabel.textContent = "type: " + tones.type; 30 | document.body.appendChild(typeLabel); 31 | 32 | var typeSlider = document.createElement("input"); 33 | typeSlider.type = "range"; 34 | typeSlider.min = 0; 35 | typeSlider.max = 3; 36 | typeSlider.value = types.indexOf(tones.type); 37 | typeSlider.style.width = "500px"; 38 | typeSlider.addEventListener("input", function() { 39 | tones.type = types[typeSlider.value]; 40 | typeLabel.textContent = "type: " + tones.type; 41 | }) 42 | document.body.appendChild(typeSlider); 43 | 44 | var attackLabel = document.createElement("h3"); 45 | attackLabel.textContent = "attack: " + tones.attack; 46 | document.body.appendChild(attackLabel); 47 | 48 | var attackSlider = document.createElement("input"); 49 | attackSlider.type = "range"; 50 | attackSlider.min = 1; 51 | attackSlider.max = 300; 52 | attackSlider.value = tones.attack; 53 | attackSlider.style.width = "500px"; 54 | attackSlider.addEventListener("input", function() { 55 | tones.attack = attackSlider.value; 56 | attackLabel.textContent = "attack: " + tones.attack; 57 | }) 58 | document.body.appendChild(attackSlider); 59 | 60 | var releaseLabel = document.createElement("h3"); 61 | releaseLabel.textContent = "release: " + tones.release; 62 | document.body.appendChild(releaseLabel); 63 | 64 | var releaseSlider = document.createElement("input"); 65 | releaseSlider.type = "range"; 66 | releaseSlider.min = 1; 67 | releaseSlider.max = 300; 68 | releaseSlider.value = tones.release; 69 | releaseSlider.style.width = "500px"; 70 | releaseSlider.addEventListener("input", function() { 71 | tones.release = releaseSlider.value; 72 | releaseLabel.textContent = "release: " + tones.release; 73 | }) 74 | document.body.appendChild(releaseSlider); 75 | 76 | ambient(); 77 | } 78 | 79 | 80 | function ambient() { 81 | 82 | var timing = 250; 83 | var notes = [ "C#", "D#", "F#", "D#"]; 84 | var prevTime = tones.getTimeMS(); 85 | var elapsed = 0; 86 | 87 | play(); 88 | 89 | 90 | function play() { 91 | var now = tones.getTimeMS(); 92 | elapsed += now - prevTime; 93 | if(elapsed > timing) { 94 | while(elapsed > timing) elapsed -= timing; 95 | var note = notes[Math.floor(Math.random() * notes.length)]; 96 | var octave = Math.floor(Math.random() * 10); 97 | tones.play(note, octave); 98 | } 99 | prevTime = now; 100 | requestAnimationFrame(play); 101 | } 102 | 103 | } 104 | 105 | 106 | function scoredAmbient() { 107 | tones.type = "triangle"; 108 | tones.release = 300; 109 | 110 | var timing = 250; 111 | var notes = [ "C#", "D#", "F#", "D#"]; 112 | 113 | score = []; 114 | for(var i = 0; i < 16; i++) { 115 | var note = notes[Math.floor(Math.random() * notes.length)]; 116 | var octave = Math.floor(Math.random() * 10); 117 | console.log(i, ":", note, octave); 118 | score.push({ 119 | note: note, 120 | octave: octave 121 | }); 122 | } 123 | var index = 0; 124 | 125 | 126 | 127 | var prevTime = tones.getTimeMS(); 128 | var elapsed = 0 129 | play(); 130 | 131 | 132 | 133 | function play() { 134 | var now = tones.getTimeMS(); 135 | elapsed += now - prevTime; 136 | if(elapsed > timing) { 137 | elapsed -= timing; 138 | var t = score[index]; 139 | tones.play(t.note, t.octave); 140 | index++; 141 | index %= score.length; 142 | } 143 | prevTime = now; 144 | requestAnimationFrame(play); 145 | 146 | } 147 | 148 | } 149 | 150 | function song() { 151 | tones.type = "square"; 152 | tones.attack = 20; 153 | tones.release = 200; 154 | 155 | var notes = "ccggaag-ffeeddc-ggffeed-ggffeed-ccggaag-ffeeddc-----", 156 | timing = 300, 157 | index = 0; 158 | 159 | var prevTime = tones.getTimeMS(); 160 | var elapsed = 0 161 | play(); 162 | 163 | 164 | 165 | function play() { 166 | var now = tones.getTimeMS(); 167 | elapsed += now - prevTime; 168 | if(elapsed > timing) { 169 | elapsed -= timing; 170 | var note = notes.charAt(index); 171 | if(note !== "-") { 172 | tones.play(note); 173 | } 174 | index++; 175 | index %= notes.length; 176 | } 177 | prevTime = now; 178 | requestAnimationFrame(play); 179 | 180 | } 181 | } 182 | 183 | function physics() { 184 | var canvas = document.createElement("canvas"), 185 | context = canvas.getContext("2d"), 186 | width = canvas.width = window.innerWidth, 187 | height = canvas.height = window.innerHeight; 188 | 189 | canvas.style.display = "block"; 190 | document.body.style.margin = 0; 191 | document.body.appendChild(canvas); 192 | 193 | 194 | var balls = [], 195 | num = 8, 196 | gravity = 0.5; 197 | 198 | for(var i = 0; i < num; i++) { 199 | var size = Math.random(); 200 | balls.push({ 201 | x: Math.random() * width, 202 | y: Math.random() * height, 203 | vx: Math.random() * 10 - 5, 204 | vy: Math.random() * 10 - 5, 205 | radius: 10 + size * 50, 206 | freq: 350 - size * 300 207 | }) 208 | } 209 | 210 | play(); 211 | 212 | function play() { 213 | context.clearRect(0, 0, width, height); 214 | for(var i = 0; i < num; i++) { 215 | var ball = balls[i]; 216 | ball.x += ball.vx; 217 | ball.y += ball.vy; 218 | if(ball.x + ball.radius > width) { 219 | ball.x = width - ball.radius; 220 | ball.vx *= -1; 221 | tones.play(ball.freq); 222 | } 223 | else if(ball.x - ball.radius < 0) { 224 | ball.x = ball.radius; 225 | ball.vx *= -1; 226 | tones.play(ball.freq); 227 | } 228 | if(ball.y + ball.radius > height) { 229 | ball.y = height - ball.radius; 230 | ball.vy *= -1; 231 | if(Math.abs(ball.vy) > 2) 232 | tones.play(ball.freq); 233 | } 234 | else if(ball.y - ball.radius < 0) { 235 | ball.y = ball.radius; 236 | ball.vy *= -1; 237 | tones.play(ball.freq); 238 | } 239 | ball.vy += gravity; 240 | context.beginPath(); 241 | context.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2, false); 242 | context.fill(); 243 | } 244 | 245 | 246 | 247 | requestAnimationFrame(play); 248 | } 249 | } 250 | 251 | function piano() { 252 | tones.attack = 0; 253 | tones.release = 300; 254 | tones.type = "sawtooth"; 255 | // white 256 | var notes = ["c", "d", "e", "f", "g", "a", "b"]; 257 | for(var i = 0; i < 7; i++) { 258 | makeKey(100 + i * 100, 100, 100, 500, "white", notes[i]); 259 | } 260 | // black 261 | makeKey(170, 100, 60, 275, "black", "c#"); 262 | makeKey(270, 100, 60, 275, "black", "d#"); 263 | makeKey(470, 100, 60, 275, "black", "f#"); 264 | makeKey(570, 100, 60, 275, "black", "g#"); 265 | makeKey(670, 100, 60, 275, "black", "a#"); 266 | 267 | 268 | function makeKey(x, y, width, height, color, note) { 269 | var key = document.createElement("div"); 270 | key.style.width = width + "px"; 271 | key.style.height = height + "px"; 272 | key.style.position = "absolute"; 273 | key.style.left = x + "px"; 274 | key.style.top = y + "px"; 275 | key.style.backgroundColor = color; 276 | key.style.border = "solid 1px black"; 277 | key.note = note; 278 | key.addEventListener("mousedown", function(event) { 279 | tones.play(event.target.note); 280 | }); 281 | document.body.appendChild(key); 282 | 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /demo_require.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AudioLib 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /demo_require.js: -------------------------------------------------------------------------------- 1 | require(["tones"], function(tones) { 2 | 3 | // ambient(); 4 | // scoredAmbient(); 5 | // song(); 6 | // interactive(); 7 | // physics(); 8 | piano(); 9 | 10 | 11 | //////////// 12 | // sounds 13 | //////////// 14 | 15 | /* 16 | tone types: 17 | "sine", 18 | "square", 19 | "sawtooth", 20 | "triangle", 21 | "custom" 22 | */ 23 | 24 | 25 | 26 | function interactive() { 27 | var types = ["sine", "square", "sawtooth", "triangle"]; 28 | var typeLabel = document.createElement("h3"); 29 | typeLabel.textContent = "type: " + tones.type; 30 | document.body.appendChild(typeLabel); 31 | 32 | var typeSlider = document.createElement("input"); 33 | typeSlider.type = "range"; 34 | typeSlider.min = 0; 35 | typeSlider.max = 3; 36 | typeSlider.value = types.indexOf(tones.type); 37 | typeSlider.style.width = "500px"; 38 | typeSlider.addEventListener("input", function() { 39 | tones.type = types[typeSlider.value]; 40 | typeLabel.textContent = "type: " + tones.type; 41 | }) 42 | document.body.appendChild(typeSlider); 43 | 44 | var attackLabel = document.createElement("h3"); 45 | attackLabel.textContent = "attack: " + tones.attack; 46 | document.body.appendChild(attackLabel); 47 | 48 | var attackSlider = document.createElement("input"); 49 | attackSlider.type = "range"; 50 | attackSlider.min = 1; 51 | attackSlider.max = 300; 52 | attackSlider.value = tones.attack; 53 | attackSlider.style.width = "500px"; 54 | attackSlider.addEventListener("input", function() { 55 | tones.attack = attackSlider.value; 56 | attackLabel.textContent = "attack: " + tones.attack; 57 | }) 58 | document.body.appendChild(attackSlider); 59 | 60 | var releaseLabel = document.createElement("h3"); 61 | releaseLabel.textContent = "release: " + tones.release; 62 | document.body.appendChild(releaseLabel); 63 | 64 | var releaseSlider = document.createElement("input"); 65 | releaseSlider.type = "range"; 66 | releaseSlider.min = 1; 67 | releaseSlider.max = 300; 68 | releaseSlider.value = tones.release; 69 | releaseSlider.style.width = "500px"; 70 | releaseSlider.addEventListener("input", function() { 71 | tones.release = releaseSlider.value; 72 | releaseLabel.textContent = "release: " + tones.release; 73 | }) 74 | document.body.appendChild(releaseSlider); 75 | 76 | ambient(); 77 | } 78 | 79 | 80 | function ambient() { 81 | 82 | var timing = 250; 83 | var notes = [ "C#", "D#", "F#", "D#"]; 84 | var prevTime = tones.getTimeMS(); 85 | var elapsed = 0; 86 | 87 | play(); 88 | 89 | 90 | function play() { 91 | var now = tones.getTimeMS(); 92 | elapsed += now - prevTime; 93 | if(elapsed > timing) { 94 | while(elapsed > timing) elapsed -= timing; 95 | var note = notes[Math.floor(Math.random() * notes.length)]; 96 | var octave = Math.floor(Math.random() * 10); 97 | tones.play(note, octave); 98 | } 99 | prevTime = now; 100 | requestAnimationFrame(play); 101 | } 102 | 103 | } 104 | 105 | 106 | function scoredAmbient() { 107 | tones.type = "triangle"; 108 | tones.release = 300; 109 | 110 | var timing = 250; 111 | var notes = [ "C#", "D#", "F#", "D#"]; 112 | 113 | score = []; 114 | for(var i = 0; i < 16; i++) { 115 | var note = notes[Math.floor(Math.random() * notes.length)]; 116 | var octave = Math.floor(Math.random() * 10); 117 | console.log(i, ":", note, octave); 118 | score.push({ 119 | note: note, 120 | octave: octave 121 | }); 122 | } 123 | var index = 0; 124 | 125 | 126 | 127 | var prevTime = tones.getTimeMS(); 128 | var elapsed = 0 129 | play(); 130 | 131 | 132 | 133 | function play() { 134 | var now = tones.getTimeMS(); 135 | elapsed += now - prevTime; 136 | if(elapsed > timing) { 137 | elapsed -= timing; 138 | var t = score[index]; 139 | tones.play(t.note, t.octave); 140 | index++; 141 | index %= score.length; 142 | } 143 | prevTime = now; 144 | requestAnimationFrame(play); 145 | 146 | } 147 | 148 | } 149 | 150 | function song() { 151 | tones.type = "square"; 152 | tones.attack = 20; 153 | tones.release = 200; 154 | 155 | var notes = "ccggaag-ffeeddc-ggffeed-ggffeed-ccggaag-ffeeddc-----", 156 | timing = 300, 157 | index = 0; 158 | 159 | var prevTime = tones.getTimeMS(); 160 | var elapsed = 0 161 | play(); 162 | 163 | 164 | 165 | function play() { 166 | var now = tones.getTimeMS(); 167 | elapsed += now - prevTime; 168 | if(elapsed > timing) { 169 | elapsed -= timing; 170 | var note = notes.charAt(index); 171 | if(note !== "-") { 172 | tones.play(note); 173 | } 174 | index++; 175 | index %= notes.length; 176 | } 177 | prevTime = now; 178 | requestAnimationFrame(play); 179 | 180 | } 181 | } 182 | 183 | function physics() { 184 | var canvas = document.createElement("canvas"), 185 | context = canvas.getContext("2d"), 186 | width = canvas.width = window.innerWidth, 187 | height = canvas.height = window.innerHeight; 188 | 189 | canvas.style.display = "block"; 190 | document.body.style.margin = 0; 191 | document.body.appendChild(canvas); 192 | 193 | 194 | var balls = [], 195 | num = 8, 196 | gravity = 0.5; 197 | 198 | for(var i = 0; i < num; i++) { 199 | var size = Math.random(); 200 | balls.push({ 201 | x: Math.random() * width, 202 | y: Math.random() * height, 203 | vx: Math.random() * 10 - 5, 204 | vy: Math.random() * 10 - 5, 205 | radius: 10 + size * 50, 206 | freq: 350 - size * 300 207 | }) 208 | } 209 | 210 | play(); 211 | 212 | function play() { 213 | context.clearRect(0, 0, width, height); 214 | for(var i = 0; i < num; i++) { 215 | var ball = balls[i]; 216 | ball.x += ball.vx; 217 | ball.y += ball.vy; 218 | if(ball.x + ball.radius > width) { 219 | ball.x = width - ball.radius; 220 | ball.vx *= -1; 221 | tones.play(ball.freq); 222 | } 223 | else if(ball.x - ball.radius < 0) { 224 | ball.x = ball.radius; 225 | ball.vx *= -1; 226 | tones.play(ball.freq); 227 | } 228 | if(ball.y + ball.radius > height) { 229 | ball.y = height - ball.radius; 230 | ball.vy *= -1; 231 | if(Math.abs(ball.vy) > 2) 232 | tones.play(ball.freq); 233 | } 234 | else if(ball.y - ball.radius < 0) { 235 | ball.y = ball.radius; 236 | ball.vy *= -1; 237 | tones.play(ball.freq); 238 | } 239 | ball.vy += gravity; 240 | context.beginPath(); 241 | context.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2, false); 242 | context.fill(); 243 | } 244 | 245 | 246 | 247 | requestAnimationFrame(play); 248 | } 249 | } 250 | 251 | function piano() { 252 | tones.attack = 0; 253 | tones.release = 300; 254 | tones.type = "sawtooth"; 255 | // white 256 | var notes = ["c", "d", "e", "f", "g", "a", "b"]; 257 | for(var i = 0; i < 7; i++) { 258 | makeKey(100 + i * 100, 100, 100, 500, "white", notes[i]); 259 | } 260 | // black 261 | makeKey(170, 100, 60, 275, "black", "c#"); 262 | makeKey(270, 100, 60, 275, "black", "d#"); 263 | makeKey(470, 100, 60, 275, "black", "f#"); 264 | makeKey(570, 100, 60, 275, "black", "g#"); 265 | makeKey(670, 100, 60, 275, "black", "a#"); 266 | 267 | 268 | function makeKey(x, y, width, height, color, note) { 269 | var key = document.createElement("div"); 270 | key.style.width = width + "px"; 271 | key.style.height = height + "px"; 272 | key.style.position = "absolute"; 273 | key.style.left = x + "px"; 274 | key.style.top = y + "px"; 275 | key.style.backgroundColor = color; 276 | key.style.border = "solid 1px black"; 277 | key.note = note; 278 | key.addEventListener("mousedown", function(event) { 279 | tones.play(event.target.note); 280 | }); 281 | document.body.appendChild(key); 282 | 283 | } 284 | } 285 | }); -------------------------------------------------------------------------------- /require.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 1.0.8 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(r){function K(a){return O.call(a)==="[object Function]"}function G(a){return O.call(a)==="[object Array]"}function $(a,c,l){for(var j in c)if(!(j in L)&&(!(j in a)||l))a[j]=c[j];return d}function P(a,c,d){a=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+a);if(d)a.originalError=d;return a}function aa(a,c,d){var j,k,t;for(j=0;t=c[j];j++){t=typeof t==="string"?{name:t}:t;k=t.location;if(d&&(!k||k.indexOf("/")!==0&&k.indexOf(":")===-1))k=d+"/"+(k||t.name);a[t.name]={name:t.name,location:k|| 8 | t.name,main:(t.main||"main").replace(fa,"").replace(ba,"")}}}function V(a,c){a.holdReady?a.holdReady(c):c?a.readyWait+=1:a.ready(!0)}function ga(a){function c(b,f){var g,m;if(b&&b.charAt(0)===".")if(f){q.pkgs[f]?f=[f]:(f=f.split("/"),f=f.slice(0,f.length-1));g=b=f.concat(b.split("/"));var a;for(m=0;a=g[m];m++)if(a===".")g.splice(m,1),m-=1;else if(a==="..")if(m===1&&(g[2]===".."||g[0]===".."))break;else m>0&&(g.splice(m-1,2),m-=2);m=q.pkgs[g=b[0]];b=b.join("/");m&&b===g+"/"+m.main&&(b=g)}else b.indexOf("./")=== 9 | 0&&(b=b.substring(2));return b}function l(b,f){var g=b?b.indexOf("!"):-1,m=null,a=f?f.name:null,h=b,e,d;g!==-1&&(m=b.substring(0,g),b=b.substring(g+1,b.length));m&&(m=c(m,a));b&&(m?e=(g=n[m])&&g.normalize?g.normalize(b,function(b){return c(b,a)}):c(b,a):(e=c(b,a),d=G[e],d||(d=i.nameToUrl(b,null,f),G[e]=d)));return{prefix:m,name:e,parentMap:f,url:d,originalName:h,fullName:m?m+"!"+(e||""):e}}function j(){var b=!0,f=q.priorityWait,g,a;if(f){for(a=0;g=f[a];a++)if(!s[g]){b=!1;break}b&&delete q.priorityWait}return b} 10 | function k(b,f,g){return function(){var a=ha.call(arguments,0),c;if(g&&K(c=a[a.length-1]))c.__requireJsBuild=!0;a.push(f);return b.apply(null,a)}}function t(b,f,g){f=k(g||i.require,b,f);$(f,{nameToUrl:k(i.nameToUrl,b),toUrl:k(i.toUrl,b),defined:k(i.requireDefined,b),specified:k(i.requireSpecified,b),isBrowser:d.isBrowser});return f}function p(b){var f,g,a,c=b.callback,h=b.map,e=h.fullName,ca=b.deps;a=b.listeners;var j=q.requireExecCb||d.execCb;if(c&&K(c)){if(q.catchError.define)try{g=j(e,b.callback, 11 | ca,n[e])}catch(k){f=k}else g=j(e,b.callback,ca,n[e]);if(e)(c=b.cjsModule)&&c.exports!==r&&c.exports!==n[e]?g=n[e]=b.cjsModule.exports:g===r&&b.usingExports?g=n[e]:(n[e]=g,H[e]&&(T[e]=!0))}else e&&(g=n[e]=c,H[e]&&(T[e]=!0));if(x[b.id])delete x[b.id],b.isDone=!0,i.waitCount-=1,i.waitCount===0&&(J=[]);delete M[e];if(d.onResourceLoad&&!b.placeholder)d.onResourceLoad(i,h,b.depArray);if(f)return g=(e?l(e).url:"")||f.fileName||f.sourceURL,a=f.moduleTree,f=P("defineerror",'Error evaluating module "'+e+'" at location "'+ 12 | g+'":\n'+f+"\nfileName:"+g+"\nlineNumber: "+(f.lineNumber||f.line),f),f.moduleName=e,f.moduleTree=a,d.onError(f);for(f=0;c=a[f];f++)c(g);return r}function u(b,f){return function(g){b.depDone[f]||(b.depDone[f]=!0,b.deps[f]=g,b.depCount-=1,b.depCount||p(b))}}function o(b,f){var g=f.map,a=g.fullName,c=g.name,h=N[b]||(N[b]=n[b]),e;if(!f.loading)f.loading=!0,e=function(b){f.callback=function(){return b};p(f);s[f.id]=!0;A()},e.fromText=function(b,f){var g=Q;s[b]=!1;i.scriptCount+=1;i.fake[b]=!0;g&&(Q=!1); 13 | d.exec(f);g&&(Q=!0);i.completeLoad(b)},a in n?e(n[a]):h.load(c,t(g.parentMap,!0,function(b,a){var c=[],e,m;for(e=0;m=b[e];e++)m=l(m,g.parentMap),b[e]=m.fullName,m.prefix||c.push(b[e]);f.moduleDeps=(f.moduleDeps||[]).concat(c);return i.require(b,a)}),e,q)}function y(b){x[b.id]||(x[b.id]=b,J.push(b),i.waitCount+=1)}function D(b){this.listeners.push(b)}function v(b,f){var g=b.fullName,a=b.prefix,c=a?N[a]||(N[a]=n[a]):null,h,e;g&&(h=M[g]);if(!h&&(e=!0,h={id:(a&&!c?O++ +"__p@:":"")+(g||"__r@"+O++),map:b, 14 | depCount:0,depDone:[],depCallbacks:[],deps:[],listeners:[],add:D},B[h.id]=!0,g&&(!a||N[a])))M[g]=h;a&&!c?(g=l(a),a in n&&!n[a]&&(delete n[a],delete R[g.url]),a=v(g,!0),a.add(function(){var f=l(b.originalName,b.parentMap),f=v(f,!0);h.placeholder=!0;f.add(function(b){h.callback=function(){return b};p(h)})})):e&&f&&(s[h.id]=!1,i.paused.push(h),y(h));return h}function C(b,f,a,c){var b=l(b,c),d=b.name,h=b.fullName,e=v(b),j=e.id,k=e.deps,o;if(h){if(h in n||s[j]===!0||h==="jquery"&&q.jQuery&&q.jQuery!== 15 | a().fn.jquery)return;B[j]=!0;s[j]=!0;h==="jquery"&&a&&W(a())}e.depArray=f;e.callback=a;for(a=0;a0)return r;if(q.priorityWait)if(j())A();else return r;for(h in s)if(!(h in L)&&(c=!0,!s[h]))if(b)a+=h+" ";else if(l=!0,h.indexOf("!")===-1){k=[];break}else(e=M[h]&&M[h].moduleDeps)&&k.push.apply(k,e);if(!c&&!i.waitCount)return r;if(b&&a)return b=P("timeout","Load timeout for modules: "+a),b.requireType="timeout",b.requireModules=a,b.contextName=i.contextName,d.onError(b); 18 | if(l&&k.length)for(a=0;h=x[k[a]];a++)if(h=F(h,{})){z(h,{});break}if(!b&&(l||i.scriptCount)){if((I||da)&&!X)X=setTimeout(function(){X=0;E()},50);return r}if(i.waitCount){for(a=0;h=J[a];a++)z(h,{});i.paused.length&&A();Y<5&&(Y+=1,E())}Y=0;d.checkReadyState();return r}var i,A,q={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},catchError:{}},S=[],B={require:!0,exports:!0,module:!0},G={},n={},s={},x={},J=[],R={},O=0,M={},N={},H={},T={},Z=0;W=function(b){if(!i.jQuery&&(b=b||(typeof jQuery!=="undefined"?jQuery: 19 | null))&&!(q.jQuery&&b.fn.jquery!==q.jQuery)&&("holdReady"in b||"readyWait"in b))if(i.jQuery=b,w(["jquery",[],function(){return jQuery}]),i.scriptCount)V(b,!0),i.jQueryIncremented=!0};A=function(){var b,a,c,l,k,h;i.takeGlobalQueue();Z+=1;if(i.scriptCount<=0)i.scriptCount=0;for(;S.length;)if(b=S.shift(),b[0]===null)return d.onError(P("mismatch","Mismatched anonymous define() module: "+b[b.length-1]));else w(b);if(!q.priorityWait||j())for(;i.paused.length;){k=i.paused;i.pausedCount+=k.length;i.paused= 20 | [];for(l=0;b=k[l];l++)a=b.map,c=a.url,h=a.fullName,a.prefix?o(a.prefix,b):!R[c]&&!s[h]&&((q.requireLoad||d.load)(i,h,c),c.indexOf("empty:")!==0&&(R[c]=!0));i.startTime=(new Date).getTime();i.pausedCount-=k.length}Z===1&&E();Z-=1;return r};i={contextName:a,config:q,defQueue:S,waiting:x,waitCount:0,specified:B,loaded:s,urlMap:G,urlFetched:R,scriptCount:0,defined:n,paused:[],pausedCount:0,plugins:N,needFullExec:H,fake:{},fullExec:T,managerCallbacks:M,makeModuleMap:l,normalize:c,configure:function(b){var a, 21 | c,d;b.baseUrl&&b.baseUrl.charAt(b.baseUrl.length-1)!=="/"&&(b.baseUrl+="/");a=q.paths;d=q.pkgs;$(q,b,!0);if(b.paths){for(c in b.paths)c in L||(a[c]=b.paths[c]);q.paths=a}if((a=b.packagePaths)||b.packages){if(a)for(c in a)c in L||aa(d,a[c],c);b.packages&&aa(d,b.packages);q.pkgs=d}if(b.priority)c=i.requireWait,i.requireWait=!1,A(),i.require(b.priority),A(),i.requireWait=c,q.priorityWait=b.priority;if(b.deps||b.callback)i.require(b.deps||[],b.callback)},requireDefined:function(b,a){return l(b,a).fullName in 22 | n},requireSpecified:function(b,a){return l(b,a).fullName in B},require:function(b,c,g){if(typeof b==="string"){if(K(c))return d.onError(P("requireargs","Invalid require call"));if(d.get)return d.get(i,b,c);c=l(b,c);b=c.fullName;return!(b in n)?d.onError(P("notloaded","Module name '"+c.fullName+"' has not been loaded yet for context: "+a)):n[b]}(b&&b.length||c)&&C(null,b,c,g);if(!i.requireWait)for(;!i.scriptCount&&i.paused.length;)A();return i.require},takeGlobalQueue:function(){U.length&&(ja.apply(i.defQueue, 23 | [i.defQueue.length-1,0].concat(U)),U=[])},completeLoad:function(b){var a;for(i.takeGlobalQueue();S.length;)if(a=S.shift(),a[0]===null){a[0]=b;break}else if(a[0]===b)break;else w(a),a=null;a?w(a):w([b,[],b==="jquery"&&typeof jQuery!=="undefined"?function(){return jQuery}:null]);d.isAsync&&(i.scriptCount-=1);A();d.isAsync||(i.scriptCount-=1)},toUrl:function(b,a){var c=b.lastIndexOf("."),d=null;c!==-1&&(d=b.substring(c,b.length),b=b.substring(0,c));return i.nameToUrl(b,d,a)},nameToUrl:function(b,a,g){var l, 24 | k,h,e,j=i.config,b=c(b,g&&g.fullName);if(d.jsExtRegExp.test(b))a=b+(a?a:"");else{l=j.paths;k=j.pkgs;g=b.split("/");for(e=g.length;e>0;e--)if(h=g.slice(0,e).join("/"),l[h]){g.splice(0,e,l[h]);break}else if(h=k[h]){b=b===h.name?h.location+"/"+h.main:h.location;g.splice(0,e,b);break}a=g.join("/")+(a||".js");a=(a.charAt(0)==="/"||a.match(/^[\w\+\.\-]+:/)?"":j.baseUrl)+a}return j.urlArgs?a+((a.indexOf("?")===-1?"?":"&")+j.urlArgs):a}};i.jQueryCheck=W;i.resume=A;return i}function ka(){var a,c,d;if(C&&C.readyState=== 25 | "interactive")return C;a=document.getElementsByTagName("script");for(c=a.length-1;c>-1&&(d=a[c]);c--)if(d.readyState==="interactive")return C=d;return null}var la=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ma=/require\(\s*["']([^'"\s]+)["']\s*\)/g,fa=/^\.\//,ba=/\.js$/,O=Object.prototype.toString,u=Array.prototype,ha=u.slice,ja=u.splice,I=!!(typeof window!=="undefined"&&navigator&&document),da=!I&&typeof importScripts!=="undefined",na=I&&navigator.platform==="PLAYSTATION 3"?/^complete$/:/^(complete|loaded)$/, 26 | ea=typeof opera!=="undefined"&&opera.toString()==="[object Opera]",L={},D={},U=[],C=null,Y=0,Q=!1,ia={require:!0,module:!0,exports:!0},d,u={},J,y,v,E,o,w,F,B,z,W,X;if(typeof define==="undefined"){if(typeof requirejs!=="undefined")if(K(requirejs))return;else u=requirejs,requirejs=r;typeof require!=="undefined"&&!K(require)&&(u=require,require=r);d=requirejs=function(a,c,d){var j="_",k;!G(a)&&typeof a!=="string"&&(k=a,G(c)?(a=c,c=d):a=[]);if(k&&k.context)j=k.context;d=D[j]||(D[j]=ga(j));k&&d.configure(k); 27 | return d.require(a,c)};d.config=function(a){return d(a)};require||(require=d);d.toUrl=function(a){return D._.toUrl(a)};d.version="1.0.8";d.jsExtRegExp=/^\/|:|\?|\.js$/;y=d.s={contexts:D,skipAsync:{}};if(d.isAsync=d.isBrowser=I)if(v=y.head=document.getElementsByTagName("head")[0],E=document.getElementsByTagName("base")[0])v=y.head=E.parentNode;d.onError=function(a){throw a;};d.load=function(a,c,l){d.resourcesReady(!1);a.scriptCount+=1;d.attach(l,a,c);if(a.jQuery&&!a.jQueryIncremented)V(a.jQuery,!0), 28 | a.jQueryIncremented=!0};define=function(a,c,d){var j,k;typeof a!=="string"&&(d=c,c=a,a=null);G(c)||(d=c,c=[]);!c.length&&K(d)&&d.length&&(d.toString().replace(la,"").replace(ma,function(a,d){c.push(d)}),c=(d.length===1?["require"]:["require","exports","module"]).concat(c));if(Q&&(j=J||ka()))a||(a=j.getAttribute("data-requiremodule")),k=D[j.getAttribute("data-requirecontext")];(k?k.defQueue:U).push([a,c,d]);return r};define.amd={multiversion:!0,plugins:!0,jQuery:!0};d.exec=function(a){return eval(a)}; 29 | d.execCb=function(a,c,d,j){return c.apply(j,d)};d.addScriptToDom=function(a){J=a;E?v.insertBefore(a,E):v.appendChild(a);J=null};d.onScriptLoad=function(a){var c=a.currentTarget||a.srcElement,l;if(a.type==="load"||c&&na.test(c.readyState))C=null,a=c.getAttribute("data-requirecontext"),l=c.getAttribute("data-requiremodule"),D[a].completeLoad(l),c.detachEvent&&!ea?c.detachEvent("onreadystatechange",d.onScriptLoad):c.removeEventListener("load",d.onScriptLoad,!1)};d.attach=function(a,c,l,j,k,o){var p; 30 | if(I)return j=j||d.onScriptLoad,p=c&&c.config&&c.config.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),p.type=k||c&&c.config.scriptType||"text/javascript",p.charset="utf-8",p.async=!y.skipAsync[a],c&&p.setAttribute("data-requirecontext",c.contextName),p.setAttribute("data-requiremodule",l),p.attachEvent&&!(p.attachEvent.toString&&p.attachEvent.toString().indexOf("[native code]")<0)&&!ea?(Q=!0,o?p.onreadystatechange=function(){if(p.readyState=== 31 | "loaded")p.onreadystatechange=null,p.attachEvent("onreadystatechange",j),o(p)}:p.attachEvent("onreadystatechange",j)):p.addEventListener("load",j,!1),p.src=a,o||d.addScriptToDom(p),p;else da&&(importScripts(a),c.completeLoad(l));return null};if(I){o=document.getElementsByTagName("script");for(B=o.length-1;B>-1&&(w=o[B]);B--){if(!v)v=w.parentNode;if(F=w.getAttribute("data-main")){if(!u.baseUrl)o=F.split("/"),w=o.pop(),o=o.length?o.join("/")+"/":"./",u.baseUrl=o,F=w.replace(ba,"");u.deps=u.deps?u.deps.concat(F): 32 | [F];break}}}d.checkReadyState=function(){var a=y.contexts,c;for(c in a)if(!(c in L)&&a[c].waitCount)return;d.resourcesReady(!0)};d.resourcesReady=function(a){var c,l;d.resourcesDone=a;if(d.resourcesDone)for(l in a=y.contexts,a)if(!(l in L)&&(c=a[l],c.jQueryIncremented))V(c.jQuery,!1),c.jQueryIncremented=!1};d.pageLoaded=function(){if(document.readyState!=="complete")document.readyState="complete"};if(I&&document.addEventListener&&!document.readyState)document.readyState="loading",window.addEventListener("load", 33 | d.pageLoaded,!1);d(u);if(d.isAsync&&typeof setTimeout!=="undefined")z=y.contexts[u.context||"_"],z.requireWait=!0,setTimeout(function(){z.requireWait=!1;z.scriptCount||z.resume();d.checkReadyState()},0)}})(); 34 | -------------------------------------------------------------------------------- /requireLICENSE: -------------------------------------------------------------------------------- 1 | RequireJS is released under two licenses: new BSD, and MIT. You may pick the 2 | license that best suits your development needs. The text of both licenses are 3 | provided below. 4 | 5 | 6 | The "New" BSD License: 7 | ---------------------- 8 | 9 | Copyright (c) 2010-2014, The Dojo Foundation 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without 13 | modification, are permitted provided that the following conditions are met: 14 | 15 | * Redistributions of source code must retain the above copyright notice, this 16 | list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright notice, 18 | this list of conditions and the following disclaimer in the documentation 19 | and/or other materials provided with the distribution. 20 | * Neither the name of the Dojo Foundation nor the names of its contributors 21 | may be used to endorse or promote products derived from this software 22 | without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 28 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | 35 | 36 | 37 | MIT License 38 | ----------- 39 | 40 | Copyright (c) 2010-2014, The Dojo Foundation 41 | 42 | Permission is hereby granted, free of charge, to any person obtaining a copy 43 | of this software and associated documentation files (the "Software"), to deal 44 | in the Software without restriction, including without limitation the rights 45 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 46 | copies of the Software, and to permit persons to whom the Software is 47 | furnished to do so, subject to the following conditions: 48 | 49 | The above copyright notice and this permission notice shall be included in 50 | all copies or substantial portions of the Software. 51 | 52 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 53 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 54 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 55 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 56 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 57 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 58 | THE SOFTWARE. 59 | -------------------------------------------------------------------------------- /tones.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var tones = { 3 | context: new (window.AudioContext || window.webkitAudioContext)(), 4 | attack: 1, 5 | release: 100, 6 | volume: 1, 7 | type: "sine", 8 | 9 | 10 | playFrequency: function(freq) { 11 | this.attack = this.attack || 1; 12 | this.release = this.release || 1; 13 | var envelope = this.context.createGain(); 14 | envelope.gain.setValueAtTime(this.volume, this.context.currentTime); 15 | envelope.connect(this.context.destination); 16 | 17 | envelope.gain.setValueAtTime(0, this.context.currentTime); 18 | envelope.gain.setTargetAtTime(this.volume, this.context.currentTime, this.attack / 1000); 19 | if(this.release) { 20 | envelope.gain.setTargetAtTime(0, this.context.currentTime + this.attack / 1000, this.release / 1000); 21 | setTimeout(function() { 22 | osc.stop(); 23 | osc.disconnect(envelope); 24 | envelope.gain.cancelScheduledValues(tones.context.currentTime); 25 | envelope.disconnect(tones.context.destination); 26 | 27 | }, this.attack * 10 + this.release * 10); 28 | } 29 | 30 | var osc = this.context.createOscillator(); 31 | osc.frequency.setValueAtTime(freq, this.context.currentTime); 32 | osc.type = this.type; 33 | osc.connect(envelope); 34 | osc.start(); 35 | }, 36 | 37 | /** 38 | * Usage: 39 | * notes.play(440); // plays 440 hz tone 40 | * notes.play("c"); // plays note c in default 4th octave 41 | * notes.play("c#"); // plays note c sharp in default 4th octave 42 | * notes.play("eb"); // plays note e flat in default 4th octave 43 | * notes.play("c", 2); // plays note c in 2nd octave 44 | */ 45 | play: function(freqOrNote, octave) { 46 | if(typeof freqOrNote === "number") { 47 | this.playFrequency(freqOrNote); 48 | } 49 | else if(typeof freqOrNote === "string") { 50 | if(octave == null) { 51 | octave = 4; 52 | } 53 | this.playFrequency(this.map[octave][freqOrNote.toLowerCase()]); 54 | } 55 | }, 56 | 57 | getTimeMS: function() { 58 | return this.context.currentTime * 1000; 59 | }, 60 | 61 | map: [{ 62 | // octave 0 63 | "c": 16.351, 64 | "c#": 17.324, 65 | "db": 17.324, 66 | "d": 18.354, 67 | "d#": 19.445, 68 | "eb": 19.445, 69 | "e": 20.601, 70 | "f": 21.827, 71 | "f#": 23.124, 72 | "gb": 23.124, 73 | "g": 24.499, 74 | "g#": 25.956, 75 | "ab": 25.956, 76 | "a": 27.5, 77 | "a#": 29.135, 78 | "bb": 29.135, 79 | "b": 30.868 80 | }, 81 | { 82 | // octave 1 83 | "c": 32.703, 84 | "c#": 34.648, 85 | "db": 34.648, 86 | "d": 36.708, 87 | "d#": 38.891, 88 | "eb": 38.891, 89 | "e": 41.203, 90 | "f": 43.654, 91 | "f#": 46.249, 92 | "gb": 46.249, 93 | "g": 48.999, 94 | "g#": 51.913, 95 | "ab": 51.913, 96 | "a": 55, 97 | "a#": 58.27, 98 | "bb": 58.27, 99 | "b": 61.735 100 | }, 101 | { 102 | // octave 2 103 | "c": 65.406, 104 | "c#": 69.296, 105 | "db": 69.296, 106 | "d": 73.416, 107 | "d#": 77.782, 108 | "eb": 77.782, 109 | "e": 82.407, 110 | "f": 87.307, 111 | "f#": 92.499, 112 | "gb": 92.499, 113 | "g": 97.999, 114 | "g#": 103.826, 115 | "ab": 103.826, 116 | "a": 110, 117 | "a#": 116.541, 118 | "bb": 116.541, 119 | "b": 123.471 120 | }, 121 | { 122 | // octave 3 123 | "c": 130.813, 124 | "c#": 138.591, 125 | "db": 138.591, 126 | "d": 146.832, 127 | "d#": 155.563, 128 | "eb": 155.563, 129 | "e": 164.814, 130 | "f": 174.614, 131 | "f#": 184.997, 132 | "gb": 184.997, 133 | "g": 195.998, 134 | "g#": 207.652, 135 | "ab": 207.652, 136 | "a": 220, 137 | "a#": 233.082, 138 | "bb": 233.082, 139 | "b": 246.942 140 | }, 141 | { 142 | // octave 4 143 | "c": 261.626, 144 | "c#": 277.183, 145 | "db": 277.183, 146 | "d": 293.665, 147 | "d#": 311.127, 148 | "eb": 311.127, 149 | "e": 329.628, 150 | "f": 349.228, 151 | "f#": 369.994, 152 | "gb": 369.994, 153 | "g": 391.995, 154 | "g#": 415.305, 155 | "ab": 415.305, 156 | "a": 440, 157 | "a#": 466.164, 158 | "bb": 466.164, 159 | "b": 493.883 160 | }, 161 | { 162 | // octave 5 163 | "c": 523.251, 164 | "c#": 554.365, 165 | "db": 554.365, 166 | "d": 587.33, 167 | "d#": 622.254, 168 | "eb": 622.254, 169 | "e": 659.255, 170 | "f": 698.456, 171 | "f#": 739.989, 172 | "gb": 739.989, 173 | "g": 783.991, 174 | "g#": 830.609, 175 | "ab": 830.609, 176 | "a": 880, 177 | "a#": 932.328, 178 | "bb": 932.328, 179 | "b": 987.767 180 | }, 181 | { 182 | // octave 6 183 | "c": 1046.502, 184 | "c#": 1108.731, 185 | "db": 1108.731, 186 | "d": 1174.659, 187 | "d#": 1244.508, 188 | "eb": 1244.508, 189 | "e": 1318.51, 190 | "f": 1396.913, 191 | "f#": 1479.978, 192 | "gb": 1479.978, 193 | "g": 1567.982, 194 | "g#": 1661.219, 195 | "ab": 1661.219, 196 | "a": 1760, 197 | "a#": 1864.655, 198 | "bb": 1864.655, 199 | "b": 1975.533 200 | }, 201 | { 202 | // octave 7 203 | "c": 2093.005, 204 | "c#": 2217.461, 205 | "db": 2217.461, 206 | "d": 2349.318, 207 | "d#": 2489.016, 208 | "eb": 2489.016, 209 | "e": 2637.021, 210 | "f": 2793.826, 211 | "f#": 2959.955, 212 | "gb": 2959.955, 213 | "g": 3135.964, 214 | "g#": 3322.438, 215 | "ab": 3322.438, 216 | "a": 3520, 217 | "a#": 3729.31, 218 | "bb": 3729.31, 219 | "b": 3951.066 220 | }, 221 | { 222 | // octave 8 223 | "c": 4186.009, 224 | "c#": 4434.922, 225 | "db": 4434.922, 226 | "d": 4698.636, 227 | "d#": 4978.032, 228 | "eb": 4978.032, 229 | "e": 5274.042, 230 | "f": 5587.652, 231 | "f#": 5919.91, 232 | "gb": 5919.91, 233 | "g": 6271.928, 234 | "g#": 6644.876, 235 | "ab": 6644.876, 236 | "a": 7040, 237 | "a#": 7458.62, 238 | "bb": 7458.62, 239 | "b": 7902.132 240 | }, 241 | { 242 | // octave 9 243 | "c": 8372.018, 244 | "c#": 8869.844, 245 | "db": 8869.844, 246 | "d": 9397.272, 247 | "d#": 9956.064, 248 | "eb": 9956.064, 249 | "e": 10548.084, 250 | "f": 11175.304, 251 | "f#": 11839.82, 252 | "gb": 11839.82, 253 | "g": 12543.856, 254 | "g#": 13289.752, 255 | "ab": 13289.752, 256 | "a": 14080, 257 | "a#": 14917.24, 258 | "bb": 14917.24, 259 | "b": 15804.264 260 | }] 261 | }; 262 | 263 | // need to create a node in order to kick off the timer in Chrome. 264 | tones.context.createGain(); 265 | 266 | if (typeof define === "function" && define.amd) { 267 | define(tones); 268 | } else { 269 | window.tones = tones; 270 | } 271 | 272 | }(window)); --------------------------------------------------------------------------------