source: main/trunk/model-sites-dev/respooled/collect/popup-video-respooled/js/midi/plugin.webaudio.js@ 29863

Last change on this file since 29863 was 29863, checked in by davidb, 9 years ago

First cut at respooled site/collection

File size: 8.7 KB
Line 
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 source.gain.linearRampToValueAtTime(1, delay);
193 source.gain.linearRampToValueAtTime(0, delay + 0.3);
194 if (source.noteOff) { // old api
195 source.noteOff(delay + 0.3);
196 } else { // new api
197 source.stop(delay + 0.3);
198 }
199 delete sources[sid];
200 }
201 };
202
203 midi.setEffects = function(list) {
204 if (ctx.tunajs) {
205 for (var n = 0; n < list.length; n ++) {
206 var data = list[n];
207 var effect = new ctx.tunajs[data.type](data);
208 effect.connect(ctx.destination);
209 effects[data.type] = effect;
210 }
211 } else {
212 return console.log('Effects module not installed.');
213 }
214 };
215
216 midi.connect = function(opts) {
217 root.setDefaultPlugin(midi);
218 midi.setContext(ctx || createAudioContext(), opts.onsuccess);
219 };
220
221 midi.getContext = function() {
222 return ctx;
223 };
224
225 midi.setContext = function(newCtx, onload, onprogress, onerror) {
226 ctx = newCtx;
227
228 /// tuna.js effects module - https://github.com/Dinahmoe/tuna
229 if (typeof Tuna !== 'undefined' && !ctx.tunajs) {
230 ctx.tunajs = new Tuna(ctx);
231 }
232
233 /// loading audio files
234 var urls = [];
235 var notes = root.keyToNote;
236 for (var key in notes) urls.push(key);
237 ///
238 var waitForEnd = function(instrument) {
239 for (var key in bufferPending) { // has pending items
240 if (bufferPending[key]) return;
241 }
242 ///
243 if (onload) { // run onload once
244 onload();
245 onload = null;
246 }
247 };
248 ///
249 var requestAudio = function(soundfont, instrumentId, index, key) {
250 var url = soundfont[key];
251 if (url) {
252 bufferPending[instrumentId] ++;
253 loadAudio(url, function(buffer) {
254 buffer.id = key;
255 var noteId = root.keyToNote[key];
256 audioBuffers[instrumentId + '' + noteId] = buffer;
257 ///
258 if (-- bufferPending[instrumentId] === 0) {
259 var percent = index / 87;
260// console.log(MIDI.GM.byId[instrumentId], 'processing: ', percent);
261 soundfont.isLoaded = true;
262 waitForEnd(instrument);
263 }
264 }, function(err) {
265 // console.log(err);
266 });
267 }
268 };
269 ///
270 var bufferPending = {};
271 for (var instrument in root.Soundfont) {
272 var soundfont = root.Soundfont[instrument];
273 if (soundfont.isLoaded) {
274 continue;
275 }
276 ///
277 var synth = root.GM.byName[instrument];
278 var instrumentId = synth.number;
279 ///
280 bufferPending[instrumentId] = 0;
281 ///
282 for (var index = 0; index < urls.length; index++) {
283 var key = urls[index];
284 requestAudio(soundfont, instrumentId, index, key);
285 }
286 }
287 ///
288 setTimeout(waitForEnd, 1);
289 };
290
291 /* Load audio file: streaming | base64 | arraybuffer
292 ---------------------------------------------------------------------- */
293 function loadAudio(url, onload, onerror) {
294 if (useStreamingBuffer) {
295 var audio = new Audio();
296 audio.src = url;
297 audio.controls = false;
298 audio.autoplay = false;
299 audio.preload = false;
300 audio.addEventListener('canplay', function() {
301 onload && onload(audio);
302 });
303 audio.addEventListener('error', function(err) {
304 onerror && onerror(err);
305 });
306 document.body.appendChild(audio);
307 } else if (url.indexOf('data:audio') === 0) { // Base64 string
308 var base64 = url.split(',')[1];
309 var buffer = Base64Binary.decodeArrayBuffer(base64);
310 ctx.decodeAudioData(buffer, onload, onerror);
311 } else { // XMLHTTP buffer
312 var request = new XMLHttpRequest();
313 request.open('GET', url, true);
314 request.responseType = 'arraybuffer';
315 request.onload = function() {
316 ctx.decodeAudioData(request.response, onload, onerror);
317 };
318 request.send();
319 }
320 };
321
322 function createAudioContext() {
323 return new (window.AudioContext || window.webkitAudioContext)();
324 };
325 })();
326})(MIDI);
Note: See TracBrowser for help on using the repository browser.