source: main/trunk/model-sites-dev/respooled/collect/popup-video-respooled/js/midi/player.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.9 KB
Line 
1/*
2 ----------------------------------------------------------
3 MIDI.Player : 0.3.1 : 2015-03-26
4 ----------------------------------------------------------
5 https://github.com/mudcube/MIDI.js
6 ----------------------------------------------------------
7*/
8
9if (typeof MIDI === 'undefined') MIDI = {};
10if (typeof MIDI.Player === 'undefined') MIDI.Player = {};
11
12(function() { 'use strict';
13
14var midi = MIDI.Player;
15midi.currentTime = 0;
16midi.endTime = 0;
17midi.restart = 0;
18midi.playing = false;
19midi.timeWarp = 1;
20midi.startDelay = 0;
21midi.BPM = 120;
22
23midi.start =
24midi.resume = function(onsuccess) {
25 if (midi.currentTime < -1) {
26 midi.currentTime = -1;
27 }
28 startAudio(midi.currentTime, null, onsuccess);
29};
30
31midi.pause = function() {
32 var tmp = midi.restart;
33 stopAudio();
34 midi.restart = tmp;
35};
36
37midi.stop = function() {
38 stopAudio();
39 midi.restart = 0;
40 midi.currentTime = 0;
41};
42
43midi.addListener = function(onsuccess) {
44 onMidiEvent = onsuccess;
45};
46
47midi.removeListener = function() {
48 onMidiEvent = undefined;
49};
50
51midi.clearAnimation = function() {
52 if (midi.animationFrameId) {
53 cancelAnimationFrame(midi.animationFrameId);
54 }
55};
56
57midi.setAnimation = function(callback) {
58 var currentTime = 0;
59 var tOurTime = 0;
60 var tTheirTime = 0;
61 //
62 midi.clearAnimation();
63 ///
64 var frame = function() {
65 midi.animationFrameId = requestAnimationFrame(frame);
66 ///
67 if (midi.endTime === 0) {
68 return;
69 }
70 if (midi.playing) {
71 currentTime = (tTheirTime === midi.currentTime) ? tOurTime - Date.now() : 0;
72 if (midi.currentTime === 0) {
73 currentTime = 0;
74 } else {
75 currentTime = midi.currentTime - currentTime;
76 }
77 if (tTheirTime !== midi.currentTime) {
78 tOurTime = Date.now();
79 tTheirTime = midi.currentTime;
80 }
81 } else { // paused
82 currentTime = midi.currentTime;
83 }
84 ///
85 var endTime = midi.endTime;
86 var percent = currentTime / endTime;
87 var total = currentTime / 1000;
88 var minutes = total / 60;
89 var seconds = total - (minutes * 60);
90 var t1 = minutes * 60 + seconds;
91 var t2 = (endTime / 1000);
92 ///
93 if (t2 - t1 < -1.0) {
94 return;
95 } else {
96 callback({
97 now: t1,
98 end: t2,
99 events: noteRegistrar
100 });
101 }
102 };
103 ///
104 requestAnimationFrame(frame);
105};
106
107// helpers
108
109midi.loadMidiFile = function(onsuccess, onprogress, onerror) {
110 try {
111 midi.replayer = new Replayer(MidiFile(midi.currentData), midi.timeWarp, null, midi.BPM);
112 midi.data = midi.replayer.getData();
113 midi.endTime = getLength();
114 ///
115 MIDI.loadPlugin({
116// instruments: midi.getFileInstruments(),
117 onsuccess: onsuccess,
118 onprogress: onprogress,
119 onerror: onerror
120 });
121 } catch(event) {
122 onerror && onerror(event);
123 }
124};
125
126midi.loadFile = function(file, onsuccess, onprogress, onerror) {
127 midi.stop();
128 if (file.indexOf('base64,') !== -1) {
129 var data = window.atob(file.split(',')[1]);
130 midi.currentData = data;
131 midi.loadMidiFile(onsuccess, onprogress, onerror);
132 } else {
133 var fetch = new XMLHttpRequest();
134 fetch.open('GET', file);
135 fetch.overrideMimeType('text/plain; charset=x-user-defined');
136 fetch.onreadystatechange = function() {
137 if (this.readyState === 4) {
138 if (this.status === 200) {
139 var t = this.responseText || '';
140 var ff = [];
141 var mx = t.length;
142 var scc = String.fromCharCode;
143 for (var z = 0; z < mx; z++) {
144 ff[z] = scc(t.charCodeAt(z) & 255);
145 }
146 ///
147 var data = ff.join('');
148 midi.currentData = data;
149 midi.loadMidiFile(onsuccess, onprogress, onerror);
150 } else {
151 onerror && onerror('Unable to load MIDI file');
152 }
153 }
154 };
155 fetch.send();
156 }
157};
158
159midi.getFileInstruments = function() {
160 var instruments = {};
161 var programs = {};
162 for (var n = 0; n < midi.data.length; n ++) {
163 var event = midi.data[n][0].event;
164 if (event.type !== 'channel') {
165 continue;
166 }
167 var channel = event.channel;
168 switch(event.subtype) {
169 case 'controller':
170// console.log(event.channel, MIDI.defineControl[event.controllerType], event.value);
171 break;
172 case 'programChange':
173 programs[channel] = event.programNumber;
174 break;
175 case 'noteOn':
176 var program = programs[channel];
177 var gm = MIDI.GM.byId[isFinite(program) ? program : channel];
178 instruments[gm.id] = true;
179 break;
180 }
181 }
182 var ret = [];
183 for (var key in instruments) {
184 ret.push(key);
185 }
186 return ret;
187};
188
189// Playing the audio
190
191var eventQueue = []; // hold events to be triggered
192var queuedTime; //
193var startTime = 0; // to measure time elapse
194var noteRegistrar = {}; // get event for requested note
195var onMidiEvent = undefined; // listener
196var scheduleTracking = function(channel, note, currentTime, offset, message, velocity, time) {
197 return setTimeout(function() {
198 var data = {
199 channel: channel,
200 note: note,
201 now: currentTime,
202 end: midi.endTime,
203 message: message,
204 velocity: velocity
205 };
206 //
207 if (message === 128) {
208 delete noteRegistrar[note];
209 } else {
210 noteRegistrar[note] = data;
211 }
212 if (onMidiEvent) {
213 onMidiEvent(data);
214 }
215 midi.currentTime = currentTime;
216 ///
217 eventQueue.shift();
218 ///
219 if (eventQueue.length < 1000) {
220 startAudio(queuedTime, true);
221 } else if (midi.currentTime === queuedTime && queuedTime < midi.endTime) { // grab next sequence
222 startAudio(queuedTime, true);
223 }
224 }, currentTime - offset);
225};
226
227var getContext = function() {
228 if (MIDI.api === 'webaudio') {
229 return MIDI.WebAudio.getContext();
230 } else {
231 midi.ctx = {currentTime: 0};
232 }
233 return midi.ctx;
234};
235
236var getLength = function() {
237 var data = midi.data;
238 var length = data.length;
239 var totalTime = 0.5;
240 for (var n = 0; n < length; n++) {
241 totalTime += data[n][1];
242 }
243 return totalTime;
244};
245
246var __now;
247var getNow = function() {
248 if (window.performance && window.performance.now) {
249 return window.performance.now();
250 } else {
251 return Date.now();
252 }
253};
254
255var startAudio = function(currentTime, fromCache, onsuccess) {
256 if (!midi.replayer) {
257 return;
258 }
259 if (!fromCache) {
260 if (typeof currentTime === 'undefined') {
261 currentTime = midi.restart;
262 }
263 ///
264 midi.playing && stopAudio();
265 midi.playing = true;
266 midi.data = midi.replayer.getData();
267 midi.endTime = getLength();
268 }
269 ///
270 var note;
271 var offset = 0;
272 var messages = 0;
273 var data = midi.data;
274 var ctx = getContext();
275 var length = data.length;
276 //
277 queuedTime = 0.5;
278 ///
279 var interval = eventQueue[0] && eventQueue[0].interval || 0;
280 var foffset = currentTime - midi.currentTime;
281 ///
282 if (MIDI.api !== 'webaudio') { // set currentTime on ctx
283 var now = getNow();
284 __now = __now || now;
285 ctx.currentTime = (now - __now) / 1000;
286 }
287 ///
288 startTime = ctx.currentTime;
289 ///
290 for (var n = 0; n < length && messages < 100; n++) {
291 var obj = data[n];
292 if ((queuedTime += obj[1]) <= currentTime) {
293 offset = queuedTime;
294 continue;
295 }
296 ///
297 currentTime = queuedTime - offset;
298 ///
299 var event = obj[0].event;
300 if (event.type !== 'channel') {
301 continue;
302 }
303 ///
304 var channelId = event.channel;
305 var channel = MIDI.channels[channelId];
306 var delay = ctx.currentTime + ((currentTime + foffset + midi.startDelay) / 1000);
307 var queueTime = queuedTime - offset + midi.startDelay;
308 switch (event.subtype) {
309 case 'controller':
310 MIDI.setController(channelId, event.controllerType, event.value, delay);
311 break;
312 case 'programChange':
313 MIDI.programChange(channelId, event.programNumber, delay);
314 break;
315 case 'pitchBend':
316 MIDI.pitchBend(channelId, event.value, delay);
317 break;
318 case 'noteOn':
319 if (channel.mute) break;
320 note = event.noteNumber - (midi.MIDIOffset || 0);
321 eventQueue.push({
322 event: event,
323 time: queueTime,
324 source: MIDI.noteOn(channelId, event.noteNumber, event.velocity, delay),
325 interval: scheduleTracking(channelId, note, queuedTime + midi.startDelay, offset - foffset, 144, event.velocity)
326 });
327 messages++;
328 break;
329 case 'noteOff':
330 if (channel.mute) break;
331 note = event.noteNumber - (midi.MIDIOffset || 0);
332 eventQueue.push({
333 event: event,
334 time: queueTime,
335 source: MIDI.noteOff(channelId, event.noteNumber, delay),
336 interval: scheduleTracking(channelId, note, queuedTime, offset - foffset, 128, 0)
337 });
338 break;
339 default:
340 break;
341 }
342 }
343 ///
344 onsuccess && onsuccess(eventQueue);
345};
346
347var stopAudio = function() {
348 var ctx = getContext();
349 midi.playing = false;
350 midi.restart += (ctx.currentTime - startTime) * 1000;
351 // stop the audio, and intervals
352 while (eventQueue.length) {
353 var o = eventQueue.pop();
354 window.clearInterval(o.interval);
355 if (!o.source) continue; // is not webaudio
356 if (typeof(o.source) === 'number') {
357 window.clearTimeout(o.source);
358 } else { // webaudio
359 o.source.disconnect(0);
360 }
361 }
362 // run callback to cancel any notes still playing
363 for (var key in noteRegistrar) {
364 var o = noteRegistrar[key]
365 if (noteRegistrar[key].message === 144 && onMidiEvent) {
366 onMidiEvent({
367 channel: o.channel,
368 note: o.note,
369 now: o.now,
370 end: o.end,
371 message: 128,
372 velocity: o.velocity
373 });
374 }
375 }
376 // reset noteRegistrar
377 noteRegistrar = {};
378};
379
380})();
Note: See TracBrowser for help on using the repository browser.