player2.js 11 KB

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