mediocre-blog/assets/trading-in-the-rain/MIDI.js/js/midi/player.js

380 lines
8.9 KiB
JavaScript
Raw Normal View History

2020-04-27 00:15:15 +00:00
/*
----------------------------------------------------------
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 = {};
};
})();