UNPKG

lunchpad

Version:

interface for the novation launchpad mini, for node and the browser

621 lines (559 loc) 17.9 kB
function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var eventemitter3 = {exports: {}}; var hasRequiredEventemitter3; function requireEventemitter3 () { if (hasRequiredEventemitter3) return eventemitter3.exports; hasRequiredEventemitter3 = 1; (function (module) { var has = Object.prototype.hasOwnProperty , prefix = '~'; /** * Constructor to create a storage for our `EE` objects. * An `Events` instance is a plain object whose properties are event names. * * @constructor * @private */ function Events() {} // // We try to not inherit from `Object.prototype`. In some engines creating an // instance in this way is faster than calling `Object.create(null)` directly. // If `Object.create(null)` is not supported we prefix the event names with a // character to make sure that the built-in object properties are not // overridden or used as an attack vector. // if (Object.create) { Events.prototype = Object.create(null); // // This hack is needed because the `__proto__` property is still inherited in // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. // if (!new Events().__proto__) prefix = false; } /** * Representation of a single event listener. * * @param {Function} fn The listener function. * @param {*} context The context to invoke the listener with. * @param {Boolean} [once=false] Specify if the listener is a one-time listener. * @constructor * @private */ function EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; } /** * Add a listener for a given event. * * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} context The context to invoke the listener with. * @param {Boolean} once Specify if the listener is a one-time listener. * @returns {EventEmitter} * @private */ function addListener(emitter, event, fn, context, once) { if (typeof fn !== 'function') { throw new TypeError('The listener must be a function'); } var listener = new EE(fn, context || emitter, once) , evt = prefix ? prefix + event : event; if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); else emitter._events[evt] = [emitter._events[evt], listener]; return emitter; } /** * Clear event by name. * * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. * @param {(String|Symbol)} evt The Event name. * @private */ function clearEvent(emitter, evt) { if (--emitter._eventsCount === 0) emitter._events = new Events(); else delete emitter._events[evt]; } /** * Minimal `EventEmitter` interface that is molded against the Node.js * `EventEmitter` interface. * * @constructor * @public */ function EventEmitter() { this._events = new Events(); this._eventsCount = 0; } /** * Return an array listing the events for which the emitter has registered * listeners. * * @returns {Array} * @public */ EventEmitter.prototype.eventNames = function eventNames() { var names = [] , events , name; if (this._eventsCount === 0) return names; for (name in (events = this._events)) { if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); } if (Object.getOwnPropertySymbols) { return names.concat(Object.getOwnPropertySymbols(events)); } return names; }; /** * Return the listeners registered for a given event. * * @param {(String|Symbol)} event The event name. * @returns {Array} The registered listeners. * @public */ EventEmitter.prototype.listeners = function listeners(event) { var evt = prefix ? prefix + event : event , handlers = this._events[evt]; if (!handlers) return []; if (handlers.fn) return [handlers.fn]; for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { ee[i] = handlers[i].fn; } return ee; }; /** * Return the number of listeners listening to a given event. * * @param {(String|Symbol)} event The event name. * @returns {Number} The number of listeners. * @public */ EventEmitter.prototype.listenerCount = function listenerCount(event) { var evt = prefix ? prefix + event : event , listeners = this._events[evt]; if (!listeners) return 0; if (listeners.fn) return 1; return listeners.length; }; /** * Calls each of the listeners registered for a given event. * * @param {(String|Symbol)} event The event name. * @returns {Boolean} `true` if the event had listeners, else `false`. * @public */ EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return false; var listeners = this._events[evt] , len = arguments.length , args , i; if (listeners.fn) { if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); switch (len) { case 1: return listeners.fn.call(listeners.context), true; case 2: return listeners.fn.call(listeners.context, a1), true; case 3: return listeners.fn.call(listeners.context, a1, a2), true; case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; } for (i = 1, args = new Array(len -1); i < len; i++) { args[i - 1] = arguments[i]; } listeners.fn.apply(listeners.context, args); } else { var length = listeners.length , j; for (i = 0; i < length; i++) { if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); switch (len) { case 1: listeners[i].fn.call(listeners[i].context); break; case 2: listeners[i].fn.call(listeners[i].context, a1); break; case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; default: if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { args[j - 1] = arguments[j]; } listeners[i].fn.apply(listeners[i].context, args); } } } return true; }; /** * Add a listener for a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.on = function on(event, fn, context) { return addListener(this, event, fn, context, false); }; /** * Add a one-time listener for a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.once = function once(event, fn, context) { return addListener(this, event, fn, context, true); }; /** * Remove the listeners of a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn Only remove the listeners that match this function. * @param {*} context Only remove the listeners that have this context. * @param {Boolean} once Only remove one-time listeners. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return this; if (!fn) { clearEvent(this, evt); return this; } var listeners = this._events[evt]; if (listeners.fn) { if ( listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context) ) { clearEvent(this, evt); } } else { for (var i = 0, events = [], length = listeners.length; i < length; i++) { if ( listeners[i].fn !== fn || (once && !listeners[i].once) || (context && listeners[i].context !== context) ) { events.push(listeners[i]); } } // // Reset the array, or remove it completely if we have no more listeners. // if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; else clearEvent(this, evt); } return this; }; /** * Remove all listeners, or those of the specified event. * * @param {(String|Symbol)} [event] The event name. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { var evt; if (event) { evt = prefix ? prefix + event : event; if (this._events[evt]) clearEvent(this, evt); } else { this._events = new Events(); this._eventsCount = 0; } return this; }; // // Alias methods names because people roll like that. // EventEmitter.prototype.off = EventEmitter.prototype.removeListener; EventEmitter.prototype.addListener = EventEmitter.prototype.on; // // Expose the prefix. // EventEmitter.prefixed = prefix; // // Allow `EventEmitter` to be imported as module namespace. // EventEmitter.EventEmitter = EventEmitter; // // Expose the module. // { module.exports = EventEmitter; } } (eventemitter3)); return eventemitter3.exports; } var eventemitter3Exports = requireEventemitter3(); var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports); const store = {}; class Color { static getRandomColor() { return Color.getColor(Math.floor(Math.random() * 4), Math.floor(Math.random() * 4)); } static getColor(red, green) { if (!store[red + '_' + green]) { store[red + '_' + green] = new Color(red, green); } return store[red + '_' + green]; } constructor(red, green) { this.red = this.clamp(red); this.green = this.clamp(green); this.code = this.calcCode(red, green); } lighter() { return Color.getColor(this.clamp(this.getRed() + 1), this.clamp(this.getGreen() + 1)); } darker() { return Color.getColor(this.clamp(this.getRed() + 1), this.clamp(this.getGreen() + 1)); } getRed() { return this.red; } getGreen() { return this.green; } getCode() { return this.code; } getRgb() { let r = Math.floor((255 / 3) * this.getRed()); let g = Math.floor((255 / 3) * this.getGreen()); return '#' + this.pad(r.toString(16)) + this.pad(g.toString(16)) + '00'; } calcCode(red, green) { const redStr = Number(red).toString(2); const greenStr = Number(green).toString(2); return parseInt(this.pad(greenStr) + '00' + this.pad(redStr), 2); } pad(val, count = 2) { if (val.length < count) { return '0'.repeat(count - val.length) + val; } return val; } clamp(val) { return Math.max(0, Math.min(val, 3)); } } Color.BLACK = Color.getColor(0, 0); Color.RED = Color.getColor(3, 0); Color.GREEN = Color.getColor(0, 3); Color.AMBER = Color.getColor(3, 3); function generateBlankSquares(color = Color.BLACK) { let squares = []; for (let x = 0; x < 8; x++) { let row = []; for (let y = 0; y < 8; y++) { row.push(color); } squares.push(row); } return squares; } class LaunchpadBase extends EventEmitter { constructor() { super(); this.squares = generateBlankSquares(Color.BLACK); this.functionX = [Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK]; this.functionY = [Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK]; } clearSquares() { this.updateBoard(generateBlankSquares(Color.BLACK)); return this; } clearAll() { this.updateBoard(generateBlankSquares(Color.BLACK), [Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK], [Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK]); return this; } getSquare(x, y) { return this.squares[x][y]; } setSquare(x, y, color) { this.squares[x][y] = color; this._setSquare(x, y, color); return this; } getFunctionX(x) { return this.functionX[x]; } setFunctionX(x, color) { this.functionX[x] = color; this._setFunctionX(x, color); return this; } getFunctionY(y) { return this.functionX[y]; } setFunctionY(y, color) { this.functionY[y] = color; this._setFunctionY(y, color); return this; } flush() { this._flush(); } _selectSquare(x, y) { this.emit('input', x, y); } _selectFunctionX(x) { this.emit('functionX', x); } _selectFunctionY(y) { this.emit('functionY', y); } updateBoard(squares, functionX, functionY) { if (squares) { for (let x = 0; x < squares.length; x++) { for (let y = 0; y < squares[x].length; y++) { let color = squares[x][y]; if (!color) { continue; } this.setSquare(x, y, color); } } } if (functionX) { for (let x = 0; x < functionX.length; x++) { let color = functionX[x]; if (!color) { continue; } this.setFunctionX(x, color); } } if (functionY) { for (let y = 0; y < functionY.length; y++) { let color = functionY[y]; if (!color) { continue; } this.setFunctionY(y, color); } } return this; } } class LaunchpadMidi extends LaunchpadBase { _setSquare(x, y, color) { this._send(144, this._getSquareCoordinate(x, y), color.getCode()); } _setFunctionX(x, color) { this._send(176, this._getFunctionXCoordinate(x), color.getCode()); } _setFunctionY(y, color) { this._send(144, this._getFunctionYCoordinate(y), color.getCode()); } _send(order, note, velocity) { throw Error('missing _send implementation'); } _flush() { //noop for now } _handleMidiMessage(message) { if (message[2] < 127) { return; } if (message[0] === 176) { this._selectFunctionX(message[1] - 104); } else { const x = message[1] % 16; const y = Math.floor(message[1] / 16) * -1 + 7; if (x === 8) { this._selectFunctionY(y); } else { this._selectSquare(x, y); } } } _getFunctionXCoordinate(x) { return x + 104; } _getFunctionYCoordinate(y) { return this._getSquareCoordinate(8, y); } _getSquareCoordinate(x, y) { return (((y - 7) * -1) * 16) + x; } } class LaunchpadBrowser extends LaunchpadMidi { constructor(input, output) { super(); this.output = output; input.onmidimessage = (event) => { this._handleMidiMessage(event.data); }; } _send(order, note, velocity) { this.output.send([order, note, velocity]); } } function isLaunchpadPort(name) { return name.indexOf('Launchpad Mini') > -1; } async function initializeBrowser(launchpadNumber = 1) { if (!navigator.requestMIDIAccess) { throw new Error('browser does not support requestMIDIAccess'); } const midiAccess = await navigator.requestMIDIAccess(); const { input, output } = getLaunchpadBrowser(midiAccess, launchpadNumber); const launchpad = new LaunchpadBrowser(input, output); launchpad.clearAll(); return launchpad; } function getLaunchpadBrowser(midiAccess, launchpadNumber) { let lpInput = null; let lpOutput = null; let inputsFound = 0; let outputsFound = 0; for (let input of midiAccess.inputs.values()) { if (!isLaunchpadPort(input.name)) continue; inputsFound++; if (inputsFound === launchpadNumber) { lpInput = input; break; } } for (let output of midiAccess.outputs.values()) { if (!isLaunchpadPort(output.name)) continue; outputsFound++; if (outputsFound === launchpadNumber) { lpOutput = output; break; } } if (inputsFound == 0 || outputsFound == 0) { throw new Error('no launchpad found'); } if (!lpInput || !lpOutput) { throw new Error(`launchpad #${launchpadNumber} not found`); } return { input: lpInput, output: lpOutput }; } var indexBrowser = { initialize: initializeBrowser, Color, }; export { indexBrowser as default };