webmidi
Version:
WEBMIDI.js makes it easy to talk to MIDI instruments from a browser or from Node.js. It simplifies the control of external or virtual MIDI instruments with functions such as playNote(), sendPitchBend(), sendControlChange(), etc. It also allows reacting to
1,460 lines (1,417 loc) • 495 kB
JavaScript
/**
* WEBMIDI.js v3.1.12
* A JavaScript library to kickstart your MIDI projects
* https://webmidijs.org
* Build generated on November 22nd, 2024.
*
* © Copyright 2015-2024, Jean-Philippe Côté.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
/* Version: 3.1.12 - November 22, 2024 18:55:06 */
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/**
* The `EventEmitter` class provides methods to implement the _observable_ design pattern. This
* pattern allows one to _register_ a function to execute when a specific event is _emitted_ by the
* emitter.
*
* It is intended to be an abstract class meant to be extended by (or mixed into) other objects.
*/
class EventEmitter {
/**
* Creates a new `EventEmitter`object.
*
* @param {boolean} [eventsSuspended=false] Whether the `EventEmitter` is initially in a suspended
* state (i.e. not executing callbacks).
*/
constructor(eventsSuspended = false) {
/**
* An object containing a property for each event with at least one registered listener. Each
* event property contains an array of all the [`Listener`]{@link Listener} objects registered
* for the event.
*
* @type {Object}
* @readonly
*/
this.eventMap = {};
/**
* Whether or not the execution of callbacks is currently suspended for this emitter.
*
* @type {boolean}
*/
this.eventsSuspended = eventsSuspended == true ? true : false;
}
/**
* The callback function is executed when the associated event is triggered via [`emit()`](#emit).
* The [`emit()`](#emit) method relays all additional arguments it received to the callback
* functions. Since [`emit()`](#emit) can be passed a variable number of arguments, it is up to
* the developer to make sure the arguments match those of the associated callback. In addition,
* the callback also separately receives all the arguments present in the listener's
* [`arguments`](Listener#arguments) property. This makes it easy to pass data from where the
* listener is added to where the listener is executed.
*
* @callback EventEmitter~callback
* @param {...*} [args] A variable number of arguments matching the ones (if any) that were passed
* to the [`emit()`](#emit) method (except, the first one) followed by the arguments found in the
* listener's [`arguments`](Listener#arguments) array.
*/
/**
* Adds a listener for the specified event. It returns the [`Listener`]{@link Listener} object
* that was created and attached to the event.
*
* To attach a global listener that will be triggered for any events, use
* [`EventEmitter.ANY_EVENT`]{@link #ANY_EVENT} as the first parameter. Note that a global
* listener will also be triggered by non-registered events.
*
* @param {string|Symbol} event The event to listen to.
* @param {EventEmitter~callback} callback The callback function to execute when the event occurs.
* @param {Object} [options={}]
* @param {Object} [options.context=this] The value of `this` in the callback function.
* @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning
* of the listeners array and thus executed first.
* @param {number} [options.duration=Infinity] The number of milliseconds before the listener
* automatically expires.
* @param {number} [options.remaining=Infinity] The number of times after which the callback
* should automatically be removed.
* @param {array} [options.arguments] An array of arguments which will be passed separately to the
* callback function. This array is stored in the [`arguments`]{@link Listener#arguments}
* property of the [`Listener`]{@link Listener} object and can be retrieved or modified as
* desired.
*
* @returns {Listener} The newly created [`Listener`]{@link Listener} object.
*
* @throws {TypeError} The `event` parameter must be a string or
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}.
* @throws {TypeError} The `callback` parameter must be a function.
*/
addListener(event, callback, options = {}) {
if (typeof event === "string" && event.length < 1 || event instanceof String && event.length < 1 || typeof event !== "string" && !(event instanceof String) && event !== EventEmitter.ANY_EVENT) {
throw new TypeError("The 'event' parameter must be a string or EventEmitter.ANY_EVENT.");
}
if (typeof callback !== "function") throw new TypeError("The callback must be a function.");
const listener = new Listener(event, this, callback, options);
if (!this.eventMap[event]) this.eventMap[event] = [];
if (options.prepend) {
this.eventMap[event].unshift(listener);
} else {
this.eventMap[event].push(listener);
}
return listener;
}
/**
* Adds a one-time listener for the specified event. The listener will be executed once and then
* destroyed. It returns the [`Listener`]{@link Listener} object that was created and attached
* to the event.
*
* To attach a global listener that will be triggered for any events, use
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} as the first parameter. Note that a
* global listener will also be triggered by non-registered events.
*
* @param {string|Symbol} event The event to listen to
* @param {EventEmitter~callback} callback The callback function to execute when the event occurs
* @param {Object} [options={}]
* @param {Object} [options.context=this] The context to invoke the callback function in.
* @param {boolean} [options.prepend=false] Whether the listener should be added at the beginning
* of the listeners array and thus executed first.
* @param {number} [options.duration=Infinity] The number of milliseconds before the listener
* automatically expires.
* @param {array} [options.arguments] An array of arguments which will be passed separately to the
* callback function. This array is stored in the [`arguments`]{@link Listener#arguments}
* property of the [`Listener`]{@link Listener} object and can be retrieved or modified as
* desired.
*
* @returns {Listener} The newly created [`Listener`]{@link Listener} object.
*
* @throws {TypeError} The `event` parameter must be a string or
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}.
* @throws {TypeError} The `callback` parameter must be a function.
*/
addOneTimeListener(event, callback, options = {}) {
options.remaining = 1;
this.addListener(event, callback, options);
}
/**
* Identifier to use when adding or removing a listener that should be triggered when any events
* occur.
*
* @type {Symbol}
*/
static get ANY_EVENT() {
return Symbol.for("Any event");
}
/**
* Returns `true` if the specified event has at least one registered listener. If no event is
* specified, the method returns `true` if any event has at least one listener registered (this
* includes global listeners registered to
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}).
*
* Note: to specifically check for global listeners added with
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}, use
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} as the parameter.
*
* @param {string|Symbol} [event=(any event)] The event to check
* @param {function|Listener} [callback=(any callback)] The actual function that was added to the
* event or the {@link Listener} object returned by `addListener()`.
* @returns {boolean}
*/
hasListener(event, callback) {
if (event === undefined) {
// Check for ANY_EVENT
if (this.eventMap[EventEmitter.ANY_EVENT] && this.eventMap[EventEmitter.ANY_EVENT].length > 0) {
return true;
}
// Check for any regular events
return Object.entries(this.eventMap).some(([, value]) => {
return value.length > 0;
});
} else {
if (this.eventMap[event] && this.eventMap[event].length > 0) {
if (callback instanceof Listener) {
let result = this.eventMap[event].filter(listener => listener === callback);
return result.length > 0;
} else if (typeof callback === "function") {
let result = this.eventMap[event].filter(listener => listener.callback === callback);
return result.length > 0;
} else if (callback != undefined) {
return false;
}
return true;
} else {
return false;
}
}
}
/**
* An array of all the unique event names for which the emitter has at least one registered
* listener.
*
* Note: this excludes global events registered with
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} because they are not tied to a
* specific event.
*
* @type {string[]}
* @readonly
*/
get eventNames() {
return Object.keys(this.eventMap);
}
/**
* Returns an array of all the [`Listener`]{@link Listener} objects that have been registered for
* a specific event.
*
* Please note that global events (those added with
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}) are not returned for "regular"
* events. To get the list of global listeners, specifically use
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} as the parameter.
*
* @param {string|Symbol} event The event to get listeners for.
* @returns {Listener[]} An array of [`Listener`]{@link Listener} objects.
*/
getListeners(event) {
return this.eventMap[event] || [];
}
/**
* Suspends execution of all callbacks functions registered for the specified event type.
*
* You can suspend execution of callbacks registered with
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} by passing
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} to `suspendEvent()`. Beware that this
* will not suspend all callbacks but only those registered with
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}. While this may seem counter-intuitive
* at first glance, it allows the selective suspension of global listeners while leaving other
* listeners alone. If you truly want to suspends all callbacks for a specific
* [`EventEmitter`]{@link EventEmitter}, simply set its `eventsSuspended` property to `true`.
*
* @param {string|Symbol} event The event name (or `EventEmitter.ANY_EVENT`) for which to suspend
* execution of all callback functions.
*/
suspendEvent(event) {
this.getListeners(event).forEach(listener => {
listener.suspended = true;
});
}
/**
* Resumes execution of all suspended callback functions registered for the specified event type.
*
* You can resume execution of callbacks registered with
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} by passing
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} to `unsuspendEvent()`. Beware that
* this will not resume all callbacks but only those registered with
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}. While this may seem
* counter-intuitive, it allows the selective unsuspension of global listeners while leaving other
* callbacks alone.
*
* @param {string|Symbol} event The event name (or `EventEmitter.ANY_EVENT`) for which to resume
* execution of all callback functions.
*/
unsuspendEvent(event) {
this.getListeners(event).forEach(listener => {
listener.suspended = false;
});
}
/**
* Returns the number of listeners registered for a specific event.
*
* Please note that global events (those added with
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}) do not count towards the remaining
* number for a "regular" event. To get the number of global listeners, specifically use
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} as the parameter.
*
* @param {string|Symbol} event The event which is usually a string but can also be the special
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} symbol.
* @returns {number} An integer representing the number of listeners registered for the specified
* event.
*/
getListenerCount(event) {
return this.getListeners(event).length;
}
/**
* Executes the callback function of all the [`Listener`]{@link Listener} objects registered for
* a given event. The callback functions are passed the additional arguments passed to `emit()`
* (if any) followed by the arguments present in the [`arguments`](Listener#arguments) property of
* the [`Listener`](Listener) object (if any).
*
* If the [`eventsSuspended`]{@link #eventsSuspended} property is `true` or the
* [`Listener.suspended`]{@link Listener#suspended} property is `true`, the callback functions
* will not be executed.
*
* This function returns an array containing the return values of each of the callbacks.
*
* It should be noted that the regular listeners are triggered first followed by the global
* listeners (those added with [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}).
*
* @param {string} event The event
* @param {...*} args Arbitrary number of arguments to pass along to the callback functions
*
* @returns {Array} An array containing the return value of each of the executed listener
* functions.
*
* @throws {TypeError} The `event` parameter must be a string.
*/
emit(event, ...args) {
if (typeof event !== "string" && !(event instanceof String)) {
throw new TypeError("The 'event' parameter must be a string.");
}
if (this.eventsSuspended) return;
// We collect return values from all listeners here
let results = [];
// We must make sure that we do not have undefined otherwise concat() will add an undefined
// entry in the array.
let listeners = this.eventMap[EventEmitter.ANY_EVENT] || [];
if (this.eventMap[event]) listeners = listeners.concat(this.eventMap[event]);
listeners.forEach(listener => {
// This is the per-listener suspension check
if (listener.suspended) return;
let params = [...args];
if (Array.isArray(listener.arguments)) params = params.concat(listener.arguments);
if (listener.remaining > 0) {
results.push(listener.callback.apply(listener.context, params));
listener.count++;
}
if (--listener.remaining < 1) listener.remove();
});
return results;
}
/**
* Removes all the listeners that were added to the object upon which the method is called and
* that match the specified criterias. If no parameters are passed, all listeners added to this
* object will be removed. If only the `event` parameter is passed, all listeners for that event
* will be removed from that object. You can remove global listeners by using
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} as the first parameter.
*
* To use more granular options, you must at least define the `event`. Then, you can specify the
* callback to match or one or more of the additional options.
*
* @param {string} [event] The event name.
* @param {EventEmitter~callback} [callback] Only remove the listeners that match this exact
* callback function.
* @param {Object} [options]
* @param {*} [options.context] Only remove the listeners that have this exact context.
* @param {number} [options.remaining] Only remove the listener if it has exactly that many
* remaining times to be executed.
*/
removeListener(event, callback, options = {}) {
if (event === undefined) {
this.eventMap = {};
return;
} else if (!this.eventMap[event]) {
return;
}
// Find listeners that do not match the criterias (those are the ones we will keep)
let listeners = this.eventMap[event].filter(listener => {
return callback && listener.callback !== callback || options.remaining && options.remaining !== listener.remaining || options.context && options.context !== listener.context;
});
if (listeners.length) {
this.eventMap[event] = listeners;
} else {
delete this.eventMap[event];
}
}
/**
* The `waitFor()` method is an async function which returns a promise. The promise is fulfilled
* when the specified event occurs. The event can be a regular event or
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} (if you want to resolve as soon as any
* event is emitted).
*
* If the `duration` option is set, the promise will only be fulfilled if the event is emitted
* within the specified duration. If the event has not been fulfilled after the specified
* duration, the promise is rejected. This makes it super easy to wait for an event and timeout
* after a certain time if the event is not triggered.
*
* @param {string|Symbol} event The event to wait for
* @param {Object} [options={}]
* @param {number} [options.duration=Infinity] The number of milliseconds to wait before the
* promise is automatically rejected.
*/
async waitFor(event, options = {}) {
options.duration = parseInt(options.duration);
if (isNaN(options.duration) || options.duration <= 0) options.duration = Infinity;
return new Promise((resolve, reject) => {
let timeout;
let listener = this.addListener(event, () => {
clearTimeout(timeout);
resolve();
}, {
remaining: 1
});
if (options.duration !== Infinity) {
timeout = setTimeout(() => {
listener.remove();
reject("The duration expired before the event was emitted.");
}, options.duration);
}
});
}
/**
* The number of unique events that have registered listeners.
*
* Note: this excludes global events registered with
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT} because they are not tied to a
* specific event.
*
* @type {number}
* @readonly
*/
get eventCount() {
return Object.keys(this.eventMap).length;
}
}
/**
* The `Listener` class represents a single event listener object. Such objects keep all relevant
* contextual information such as the event being listened to, the object the listener was attached
* to, the callback function and so on.
*
*/
class Listener {
/**
* Creates a new `Listener` object
*
* @param {string|Symbol} event The event being listened to
* @param {EventEmitter} target The [`EventEmitter`]{@link EventEmitter} object that the listener
* is attached to.
* @param {EventEmitter~callback} callback The function to call when the listener is triggered
* @param {Object} [options={}]
* @param {Object} [options.context=target] The context to invoke the listener in (a.k.a. the
* value of `this` inside the callback function).
* @param {number} [options.remaining=Infinity] The remaining number of times after which the
* callback should automatically be removed.
* @param {array} [options.arguments] An array of arguments that will be passed separately to the
* callback function upon execution. The array is stored in the [`arguments`]{@link #arguments}
* property and can be retrieved or modified as desired.
*
* @throws {TypeError} The `event` parameter must be a string or
* [`EventEmitter.ANY_EVENT`]{@link EventEmitter#ANY_EVENT}.
* @throws {ReferenceError} The `target` parameter is mandatory.
* @throws {TypeError} The `callback` must be a function.
*/
constructor(event, target, callback, options = {}) {
if (typeof event !== "string" && !(event instanceof String) && event !== EventEmitter.ANY_EVENT) {
throw new TypeError("The 'event' parameter must be a string or EventEmitter.ANY_EVENT.");
}
if (!target) {
throw new ReferenceError("The 'target' parameter is mandatory.");
}
if (typeof callback !== "function") {
throw new TypeError("The 'callback' must be a function.");
}
// Convert single value argument to array
if (options.arguments !== undefined && !Array.isArray(options.arguments)) {
options.arguments = [options.arguments];
}
// Define default options and merge declared options into them,
options = Object.assign({
context: target,
remaining: Infinity,
arguments: undefined,
duration: Infinity
}, options);
// Make sure it is eventually deleted if a duration is supplied
if (options.duration !== Infinity) {
setTimeout(() => this.remove(), options.duration);
}
/**
* An array of arguments to pass to the callback function upon execution.
* @type {array}
*/
this.arguments = options.arguments;
/**
* The callback function to execute.
* @type {Function}
*/
this.callback = callback;
/**
* The context to execute the callback function in (a.k.a. the value of `this` inside the
* callback function)
* @type {Object}
*/
this.context = options.context;
/**
* The number of times the listener function was executed.
* @type {number}
*/
this.count = 0;
/**
* The event name.
* @type {string}
*/
this.event = event;
/**
* The remaining number of times after which the callback should automatically be removed.
* @type {number}
*/
this.remaining = parseInt(options.remaining) >= 1 ? parseInt(options.remaining) : Infinity;
/**
* Whether this listener is currently suspended or not.
* @type {boolean}
*/
this.suspended = false;
/**
* The object that the event is attached to (or that emitted the event).
* @type {EventEmitter}
*/
this.target = target;
}
/**
* Removes the listener from its target.
*/
remove() {
this.target.removeListener(this.event, this.callback, {
context: this.context,
remaining: this.remaining
});
}
}
/**
* The `Enumerations` class contains enumerations and arrays of elements used throughout the
* library. All its properties are static and should be referenced using the class name. For
* example: `Enumerations.CHANNEL_MESSAGES`.
*
* @license Apache-2.0
* @since 3.0.0
*/
class Enumerations {
/**
* @enum {Object.<string, number>}
* @readonly
* @deprecated since 3.1 (use Enumerations.CHANNEL_MESSAGES instead)
* @private
* @static
*/
static get MIDI_CHANNEL_MESSAGES() {
if (this.validation) {
console.warn("The MIDI_CHANNEL_MESSAGES enum has been deprecated. Use the " + "Enumerations.CHANNEL_MESSAGES enum instead.");
}
return Enumerations.CHANNEL_MESSAGES;
}
/**
* Enumeration of all MIDI channel message names and their associated 4-bit numerical value:
*
* | Message Name | Hexadecimal | Decimal |
* |---------------------|-------------|---------|
* | `noteoff` | 0x8 | 8 |
* | `noteon` | 0x9 | 9 |
* | `keyaftertouch` | 0xA | 10 |
* | `controlchange` | 0xB | 11 |
* | `programchange` | 0xC | 12 |
* | `channelaftertouch` | 0xD | 13 |
* | `pitchbend` | 0xE | 14 |
*
* @enum {Object.<string, number>}
* @readonly
* @since 3.1
* @static
*/
static get CHANNEL_MESSAGES() {
return {
noteoff: 0x8,
// 8
noteon: 0x9,
// 9
keyaftertouch: 0xA,
// 10
controlchange: 0xB,
// 11
programchange: 0xC,
// 12
channelaftertouch: 0xD,
// 13
pitchbend: 0xE // 14
};
}
/**
* A simple array of the 16 valid MIDI channel numbers (`1` to `16`):
*
* @type {number[]}
* @readonly
* @since 3.1
* @static
*/
static get CHANNEL_NUMBERS() {
return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
}
/**
* @type {number[]}
* @readonly
* @deprecated since 3.1 (use Enumerations.CHANNEL_NUMBERS instead)
* @private
* @static
*/
static get MIDI_CHANNEL_NUMBERS() {
if (this.validation) {
console.warn("The MIDI_CHANNEL_NUMBERS array has been deprecated. Use the " + "Enumerations.CHANNEL_NUMBERS array instead.");
}
return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
}
/**
* Enumeration of all MIDI channel mode message names and their associated numerical value:
*
*
* | Message Name | Hexadecimal | Decimal |
* |-----------------------|-------------|---------|
* | `allsoundoff` | 0x78 | 120 |
* | `resetallcontrollers` | 0x79 | 121 |
* | `localcontrol` | 0x7A | 122 |
* | `allnotesoff` | 0x7B | 123 |
* | `omnimodeoff` | 0x7C | 124 |
* | `omnimodeon` | 0x7D | 125 |
* | `monomodeon` | 0x7E | 126 |
* | `polymodeon` | 0x7F | 127 |
*
* @enum {Object.<string, number>}
* @readonly
* @since 3.1
* @static
*/
static get CHANNEL_MODE_MESSAGES() {
return {
allsoundoff: 120,
resetallcontrollers: 121,
localcontrol: 122,
allnotesoff: 123,
omnimodeoff: 124,
omnimodeon: 125,
monomodeon: 126,
polymodeon: 127
};
}
/**
* @enum {Object.<string, number>}
* @deprecated since 3.1 (use Enumerations.CHANNEL_MODE_MESSAGES instead)
* @private
* @readonly
* @static
*/
static get MIDI_CHANNEL_MODE_MESSAGES() {
if (this.validation) {
console.warn("The MIDI_CHANNEL_MODE_MESSAGES enum has been deprecated. Use the " + "Enumerations.CHANNEL_MODE_MESSAGES enum instead.");
}
return Enumerations.CHANNEL_MODE_MESSAGES;
}
/**
* @enum {Object.<string, number>}
* @readonly
* @static
* @private
* @deprecated since version 3.0.26 (use `CONTROL_CHANGE_MESSAGES` instead)
*/
static get MIDI_CONTROL_CHANGE_MESSAGES() {
if (this.validation) {
console.warn("The MIDI_CONTROL_CHANGE_MESSAGES enum has been deprecated. Use the " + "Enumerations.CONTROL_CHANGE_MESSAGES array instead.");
}
return {
bankselectcoarse: 0,
modulationwheelcoarse: 1,
breathcontrollercoarse: 2,
controller3: 3,
footcontrollercoarse: 4,
portamentotimecoarse: 5,
dataentrycoarse: 6,
volumecoarse: 7,
balancecoarse: 8,
controller9: 9,
pancoarse: 10,
expressioncoarse: 11,
effectcontrol1coarse: 12,
effectcontrol2coarse: 13,
controller14: 14,
controller15: 15,
generalpurposeslider1: 16,
generalpurposeslider2: 17,
generalpurposeslider3: 18,
generalpurposeslider4: 19,
controller20: 20,
controller21: 21,
controller22: 22,
controller23: 23,
controller24: 24,
controller25: 25,
controller26: 26,
controller27: 27,
controller28: 28,
controller29: 29,
controller30: 30,
controller31: 31,
bankselectfine: 32,
modulationwheelfine: 33,
breathcontrollerfine: 34,
controller35: 35,
footcontrollerfine: 36,
portamentotimefine: 37,
dataentryfine: 38,
volumefine: 39,
balancefine: 40,
controller41: 41,
panfine: 42,
expressionfine: 43,
effectcontrol1fine: 44,
effectcontrol2fine: 45,
controller46: 46,
controller47: 47,
controller48: 48,
controller49: 49,
controller50: 50,
controller51: 51,
controller52: 52,
controller53: 53,
controller54: 54,
controller55: 55,
controller56: 56,
controller57: 57,
controller58: 58,
controller59: 59,
controller60: 60,
controller61: 61,
controller62: 62,
controller63: 63,
holdpedal: 64,
portamento: 65,
sustenutopedal: 66,
softpedal: 67,
legatopedal: 68,
hold2pedal: 69,
soundvariation: 70,
resonance: 71,
soundreleasetime: 72,
soundattacktime: 73,
brightness: 74,
soundcontrol6: 75,
soundcontrol7: 76,
soundcontrol8: 77,
soundcontrol9: 78,
soundcontrol10: 79,
generalpurposebutton1: 80,
generalpurposebutton2: 81,
generalpurposebutton3: 82,
generalpurposebutton4: 83,
controller84: 84,
controller85: 85,
controller86: 86,
controller87: 87,
controller88: 88,
controller89: 89,
controller90: 90,
reverblevel: 91,
tremololevel: 92,
choruslevel: 93,
celestelevel: 94,
phaserlevel: 95,
databuttonincrement: 96,
databuttondecrement: 97,
nonregisteredparametercoarse: 98,
nonregisteredparameterfine: 99,
registeredparametercoarse: 100,
registeredparameterfine: 101,
controller102: 102,
controller103: 103,
controller104: 104,
controller105: 105,
controller106: 106,
controller107: 107,
controller108: 108,
controller109: 109,
controller110: 110,
controller111: 111,
controller112: 112,
controller113: 113,
controller114: 114,
controller115: 115,
controller116: 116,
controller117: 117,
controller118: 118,
controller119: 119,
allsoundoff: 120,
resetallcontrollers: 121,
localcontrol: 122,
allnotesoff: 123,
omnimodeoff: 124,
omnimodeon: 125,
monomodeon: 126,
polymodeon: 127
};
}
/**
* An array of objects, ordered by control number, describing control change messages. Each object
* in the array has 3 properties with some objects having a fourth one (`position`) :
*
* * `number`: MIDI control number (0-127);
* * `name`: name of emitted event (eg: `bankselectcoarse`, `choruslevel`, etc) that can be
* listened to;
* * `description`: user-friendly description of the controller's purpose;
* * `position` (optional): whether this controller's value should be considered an `msb` or
* `lsb`
*
* Not all controllers have a predefined function. For those that don't, `name` is the word
* "controller" followed by the number (e.g. `controller112`).
*
* | Event name | Control Number |
* |--------------------------------|----------------|
* | `bankselectcoarse` | 0 |
* | `modulationwheelcoarse` | 1 |
* | `breathcontrollercoarse` | 2 |
* | `controller3` | 3 |
* | `footcontrollercoarse` | 4 |
* | `portamentotimecoarse` | 5 |
* | `dataentrycoarse` | 6 |
* | `volumecoarse` | 7 |
* | `balancecoarse` | 8 |
* | `controller9` | 9 |
* | `pancoarse` | 10 |
* | `expressioncoarse` | 11 |
* | `effectcontrol1coarse` | 12 |
* | `effectcontrol2coarse` | 13 |
* | `controller14` | 14 |
* | `controller15` | 15 |
* | `generalpurposecontroller1` | 16 |
* | `generalpurposecontroller2` | 17 |
* | `generalpurposecontroller3` | 18 |
* | `generalpurposecontroller4` | 19 |
* | `controller20` | 20 |
* | `controller21` | 21 |
* | `controller22` | 22 |
* | `controller23` | 23 |
* | `controller24` | 24 |
* | `controller25` | 25 |
* | `controller26` | 26 |
* | `controller27` | 27 |
* | `controller28` | 28 |
* | `controller29` | 29 |
* | `controller30` | 30 |
* | `controller31` | 31 |
* | `bankselectfine` | 32 |
* | `modulationwheelfine` | 33 |
* | `breathcontrollerfine` | 34 |
* | `controller35` | 35 |
* | `footcontrollerfine` | 36 |
* | `portamentotimefine` | 37 |
* | `dataentryfine` | 38 |
* | `channelvolumefine` | 39 |
* | `balancefine` | 40 |
* | `controller41` | 41 |
* | `panfine` | 42 |
* | `expressionfine` | 43 |
* | `effectcontrol1fine` | 44 |
* | `effectcontrol2fine` | 45 |
* | `controller46` | 46 |
* | `controller47` | 47 |
* | `controller48` | 48 |
* | `controller49` | 49 |
* | `controller50` | 50 |
* | `controller51` | 51 |
* | `controller52` | 52 |
* | `controller53` | 53 |
* | `controller54` | 54 |
* | `controller55` | 55 |
* | `controller56` | 56 |
* | `controller57` | 57 |
* | `controller58` | 58 |
* | `controller59` | 59 |
* | `controller60` | 60 |
* | `controller61` | 61 |
* | `controller62` | 62 |
* | `controller63` | 63 |
* | `damperpedal` | 64 |
* | `portamento` | 65 |
* | `sostenuto` | 66 |
* | `softpedal` | 67 |
* | `legatopedal` | 68 |
* | `hold2` | 69 |
* | `soundvariation` | 70 |
* | `resonance` | 71 |
* | `releasetime` | 72 |
* | `attacktime` | 73 |
* | `brightness` | 74 |
* | `decaytime` | 75 |
* | `vibratorate` | 76 |
* | `vibratodepth` | 77 |
* | `vibratodelay` | 78 |
* | `controller79` | 79 |
* | `generalpurposecontroller5` | 80 |
* | `generalpurposecontroller6` | 81 |
* | `generalpurposecontroller7` | 82 |
* | `generalpurposecontroller8` | 83 |
* | `portamentocontrol` | 84 |
* | `controller85` | 85 |
* | `controller86` | 86 |
* | `controller87` | 87 |
* | `highresolutionvelocityprefix` | 88 |
* | `controller89` | 89 |
* | `controller90` | 90 |
* | `effect1depth` | 91 |
* | `effect2depth` | 92 |
* | `effect3depth` | 93 |
* | `effect4depth` | 94 |
* | `effect5depth` | 95 |
* | `dataincrement` | 96 |
* | `datadecrement` | 97 |
* | `nonregisteredparameterfine` | 98 |
* | `nonregisteredparametercoarse` | 99 |
* | `nonregisteredparameterfine` | 100 |
* | `registeredparametercoarse` | 101 |
* | `controller102` | 102 |
* | `controller103` | 103 |
* | `controller104` | 104 |
* | `controller105` | 105 |
* | `controller106` | 106 |
* | `controller107` | 107 |
* | `controller108` | 108 |
* | `controller109` | 109 |
* | `controller110` | 110 |
* | `controller111` | 111 |
* | `controller112` | 112 |
* | `controller113` | 113 |
* | `controller114` | 114 |
* | `controller115` | 115 |
* | `controller116` | 116 |
* | `controller117` | 117 |
* | `controller118` | 118 |
* | `controller119` | 119 |
* | `allsoundoff` | 120 |
* | `resetallcontrollers` | 121 |
* | `localcontrol` | 122 |
* | `allnotesoff` | 123 |
* | `omnimodeoff` | 124 |
* | `omnimodeon` | 125 |
* | `monomodeon` | 126 |
* | `polymodeon` | 127 |
*
* @type {object[]}
* @readonly
* @static
* @since 3.1
*/
static get CONTROL_CHANGE_MESSAGES() {
return [{
number: 0,
name: "bankselectcoarse",
description: "Bank Select (Coarse)",
position: "msb"
}, {
number: 1,
name: "modulationwheelcoarse",
description: "Modulation Wheel (Coarse)",
position: "msb"
}, {
number: 2,
name: "breathcontrollercoarse",
description: "Breath Controller (Coarse)",
position: "msb"
}, {
number: 3,
name: "controller3",
description: "Undefined",
position: "msb"
}, {
number: 4,
name: "footcontrollercoarse",
description: "Foot Controller (Coarse)",
position: "msb"
}, {
number: 5,
name: "portamentotimecoarse",
description: "Portamento Time (Coarse)",
position: "msb"
}, {
number: 6,
name: "dataentrycoarse",
description: "Data Entry (Coarse)",
position: "msb"
}, {
number: 7,
name: "volumecoarse",
description: "Channel Volume (Coarse)",
position: "msb"
}, {
number: 8,
name: "balancecoarse",
description: "Balance (Coarse)",
position: "msb"
}, {
number: 9,
name: "controller9",
description: "Controller 9 (Coarse)",
position: "msb"
}, {
number: 10,
name: "pancoarse",
description: "Pan (Coarse)",
position: "msb"
}, {
number: 11,
name: "expressioncoarse",
description: "Expression Controller (Coarse)",
position: "msb"
}, {
number: 12,
name: "effectcontrol1coarse",
description: "Effect Control 1 (Coarse)",
position: "msb"
}, {
number: 13,
name: "effectcontrol2coarse",
description: "Effect Control 2 (Coarse)",
position: "msb"
}, {
number: 14,
name: "controller14",
description: "Undefined",
position: "msb"
}, {
number: 15,
name: "controller15",
description: "Undefined",
position: "msb"
}, {
number: 16,
name: "generalpurposecontroller1",
description: "General Purpose Controller 1 (Coarse)",
position: "msb"
}, {
number: 17,
name: "generalpurposecontroller2",
description: "General Purpose Controller 2 (Coarse)",
position: "msb"
}, {
number: 18,
name: "generalpurposecontroller3",
description: "General Purpose Controller 3 (Coarse)",
position: "msb"
}, {
number: 19,
name: "generalpurposecontroller4",
description: "General Purpose Controller 4 (Coarse)",
position: "msb"
}, {
number: 20,
name: "controller20",
description: "Undefined",
position: "msb"
}, {
number: 21,
name: "controller21",
description: "Undefined",
position: "msb"
}, {
number: 22,
name: "controller22",
description: "Undefined",
position: "msb"
}, {
number: 23,
name: "controller23",
description: "Undefined",
position: "msb"
}, {
number: 24,
name: "controller24",
description: "Undefined",
position: "msb"
}, {
number: 25,
name: "controller25",
description: "Undefined",
position: "msb"
}, {
number: 26,
name: "controller26",
description: "Undefined",
position: "msb"
}, {
number: 27,
name: "controller27",
description: "Undefined",
position: "msb"
}, {
number: 28,
name: "controller28",
description: "Undefined",
position: "msb"
}, {
number: 29,
name: "controller29",
description: "Undefined",
position: "msb"
}, {
number: 30,
name: "controller30",
description: "Undefined",
position: "msb"
}, {
number: 31,
name: "controller31",
description: "Undefined",
position: "msb"
}, {
number: 32,
name: "bankselectfine",
description: "Bank Select (Fine)",
position: "lsb"
}, {
number: 33,
name: "modulationwheelfine",
description: "Modulation Wheel (Fine)",
position: "lsb"
}, {
number: 34,
name: "breathcontrollerfine",
description: "Breath Controller (Fine)",
position: "lsb"
}, {
number: 35,
name: "controller35",
description: "Undefined",
position: "lsb"
}, {
number: 36,
name: "footcontrollerfine",
description: "Foot Controller (Fine)",
position: "lsb"
}, {
number: 37,
name: "portamentotimefine",
description: "Portamento Time (Fine)",
position: "lsb"
}, {
number: 38,
name: "dataentryfine",
description: "Data Entry (Fine)",
position: "lsb"
}, {
number: 39,
name: "channelvolumefine",
description: "Channel Volume (Fine)",
position: "lsb"
}, {
number: 40,
name: "balancefine",
description: "Balance (Fine)",
position: "lsb"
}, {
number: 41,
name: "controller41",
description: "Undefined",
position: "lsb"
}, {
number: 42,
name: "panfine",
description: "Pan (Fine)",
position: "lsb"
}, {
number: 43,
name: "expressionfine",
description: "Expression Controller (Fine)",
position: "lsb"
}, {
number: 44,
name: "effectcontrol1fine",
description: "Effect control 1 (Fine)",
position: "lsb"
}, {
number: 45,
name: "effectcontrol2fine",
description: "Effect control 2 (Fine)",
position: "lsb"
}, {
number: 46,
name: "controller46",
description: "Undefined",
position: "lsb"
}, {
number: 47,
name: "controller47",
description: "Undefined",
position: "lsb"
}, {
number: 48,
name: "controller48",
description: "General Purpose Controller 1 (Fine)",
position: "lsb"
}, {
number: 49,
name: "controller49",
description: "General Purpose Controller 2 (Fine)",
position: "lsb"
}, {
number: 50,
name: "controller50",
description: "General Purpose Controller 3 (Fine)",
position: "lsb"
}, {
number: 51,
name: "controller51",
description: "General Purpose Controller 4 (Fine)",
position: "lsb"
}, {
number: 52,
name: "controller52",
description: "Undefined",
position: "lsb"
}, {
number: 53,
name: "controller53",
description: "Undefined",
position: "lsb"
}, {
number: 54,
name: "controller54",
description: "Undefined",
position: "lsb"
}, {
number: 55,
name: "controller55",
description: "Undefined",
position: "lsb"
}, {
number: 56,
name: "controller56",
description: "Undefined",
position: "lsb"
}, {
number: 57,
name: "controller57",
description: "Undefined",
position: "lsb"
}, {
number: 58,
name: "controller58",
description: "Undefined",
position: "lsb"
}, {
number: 59,
name: "controller59",
description: "Undefined",
position: "lsb"
}, {
number: 60,
name: "controller60",
description: "Undefined",
position: "lsb"
}, {
number: 61,
name: "controller61",
description: "Undefined",
position: "lsb"
}, {
number: 62,
name: "controller62",
description: "Undefined",
position: "lsb"
}, {
number: 63,
name: "controller63",
description: "Undefined",
position: "lsb"
}, {
number: 64,
name: "damperpedal",
description: "Damper Pedal On/Off"
}, {
number: 65,
name: "portamento",
description: "Portamento On/Off"
}, {
number: 66,
name: "sostenuto",
description: "Sostenuto On/Off"
}, {
number: 67,
name: "softpedal",
description: "Soft Pedal On/Off"
}, {
number: 68,
name: "legatopedal",
description: "Legato Pedal On/Off"
}, {
number: 69,
name: "hold2",
description: "Hold 2 On/Off"
}, {
number: 70,
name: "soundvariation",
description: "Sound Variation",
position: "lsb"
}, {
number: 71,
name: "resonance",
description: "Resonance",
position: "lsb"
}, {
number: 72,
name: "releasetime",
description: "Release Time",
position: "lsb"
}, {
number: 73,
name: "attacktime",
description: "Attack Time",
position: "lsb"
}, {
number: 74,
name: "brightness",
description: "Brightness",
position: "lsb"
}, {
number: 75,
name: "decaytime",
description: "Decay Time",
position: "lsb"
}, {
number: 76,
name: "vibratorate",
description: "Vibrato Rate",
position: "lsb"
}, {
number: 77,
name: "vibratodepth",
description: "Vibrato Depth",
position: "lsb"
}, {
number: 78,
name: "vibratodelay",
description: "Vibrato Delay",
position: "lsb"
}, {
number: 79,
name: "controller79",
description: "Undefined",
position: "lsb"
}, {
number: 80,
name: "generalpurposecontroller5",
description: "General Purpose Controller 5",
position: "lsb"
}, {
number: 81,
name: "generalpurposecontroller6",
description: "General Purpose Controller 6",
position: "lsb"
}, {
number: 82,
name: "generalpurposecontroller7",
description: "General Purpose Controller 7",
position: "lsb"
}, {
number: 83,
name: "generalpurposecontroller8",
description: "General Purpose Controller 8",
position: "lsb"
}, {
number: 84,
name: "portamentocontrol",
description: "Portamento Control",
position: "lsb"
}, {
number: 85,
name: "controller85",
description: "Undefined"
}, {
number: 86,
name: "controller86",
description: "Undefined"
}, {
number: 87,
name: "controller87",
description: "Undefined"
}, {
number: 88,
name: "highresolutionvelocityprefix",
description: "High Resolution Velocity Prefix",
position: "lsb"
}, {
number: 89,
name: "controller89",
description: "Undefined"
}, {
number: 90,
name: "controller90",
description: "Undefined"
}, {
number: 91,
name: "effect1depth",
description: "Effects 1 Depth (Reverb Send Level)"
}, {
number: 92,
name: "effect2depth",
description: "Effects 2 Depth"
}, {
number: 93,
name: "effect3depth",
description: "Effects 3 Depth (Chorus Send Level)"
}, {
number: 94,
name: "effect4depth",
description: "Effects 4 Depth"
}, {
number: 95,
name: "effect5depth",
description: "Effects 5 Depth"
}, {
number: 96,
name: "dataincrement",
description: "Data Increment"
}, {
number: 97,
name: "datadecrement",
description: "Data Decrement"
}, {
number: 98,
name: "nonregisteredparameterfine",
description: "Non-Registered Parameter Number (Fine)",
position: "lsb"
}, {
number: 99,
name: "nonregisteredparametercoarse",
description: "Non-Registered Parameter Number (Coarse)",
position: "msb"
}, {
number: 100,
name: "registeredparameterfine",
description: "Registered Parameter