1/* 2 ---------------------------------------------------------- 3 Web Audio API - OGG or MPEG Soundbank 4 ---------------------------------------------------------- 5 http://webaudio.github.io/web-audio-api/ 6 ---------------------------------------------------------- 7*/ 8 9(function(root) { 'use strict'; 10 11 window.AudioContext && (function() { 12 var audioContext = null; // new AudioContext(); 13 var useStreamingBuffer = false; // !!audioContext.createMediaElementSource; 14 var midi = root.WebAudio = {api: 'webaudio'}; 15 var ctx; // audio context 16 var sources = {}; 17 var effects = {}; 18 var masterVolume = 127; 19 var audioBuffers = {}; 20 /// 21 midi.audioBuffers = audioBuffers; 22 midi.send = function(data, delay) { }; 23 midi.setController = function(channelId, type, value, delay) { }; 24 25 midi.setVolume = function(channelId, volume, delay) { 26 if (delay) { 27 setTimeout(function() { 28 masterVolume = volume; 29 }, delay * 1000); 30 } else { 31 masterVolume = volume; 32 } 33 }; 34 35 midi.programChange = function(channelId, program, delay) { 36// if (delay) { 37// return setTimeout(function() { 38// var channel = root.channels[channelId]; 39// channel.instrument = program; 40// }, delay); 41// } else { 42 var channel = root.channels[channelId]; 43 channel.instrument = program; 44// } 45 }; 46 47 midi.pitchBend = function(channelId, program, delay) { 48// if (delay) { 49// setTimeout(function() { 50// var channel = root.channels[channelId]; 51// channel.pitchBend = program; 52// }, delay); 53// } else { 54 var channel = root.channels[channelId]; 55 channel.pitchBend = program; 56// } 57 }; 58 59 midi.noteOn = function(channelId, noteId, velocity, delay) { 60 delay = delay || 0; 61 62 /// check whether the note exists 63 var channel = root.channels[channelId]; 64 var instrument = channel.instrument; 65 var bufferId = instrument + '' + noteId; 66 var buffer = audioBuffers[bufferId]; 67 if (!buffer) { 68// console.log(MIDI.GM.byId[instrument].id, instrument, channelId); 69 return; 70 } 71 72 /// convert relative delay to absolute delay 73 if (delay < ctx.currentTime) { 74 delay += ctx.currentTime; 75 } 76 77 /// create audio buffer 78 if (useStreamingBuffer) { 79 var source = ctx.createMediaElementSource(buffer); 80 } else { // XMLHTTP buffer 81 var source = ctx.createBufferSource(); 82 source.buffer = buffer; 83 } 84 85 /// add effects to buffer 86 if (effects) { 87 var chain = source; 88 for (var key in effects) { 89 chain.connect(effects[key].input); 90 chain = effects[key]; 91 } 92 } 93 94 /// add gain + pitchShift 95 var gain = (velocity / 127) * (masterVolume / 127) * 2 - 1; 96 source.connect(ctx.destination); 97 source.playbackRate.value = 1; // pitch shift 98 source.gainNode = ctx.createGain(); // gain 99 source.gainNode.connect(ctx.destination); 100 source.gainNode.gain.value = Math.min(1.0, Math.max(-1.0, gain)); 101 source.connect(source.gainNode); 102 /// 103 if (useStreamingBuffer) { 104 if (delay) { 105 return setTimeout(function() { 106 buffer.currentTime = 0; 107 buffer.play() 108 }, delay * 1000); 109 } else { 110 buffer.currentTime = 0; 111 buffer.play() 112 } 113 } else { 114 source.start(delay || 0); 115 } 116 /// 117 sources[channelId + '' + noteId] = source; 118 /// 119 return source; 120 }; 121 122 midi.noteOff = function(channelId, noteId, delay) { 123 delay = delay || 0; 124 125 /// check whether the note exists 126 var channel = root.channels[channelId]; 127 var instrument = channel.instrument; 128 var bufferId = instrument + '' + noteId; 129 var buffer = audioBuffers[bufferId]; 130 if (buffer) { 131 if (delay < ctx.currentTime) { 132 delay += ctx.currentTime; 133 } 134 /// 135 var source = sources[channelId + '' + noteId]; 136 if (source) { 137 if (source.gainNode) { 138 // @Miranet: 'the values of 0.2 and 0.3 could of course be used as 139 // a 'release' parameter for ADSR like time settings.' 140 // add { 'metadata': { release: 0.3 } } to soundfont files 141 var gain = source.gainNode.gain; 142 gain.linearRampToValueAtTime(gain.value, delay); 143 gain.linearRampToValueAtTime(-1.0, delay + 0.3); 144 } 145 /// 146 if (useStreamingBuffer) { 147 if (delay) { 148 setTimeout(function() { 149 buffer.pause(); 150 }, delay * 1000); 151 } else { 152 buffer.pause(); 153 } 154 } else { 155 if (source.noteOff) { 156 source.noteOff(delay + 0.5);
157 } else { 158 source.stop(delay + 0.5); 159 } 160 } 161 /// 162 delete sources[channelId + '' + noteId]; 163 /// 164 return source; 165 } 166 } 167 }; 168 169 midi.chordOn = function(channel, chord, velocity, delay) { 170 var res = {}; 171 for (var n = 0, note, len = chord.length; n < len; n++) { 172 res[note = chord[n]] = midi.noteOn(channel, note, velocity, delay); 173 } 174 return res; 175 }; 176 177 midi.chordOff = function(channel, chord, delay) { 178 var res = {}; 179 for (var n = 0, note, len = chord.length; n < len; n++) { 180 res[note = chord[n]] = midi.noteOff(channel, note, delay); 181 } 182 return res; 183 }; 184 185 midi.stopAllNotes = function() { 186 for (var sid in sources) { 187 var delay = 0; 188 if (delay < ctx.currentTime) { 189 delay += ctx.currentTime; 190 } 191 var source = sources[sid]; 192 if (source.gainNode && source.gainNode.gain) { 193 var gain = source.gainNode.gain; 194 gain.linearRampToValueAtTime(gain.value, delay); 195 gain.linearRampToValueAtTime(0, delay + 0.05); 196 } 197 if (source.noteOff) { // old api 198 source.noteOff(delay + 0.05); 199 } else { // new api 200 source.stop(delay + 0.05); 201 } 202 delete sources[sid]; 203 } 204 }; 205 206 midi.setEffects = function(list) { 207 if (ctx.tunajs) { 208 for (var n = 0; n < list.length; n ++) { 209 var data = list[n]; 210 var effect = new ctx.tunajs[data.type](data); 211 effect.connect(ctx.destination); 212 effects[data.type] = effect; 213 } 214 } else { 215 return console.log('Effects module not installed.'); 216 } 217 }; 218 219 midi.connect = function(opts) { 220 root.setDefaultPlugin(midi); 221 midi.setContext(ctx || createAudioContext(), opts.onsuccess); 222 }; 223 224 midi.getContext = function() { 225 return ctx; 226 }; 227 228 midi.setContext = function(newCtx, onload, onprogress, onerror) { 229 ctx = newCtx; 230 231 /// tuna.js effects module - https://github.com/Dinahmoe/tuna 232 if (typeof Tuna !== 'undefined' && !ctx.tunajs) { 233 ctx.tunajs = new Tuna(ctx); 234 } 235 236 /// loading audio files 237 var urls = []; 238 var notes = root.keyToNote; 239 for (var key in notes) urls.push(key); 240 /// 241 var waitForEnd = function(instrument) { 242 for (var key in bufferPending) { // has pending items 243 if (bufferPending[key]) return; 244 } 245 /// 246 if (onload) { // run onload once 247 onload(); 248 onload = null; 249 } 250 }; 251 /// 252 var requestAudio = function(soundfont, instrumentId, index, key) { 253 var url = soundfont[key]; 254 if (url) { 255 bufferPending[instrumentId] ++; 256 loadAudio(url, function(buffer) { 257 buffer.id = key; 258 var noteId = root.keyToNote[key]; 259 audioBuffers[instrumentId + '' + noteId] = buffer; 260 /// 261 if (-- bufferPending[instrumentId] === 0) { 262 var percent = index / 87; 263// console.log(MIDI.GM.byId[instrumentId], 'processing: ', percent); 264 soundfont.isLoaded = true; 265 waitForEnd(instrument); 266 } 267 }, function(err) { 268 // console.log(err); 269 }); 270 } 271 }; 272 /// 273 var bufferPending = {}; 274 for (var instrument in root.Soundfont) { 275 var soundfont = root.Soundfont[instrument]; 276 if (soundfont.isLoaded) { 277 continue; 278 } 279 /// 280 var synth = root.GM.byName[instrument]; 281 var instrumentId = synth.number; 282 /// 283 bufferPending[instrumentId] = 0; 284 /// 285 for (var index = 0; index < urls.length; index++) { 286 var key = urls[index]; 287 requestAudio(soundfont, instrumentId, index, key); 288 } 289 } 290 /// 291 setTimeout(waitForEnd, 1); 292 }; 293 294 /* Load audio file: streaming | base64 | arraybuffer 295 ---------------------------------------------------------------------- */ 296 function loadAudio(url, onload, onerror) { 297 if (useStreamingBuffer) { 298 var audio = new Audio(); 299 audio.src = url; 300 audio.controls = false; 301 audio.autoplay = false; 302 audio.preload = false; 303 audio.addEventListener('canplay', function() { 304 onload && onload(audio); 305 }); 306 audio.addEventListener('error', function(err) {
307 onerror && onerror(err); 308 }); 309 document.body.appendChild(audio); 310 } else if (url.indexOf('data:audio') === 0) { // Base64 string 311 var base64 = url.split(',')[1]; 312 var buffer = Base64Binary.decodeArrayBuffer(base64); 313 ctx.decodeAudioData(buffer, onload, onerror); 314 } else { // XMLHTTP buffer 315 var request = new XMLHttpRequest(); 316 request.open('GET', url, true); 317 request.responseType = 'arraybuffer'; 318 request.onload = function() { 319 ctx.decodeAudioData(request.response, onload, onerror); 320 }; 321 request.send(); 322 } 323 }; 324 325 function createAudioContext() { 326 return new (window.AudioContext || window.webkitAudioContext)(); 327 }; 328 })(); 329})(MIDI);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.