player.js 8.9 KB

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