/* ---------------------------------------------------------- MIDI.Player : 0.3.1 : 2015-03-26 ---------------------------------------------------------- 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.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; } /// var endTime = midi.endTime; var percent = currentTime / endTime; var total = currentTime / 1000; var minutes = total / 60; var seconds = total - (minutes * 60); var t1 = minutes * 60 + seconds; var t2 = (endTime / 1000); /// if (t2 - t1 < -1.0) { return; } else { callback({ now: t1, end: t2, events: noteRegistrar }); } }; /// 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; } /// startTime = ctx.currentTime; /// for (var n = 0; n < length && messages < 100; n++) { var obj = data[n]; if ((queuedTime += obj[1]) <= currentTime) { offset = queuedTime; 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 = {}; }; })();