UNPKG

qambi

Version:

MIDI sequencer, loads MIDI files, can record and playback MIDI, uses WebMIDI and WebAudio

495 lines (413 loc) 13.8 kB
'use strict'; 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;