qambi
Version:
MIDI sequencer, loads MIDI files, can record and playback MIDI, uses WebMIDI and WebAudio
495 lines (413 loc) • 13.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getMIDIInputById = exports.getMIDIOutputById = exports.getMIDIInputIds = exports.getMIDIOutputIds = exports.getMIDIInputs = exports.getMIDIOutputs = exports.getMIDIAccess = undefined;
exports.initMIDI = initMIDI;
var _util = require('./util');
require('jzz');
// you can also embed the shim as a stand-alone script in the html, then you can comment this line out
/*
Requests MIDI access, queries all inputs and outputs and stores them in alphabetical order
*/
var MIDIAccess = void 0;
var initialized = false;
var inputs = [];
var outputs = [];
var inputIds = [];
var outputIds = [];
var inputsById = new Map();
var outputsById = new Map();
var songMidiEventListener = void 0;
var midiEventListenerId = 0;
function getMIDIports() {
inputs = Array.from(MIDIAccess.inputs.values());
//sort ports by name ascending
inputs.sort(function (a, b) {
return a.name.toLowerCase() <= b.name.toLowerCase() ? 1 : -1;
});
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = inputs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var port = _step.value;
inputsById.set(port.id, port);
inputIds.push(port.id);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
outputs = Array.from(MIDIAccess.outputs.values());
//sort ports by name ascending
outputs.sort(function (a, b) {
return a.name.toLowerCase() <= b.name.toLowerCase() ? 1 : -1;
});
//console.log(outputs)
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = outputs[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var _port = _step2.value;
//console.log(port.id, port.name)
outputsById.set(_port.id, _port);
outputIds.push(_port.id);
}
//console.log(outputsById)
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
}
function initMIDI() {
return new Promise(function executor(resolve, reject) {
var jazz = false;
var midi = false;
var webmidi = false;
if (typeof navigator === 'undefined') {
initialized = true;
resolve({ midi: midi });
} else if (typeof navigator.requestMIDIAccess !== 'undefined') {
navigator.requestMIDIAccess().then(function onFulFilled(midiAccess) {
MIDIAccess = midiAccess;
// @TODO: implement something in webmidiapishim that allows us to detect the Jazz plugin version
if (typeof midiAccess._jazzInstances !== 'undefined') {
jazz = midiAccess._jazzInstances[0]._Jazz.version;
console.log('jazz version:', jazz);
midi = true;
} else {
webmidi = true;
midi = true;
}
getMIDIports();
// onconnect and ondisconnect are not yet implemented in Chrome and Chromium
midiAccess.onconnect = function (e) {
console.log('device connected', e);
getMIDIports();
};
midiAccess.ondisconnect = function (e) {
console.log('device disconnected', e);
getMIDIports();
};
initialized = true;
resolve({
jazz: jazz,
midi: midi,
webmidi: webmidi,
inputs: inputs,
outputs: outputs,
inputsById: inputsById,
outputsById: outputsById
});
}, function onReject(e) {
//console.log(e)
//reject('Something went wrong while requesting MIDIAccess', e)
initialized = true;
resolve({ midi: midi, jazz: jazz });
});
// browsers without WebMIDI API
} else {
initialized = true;
resolve({ midi: midi });
}
});
}
var _getMIDIAccess = function getMIDIAccess() {
if (initialized === false) {
console.warn('please call qambi.init() first');
} else {
exports.getMIDIAccess = _getMIDIAccess = function getMIDIAccess() {
return MIDIAccess;
};
return _getMIDIAccess();
}
return false;
};
exports.getMIDIAccess = _getMIDIAccess;
var _getMIDIOutputs = function getMIDIOutputs() {
if (initialized === false) {
console.warn('please call qambi.init() first');
} else {
exports.getMIDIOutputs = _getMIDIOutputs = function getMIDIOutputs() {
return outputs;
};
return _getMIDIOutputs();
}
return false;
};
exports.getMIDIOutputs = _getMIDIOutputs;
var _getMIDIInputs = function getMIDIInputs() {
if (initialized === false) {
console.warn('please call qambi.init() first');
} else {
exports.getMIDIInputs = _getMIDIInputs = function getMIDIInputs() {
return inputs;
};
return _getMIDIInputs();
}
return false;
};
exports.getMIDIInputs = _getMIDIInputs;
var _getMIDIOutputIds = function getMIDIOutputIds() {
if (initialized === false) {
console.warn('please call qambi.init() first');
} else {
exports.getMIDIOutputIds = _getMIDIOutputIds = function getMIDIOutputIds() {
return outputIds;
};
return _getMIDIOutputIds();
}
return false;
};
exports.getMIDIOutputIds = _getMIDIOutputIds;
var _getMIDIInputIds = function getMIDIInputIds() {
if (initialized === false) {
console.warn('please call qambi.init() first');
} else {
exports.getMIDIInputIds = _getMIDIInputIds = function getMIDIInputIds() {
return inputIds;
};
return _getMIDIInputIds();
}
return false;
};
exports.getMIDIInputIds = _getMIDIInputIds;
var _getMIDIOutputById = function getMIDIOutputById(id) {
if (initialized === false) {
console.warn('please call qambi.init() first');
} else {
exports.getMIDIOutputById = _getMIDIOutputById = function getMIDIOutputById(_id) {
return outputsById.get(_id);
};
return _getMIDIOutputById(id);
}
return false;
};
exports.getMIDIOutputById = _getMIDIOutputById;
var _getMIDIInputById = function getMIDIInputById(id) {
if (initialized === false) {
console.warn('please call qambi.init() first');
} else {
exports.getMIDIInputById = _getMIDIInputById = function getMIDIInputById(_id) {
return inputsById.get(_id);
};
return _getMIDIInputById(id);
}
return false;
};
/*
export function initMidiSong(song){
songMidiEventListener = function(e){
//console.log(e)
handleMidiMessageSong(song, e, this);
};
// by default a song listens to all available midi-in ports
inputs.forEach(function(port){
port.addEventListener('midimessage', songMidiEventListener);
song.midiInputs.set(port.id, port);
});
outputs.forEach(function(port){
song.midiOutputs.set(port.id, port);
});
}
export function setMidiInputSong(song, id, flag){
let input = inputs.get(id);
if(input === undefined){
warn('no midi input with id', id, 'found');
return;
}
if(flag === false){
song.midiInputs.delete(id);
input.removeEventListener('midimessage', songMidiEventListener);
}else{
song.midiInputs.set(id, input);
input.addEventListener('midimessage', songMidiEventListener);
}
let tracks = song.tracks;
for(let track of tracks){
track.setMidiInput(id, flag);
}
}
export function setMidiOutputSong(song, id, flag){
let output = outputs.get(id);
if(output === undefined){
warn('no midi output with id', id, 'found');
return;
}
if(flag === false){
song.midiOutputs.delete(id);
let time = song.scheduler.lastEventTime + 100;
output.send([0xB0, 0x7B, 0x00], time); // stop all notes
output.send([0xB0, 0x79, 0x00], time); // reset all controllers
}else{
song.midiOutputs.set(id, output);
}
let tracks = song.tracks;
for(let track of tracks){
track.setMidiOutput(id, flag);
}
}
function handleMidiMessageSong(song, midiMessageEvent, input){
let midiEvent = new MidiEvent(song.ticks, ...midiMessageEvent.data);
//console.log(midiMessageEvent.data);
let tracks = song.tracks;
for(let track of tracks){
//console.log(track.midiInputs, input);
//if(midiEvent.channel === track.channel || track.channel === 0 || track.channel === 'any'){
// handleMidiMessageTrack(midiEvent, track);
//}
// like in Cubase, midi events from all devices, sent on any midi channel are forwarded to all tracks
// set track.monitor to false if you don't want to receive midi events on a certain track
// note that track.monitor is by default set to false and that track.monitor is automatically set to true
// if you are recording on that track
//console.log(track.monitor, track.id, input.id);
if(track.monitor === true && track.midiInputs.get(input.id) !== undefined){
handleMidiMessageTrack(midiEvent, track, input);
}
}
let listeners = song.midiEventListeners.get(midiEvent.type);
if(listeners !== undefined){
for(let listener of listeners){
listener(midiEvent, input);
}
}
}
function handleMidiMessageTrack(track, midiEvent, input){
let song = track.song,
note, listeners, channel;
//data = midiMessageEvent.data,
//midiEvent = createMidiEvent(song.ticks, data[0], data[1], data[2]);
//midiEvent.source = midiMessageEvent.srcElement.name;
//console.log(midiMessageEvent)
//console.log('---->', midiEvent.type);
// add the exact time of this event so we can calculate its ticks position
midiEvent.recordMillis = context.currentTime * 1000; // millis
midiEvent.state = 'recorded';
if(midiEvent.type === 144){
note = createMidiNote(midiEvent);
track.recordingNotes[midiEvent.data1] = note;
//track.song.recordingNotes[note.id] = note;
}else if(midiEvent.type === 128){
note = track.recordingNotes[midiEvent.data1];
// check if the note exists: if the user plays notes on her keyboard before the midi system has
// been fully initialized, it can happen that the first incoming midi event is a NOTE OFF event
if(note === undefined){
return;
}
note.addNoteOff(midiEvent);
delete track.recordingNotes[midiEvent.data1];
//delete track.song.recordingNotes[note.id];
}
//console.log(song.preroll, song.recording, track.recordEnabled);
if((song.prerolling || song.recording) && track.recordEnabled === 'midi'){
if(midiEvent.type === 144){
track.song.recordedNotes.push(note);
}
track.recordPart.addEvent(midiEvent);
// song.recordedEvents is used in the key editor
track.song.recordedEvents.push(midiEvent);
}else if(track.enableRetrospectiveRecording){
track.retrospectiveRecording.push(midiEvent);
}
// call all midi event listeners
listeners = track.midiEventListeners[midiEvent.type];
if(listeners !== undefined){
objectForEach(listeners, function(listener){
listener(midiEvent, input);
});
}
channel = track.channel;
if(channel === 'any' || channel === undefined || isNaN(channel) === true){
channel = 0;
}
objectForEach(track.midiOutputs, function(output){
//console.log('midi out', output, midiEvent.type);
if(midiEvent.type === 128 || midiEvent.type === 144 || midiEvent.type === 176){
//console.log(midiEvent.type, midiEvent.data1, midiEvent.data2);
output.send([midiEvent.type, midiEvent.data1, midiEvent.data2]);
// }else if(midiEvent.type === 192){
// output.send([midiEvent.type + channel, midiEvent.data1]);
}
//output.send([midiEvent.status + channel, midiEvent.data1, midiEvent.data2]);
});
// @TODO: maybe a track should be able to send its event to both a midi-out port and an internal heartbeat song?
//console.log(track.routeToMidiOut);
if(track.routeToMidiOut === false){
midiEvent.track = track;
track.instrument.processEvent(midiEvent);
}
}
function addMidiEventListener(...args){ // caller can be a track or a song
let id = midiEventListenerId++;
let listener;
types = {},
ids = [],
loop;
// should I inline this?
loop = function(args){
for(let arg of args){
let type = typeString(arg);
//console.log(type);
if(type === 'array'){
loop(arg);
}else if(type === 'function'){
listener = arg;
}else if(isNaN(arg) === false){
arg = parseInt(arg, 10);
if(sequencer.checkEventType(arg) !== false){
types[arg] = arg;
}
}else if(type === 'string'){
if(sequencer.checkEventType(arg) !== false){
arg = sequencer.midiEventNumberByName(arg);
types[arg] = arg;
}
}
}
};
loop(args, 0, args.length);
//console.log('types', types, 'listener', listener);
objectForEach(types, function(type){
//console.log(type);
if(obj.midiEventListeners[type] === undefined){
obj.midiEventListeners[type] = {};
}
obj.midiEventListeners[type][id] = listener;
ids.push(type + '_' + id);
});
//console.log(obj.midiEventListeners);
return ids.length === 1 ? ids[0] : ids;
}
function removeMidiEventListener(id, obj){
var type;
id = id.split('_');
type = id[0];
id = id[1];
delete obj.midiEventListeners[type][id];
}
function removeMidiEventListeners(){
}
*/
exports.getMIDIInputById = _getMIDIInputById;