mediocre-blog/assets/trading-in-the-rain/MIDI.js/js/midi/plugin.webaudio.js
2020-04-26 18:15:15 -06:00

326 lines
8.7 KiB
JavaScript

/*
----------------------------------------------------------
Web Audio API - OGG or MPEG Soundbank
----------------------------------------------------------
http://webaudio.github.io/web-audio-api/
----------------------------------------------------------
*/
(function(root) { 'use strict';
window.AudioContext && (function() {
var audioContext = null; // new AudioContext();
var useStreamingBuffer = false; // !!audioContext.createMediaElementSource;
var midi = root.WebAudio = {api: 'webaudio'};
var ctx; // audio context
var sources = {};
var effects = {};
var masterVolume = 127;
var audioBuffers = {};
///
midi.audioBuffers = audioBuffers;
midi.send = function(data, delay) { };
midi.setController = function(channelId, type, value, delay) { };
midi.setVolume = function(channelId, volume, delay) {
if (delay) {
setTimeout(function() {
masterVolume = volume;
}, delay * 1000);
} else {
masterVolume = volume;
}
};
midi.programChange = function(channelId, program, delay) {
// if (delay) {
// return setTimeout(function() {
// var channel = root.channels[channelId];
// channel.instrument = program;
// }, delay);
// } else {
var channel = root.channels[channelId];
channel.instrument = program;
// }
};
midi.pitchBend = function(channelId, program, delay) {
// if (delay) {
// setTimeout(function() {
// var channel = root.channels[channelId];
// channel.pitchBend = program;
// }, delay);
// } else {
var channel = root.channels[channelId];
channel.pitchBend = program;
// }
};
midi.noteOn = function(channelId, noteId, velocity, delay) {
delay = delay || 0;
/// check whether the note exists
var channel = root.channels[channelId];
var instrument = channel.instrument;
var bufferId = instrument + '' + noteId;
var buffer = audioBuffers[bufferId];
if (!buffer) {
// console.log(MIDI.GM.byId[instrument].id, instrument, channelId);
return;
}
/// convert relative delay to absolute delay
if (delay < ctx.currentTime) {
delay += ctx.currentTime;
}
/// create audio buffer
if (useStreamingBuffer) {
var source = ctx.createMediaElementSource(buffer);
} else { // XMLHTTP buffer
var source = ctx.createBufferSource();
source.buffer = buffer;
}
/// add effects to buffer
if (effects) {
var chain = source;
for (var key in effects) {
chain.connect(effects[key].input);
chain = effects[key];
}
}
/// add gain + pitchShift
var gain = (velocity / 127) * (masterVolume / 127) * 2 - 1;
source.connect(ctx.destination);
source.playbackRate.value = 1; // pitch shift
source.gainNode = ctx.createGain(); // gain
source.gainNode.connect(ctx.destination);
source.gainNode.gain.value = Math.min(1.0, Math.max(-1.0, gain));
source.connect(source.gainNode);
///
if (useStreamingBuffer) {
if (delay) {
return setTimeout(function() {
buffer.currentTime = 0;
buffer.play()
}, delay * 1000);
} else {
buffer.currentTime = 0;
buffer.play()
}
} else {
source.start(delay || 0);
}
///
sources[channelId + '' + noteId] = source;
///
return source;
};
midi.noteOff = function(channelId, noteId, delay) {
delay = delay || 0;
/// check whether the note exists
var channel = root.channels[channelId];
var instrument = channel.instrument;
var bufferId = instrument + '' + noteId;
var buffer = audioBuffers[bufferId];
if (buffer) {
if (delay < ctx.currentTime) {
delay += ctx.currentTime;
}
///
var source = sources[channelId + '' + noteId];
if (source) {
if (source.gainNode) {
// @Miranet: 'the values of 0.2 and 0.3 could of course be used as
// a 'release' parameter for ADSR like time settings.'
// add { 'metadata': { release: 0.3 } } to soundfont files
var gain = source.gainNode.gain;
gain.linearRampToValueAtTime(gain.value, delay);
gain.linearRampToValueAtTime(-1.0, delay + 0.3);
}
///
if (useStreamingBuffer) {
if (delay) {
setTimeout(function() {
buffer.pause();
}, delay * 1000);
} else {
buffer.pause();
}
} else {
if (source.noteOff) {
source.noteOff(delay + 0.5);
} else {
source.stop(delay + 0.5);
}
}
///
delete sources[channelId + '' + noteId];
///
return source;
}
}
};
midi.chordOn = function(channel, chord, velocity, delay) {
var res = {};
for (var n = 0, note, len = chord.length; n < len; n++) {
res[note = chord[n]] = midi.noteOn(channel, note, velocity, delay);
}
return res;
};
midi.chordOff = function(channel, chord, delay) {
var res = {};
for (var n = 0, note, len = chord.length; n < len; n++) {
res[note = chord[n]] = midi.noteOff(channel, note, delay);
}
return res;
};
midi.stopAllNotes = function() {
for (var sid in sources) {
var delay = 0;
if (delay < ctx.currentTime) {
delay += ctx.currentTime;
}
var source = sources[sid];
source.gain.linearRampToValueAtTime(1, delay);
source.gain.linearRampToValueAtTime(0, delay + 0.3);
if (source.noteOff) { // old api
source.noteOff(delay + 0.3);
} else { // new api
source.stop(delay + 0.3);
}
delete sources[sid];
}
};
midi.setEffects = function(list) {
if (ctx.tunajs) {
for (var n = 0; n < list.length; n ++) {
var data = list[n];
var effect = new ctx.tunajs[data.type](data);
effect.connect(ctx.destination);
effects[data.type] = effect;
}
} else {
return console.log('Effects module not installed.');
}
};
midi.connect = function(opts) {
root.setDefaultPlugin(midi);
midi.setContext(ctx || createAudioContext(), opts.onsuccess);
};
midi.getContext = function() {
return ctx;
};
midi.setContext = function(newCtx, onload, onprogress, onerror) {
ctx = newCtx;
/// tuna.js effects module - https://github.com/Dinahmoe/tuna
if (typeof Tuna !== 'undefined' && !ctx.tunajs) {
ctx.tunajs = new Tuna(ctx);
}
/// loading audio files
var urls = [];
var notes = root.keyToNote;
for (var key in notes) urls.push(key);
///
var waitForEnd = function(instrument) {
for (var key in bufferPending) { // has pending items
if (bufferPending[key]) return;
}
///
if (onload) { // run onload once
onload();
onload = null;
}
};
///
var requestAudio = function(soundfont, instrumentId, index, key) {
var url = soundfont[key];
if (url) {
bufferPending[instrumentId] ++;
loadAudio(url, function(buffer) {
buffer.id = key;
var noteId = root.keyToNote[key];
audioBuffers[instrumentId + '' + noteId] = buffer;
///
if (-- bufferPending[instrumentId] === 0) {
var percent = index / 87;
// console.log(MIDI.GM.byId[instrumentId], 'processing: ', percent);
soundfont.isLoaded = true;
waitForEnd(instrument);
}
}, function(err) {
// console.log(err);
});
}
};
///
var bufferPending = {};
for (var instrument in root.Soundfont) {
var soundfont = root.Soundfont[instrument];
if (soundfont.isLoaded) {
continue;
}
///
var synth = root.GM.byName[instrument];
var instrumentId = synth.number;
///
bufferPending[instrumentId] = 0;
///
for (var index = 0; index < urls.length; index++) {
var key = urls[index];
requestAudio(soundfont, instrumentId, index, key);
}
}
///
setTimeout(waitForEnd, 1);
};
/* Load audio file: streaming | base64 | arraybuffer
---------------------------------------------------------------------- */
function loadAudio(url, onload, onerror) {
if (useStreamingBuffer) {
var audio = new Audio();
audio.src = url;
audio.controls = false;
audio.autoplay = false;
audio.preload = false;
audio.addEventListener('canplay', function() {
onload && onload(audio);
});
audio.addEventListener('error', function(err) {
onerror && onerror(err);
});
document.body.appendChild(audio);
} else if (url.indexOf('data:audio') === 0) { // Base64 string
var base64 = url.split(',')[1];
var buffer = Base64Binary.decodeArrayBuffer(base64);
ctx.decodeAudioData(buffer, onload, onerror);
} else { // XMLHTTP buffer
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = function() {
ctx.decodeAudioData(request.response, onload, onerror);
};
request.send();
}
};
function createAudioContext() {
return new (window.AudioContext || window.webkitAudioContext)();
};
})();
})(MIDI);