123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- /*
- ----------------------------------------------------------
- MIDI.Player : 0.3.2 : 2017-12-31
- ----------------------------------------------------------
- https://github.com/mudcube/MIDI.js
- ----------------------------------------------------------
- */
- if (typeof MIDI === 'undefined') MIDI = {};
- if (typeof MIDI.Player === 'undefined') MIDI.Player = {};
- (function() { 'use strict';
- var midi = MIDI.Player;
- midi.currentTime = 0;
- midi.endTime = 0;
- midi.restart = 0;
- midi.playing = false;
- midi.timeWarp = 1;
- midi.startDelay = 0;
- midi.BPM = 120;
- midi.playingStartTime = 0;
- midi.ctxStartTime = 0;
- midi.lastCallbackTime = 0;
- midi.start =
- midi.resume = function(onsuccess) {
- if (midi.currentTime < -1) {
- midi.currentTime = -1;
- }
- startAudio(midi.currentTime, null, onsuccess);
- };
- midi.pause = function() {
- var tmp = midi.restart;
- stopAudio();
- midi.restart = tmp;
- };
- midi.stop = function() {
- stopAudio();
- midi.restart = 0;
- midi.currentTime = 0;
- };
- midi.addListener = function(onsuccess) {
- onMidiEvent = onsuccess;
- };
- midi.removeListener = function() {
- onMidiEvent = undefined;
- };
- midi.clearAnimation = function() {
- if (midi.animationFrameId) {
- cancelAnimationFrame(midi.animationFrameId);
- }
- };
- midi.setAnimation = function(callback) {
- var currentTime = 0;
- var tOurTime = 0;
- var tTheirTime = 0;
- //
- midi.clearAnimation();
- ///
- var frame = function() {
- midi.animationFrameId = requestAnimationFrame(frame);
- ///
- if (midi.endTime === 0) {
- return;
- }
- if (midi.playing) {
- currentTime = (tTheirTime === midi.currentTime) ? tOurTime - Date.now() : 0;
- if (midi.currentTime === 0) {
- currentTime = 0;
- } else {
- currentTime = midi.currentTime - currentTime;
- }
- if (tTheirTime !== midi.currentTime) {
- tOurTime = Date.now();
- tTheirTime = midi.currentTime;
- }
- } else { // paused
- currentTime = midi.currentTime;
- }
- ///
- if(currentTime == 0 && midi.playing) currentTime = ((Date.now() - midi.ctxStartTime * 10) - midi.playingStartTime) / 100 * MIDI.Player.BPM;
- if(midi.lastCallbackTime!=currentTime){
- var endTime = midi.endTime;
- //var percent = currentTime / endTime;
- var t1 = currentTime / 1000;
- var t2 = endTime / 1000;
- ///
- if (t2 - t1 < -1.0) {
- return;
- } else {
- callback({
- now: t1,
- end: t2,
- events: noteRegistrar
- });
- }
- midi.lastCallbackTime = currentTime;
- }
- };
- ///
- requestAnimationFrame(frame);
- };
- // helpers
- midi.loadMidiFile = function(onsuccess, onprogress, onerror) {
- try {
- midi.replayer = new Replayer(MidiFile(midi.currentData), midi.timeWarp, null, midi.BPM);
- midi.data = midi.replayer.getData();
- midi.endTime = getLength();
- ///
- MIDI.loadPlugin({
- // instruments: midi.getFileInstruments(),
- onsuccess: onsuccess,
- onprogress: onprogress,
- onerror: onerror
- });
- } catch(event) {
- onerror && onerror(event);
- }
- };
- midi.loadFile = function(file, onsuccess, onprogress, onerror) {
- midi.stop();
- if (file.indexOf('base64,') !== -1) {
- var data = window.atob(file.split(',')[1]);
- midi.currentData = data;
- midi.loadMidiFile(onsuccess, onprogress, onerror);
- } else {
- var fetch = new XMLHttpRequest();
- fetch.open('GET', file);
- fetch.overrideMimeType('text/plain; charset=x-user-defined');
- fetch.onreadystatechange = function() {
- if (this.readyState === 4) {
- if (this.status === 200) {
- var t = this.responseText || '';
- var ff = [];
- var mx = t.length;
- var scc = String.fromCharCode;
- for (var z = 0; z < mx; z++) {
- ff[z] = scc(t.charCodeAt(z) & 255);
- }
- ///
- var data = ff.join('');
- midi.currentData = data;
- midi.loadMidiFile(onsuccess, onprogress, onerror);
- } else {
- onerror && onerror('Unable to load MIDI file');
- }
- }
- };
- fetch.send();
- }
- };
- midi.getFileInstruments = function() {
- var instruments = {};
- var programs = {};
- for (var n = 0; n < midi.data.length; n ++) {
- var event = midi.data[n][0].event;
- if (event.type !== 'channel') {
- continue;
- }
- var channel = event.channel;
- switch(event.subtype) {
- case 'controller':
- // console.log(event.channel, MIDI.defineControl[event.controllerType], event.value);
- break;
- case 'programChange':
- programs[channel] = event.programNumber;
- break;
- case 'noteOn':
- var program = programs[channel];
- var gm = MIDI.GM.byId[isFinite(program) ? program : channel];
- instruments[gm.id] = true;
- break;
- }
- }
- var ret = [];
- for (var key in instruments) {
- ret.push(key);
- }
- return ret;
- };
- // Playing the audio
- var eventQueue = []; // hold events to be triggered
- var queuedTime; //
- var startTime = 0; // to measure time elapse
- var noteRegistrar = {}; // get event for requested note
- var onMidiEvent = undefined; // listener
- var scheduleTracking = function(channel, note, currentTime, offset, message, velocity, time) {
- return setTimeout(function() {
- var data = {
- channel: channel,
- note: note,
- now: currentTime,
- end: midi.endTime,
- message: message,
- velocity: velocity
- };
- //
- if (message === 128) {
- delete noteRegistrar[note];
- } else {
- noteRegistrar[note] = data;
- }
- if (onMidiEvent) {
- onMidiEvent(data);
- }
- midi.currentTime = currentTime;
- ///
- eventQueue.shift();
- ///
- if (eventQueue.length < 1000) {
- startAudio(queuedTime, true);
- } else if (midi.currentTime === queuedTime && queuedTime < midi.endTime) { // grab next sequence
- startAudio(queuedTime, true);
- }
- }, currentTime - offset);
- };
- var getContext = function() {
- if (MIDI.api === 'webaudio') {
- return MIDI.WebAudio.getContext();
- } else {
- midi.ctx = {currentTime: 0};
- }
- return midi.ctx;
- };
- var getLength = function() {
- var data = midi.data;
- var length = data.length;
- var totalTime = 0.5;
- for (var n = 0; n < length; n++) {
- totalTime += data[n][1];
- }
- return totalTime;
- };
- var __now;
- var getNow = function() {
- if (window.performance && window.performance.now) {
- return window.performance.now();
- } else {
- return Date.now();
- }
- };
- var startAudio = function(currentTime, fromCache, onsuccess) {
- if (!midi.replayer) {
- return;
- }
- if (!fromCache) {
- if (typeof currentTime === 'undefined') {
- currentTime = midi.restart;
- }
- ///
- midi.playing && stopAudio();
- midi.playing = true;
- midi.data = midi.replayer.getData();
- midi.endTime = getLength();
- }
- ///
- var note;
- var offset = 0;
- var messages = 0;
- var data = midi.data;
- var ctx = getContext();
- var length = data.length;
- //
- queuedTime = 0.5;
- ///
- var interval = eventQueue[0] && eventQueue[0].interval || 0;
- var foffset = currentTime - midi.currentTime;
- ///
- if (MIDI.api !== 'webaudio') { // set currentTime on ctx
- var now = getNow();
- __now = __now || now;
- ctx.currentTime = (now - __now) / 1000;
- }
- ///
- midi.ctxStartTime = startTime = ctx.currentTime;
- midi.playingStartTime = Date.now() - midi.ctxStartTime*10 ;
- ///
- for (var n = 0; n < length && messages < 100; n++) {
- var obj = data[n];
- if ((queuedTime += obj[1]) <= currentTime) {
- offset = queuedTime;
- if (obj[0].event.type !== 'channel')
- continue;
- }
- ///
- currentTime = queuedTime - offset;
- ///
- var event = obj[0].event;
- if (event.type !== 'channel') {
- continue;
- }
- ///
- var channelId = event.channel;
- var channel = MIDI.channels[channelId];
- var delay = ctx.currentTime + ((currentTime + foffset + midi.startDelay) / 1000);
- var queueTime = queuedTime - offset + midi.startDelay;
- switch (event.subtype) {
- case 'controller':
- MIDI.setController(channelId, event.controllerType, event.value, delay);
- break;
- case 'programChange':
- MIDI.programChange(channelId, event.programNumber, delay);
- break;
- case 'pitchBend':
- MIDI.pitchBend(channelId, event.value, delay);
- break;
- case 'noteOn':
- if (channel.mute) break;
- note = event.noteNumber - (midi.MIDIOffset || 0);
- eventQueue.push({
- event: event,
- time: queueTime,
- source: MIDI.noteOn(channelId, event.noteNumber, event.velocity, delay),
- interval: scheduleTracking(channelId, note, queuedTime + midi.startDelay, offset - foffset, 144, event.velocity)
- });
- messages++;
- break;
- case 'noteOff':
- if (channel.mute) break;
- note = event.noteNumber - (midi.MIDIOffset || 0);
- eventQueue.push({
- event: event,
- time: queueTime,
- source: MIDI.noteOff(channelId, event.noteNumber, delay),
- interval: scheduleTracking(channelId, note, queuedTime, offset - foffset, 128, 0)
- });
- break;
- default:
- break;
- }
- }
- ///
- onsuccess && onsuccess(eventQueue);
- };
- var stopAudio = function() {
- var ctx = getContext();
- midi.playing = false;
- midi.restart += (ctx.currentTime - startTime) * 1000;
- // stop the audio, and intervals
- while (eventQueue.length) {
- var o = eventQueue.pop();
- window.clearInterval(o.interval);
- if (!o.source) continue; // is not webaudio
- if (typeof(o.source) === 'number') {
- window.clearTimeout(o.source);
- } else { // webaudio
- o.source.disconnect(0);
- }
- }
- // run callback to cancel any notes still playing
- for (var key in noteRegistrar) {
- var o = noteRegistrar[key]
- if (noteRegistrar[key].message === 144 && onMidiEvent) {
- onMidiEvent({
- channel: o.channel,
- note: o.note,
- now: o.now,
- end: o.end,
- message: 128,
- velocity: o.velocity
- });
- }
- }
- // reset noteRegistrar
- noteRegistrar = {};
- };
- })();
|