diff --git a/res/controllers/Behringer-DDM4000-scripts.js b/res/controllers/Behringer-DDM4000-scripts.js index e624e9b8fc5..b8c89b2ab97 100644 --- a/res/controllers/Behringer-DDM4000-scripts.js +++ b/res/controllers/Behringer-DDM4000-scripts.js @@ -10,6 +10,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ var DEFAULT_LONGPRESS_DURATION = 500; var DEFAULT_BLINK_DURATION = 425; + var THROTTLE_DELAY = 40; /* Shortcut variables */ var c = components; @@ -201,6 +202,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ var ModeButton = function(options) { options = options || {}; options.key = options.key || "mode"; + options.longPressTimeout = options.longPressTimeout || DEFAULT_LONGPRESS_DURATION; e.LongPressButton.call(this, options); }; ModeButton.prototype = e.deriveFrom(e.LongPressButton, { @@ -219,12 +221,12 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi.sendShortMsg(cc, this.midi[1] + 1, this.outValueScale(value)); }, }); - this.modeButton = new ModeButton( - {midi: bankOptions.mode, group: bankOptions.group, longPressTimeout: DEFAULT_LONGPRESS_DURATION}); + this.modeButton = new ModeButton({midi: bankOptions.mode, group: bankOptions.group}); }; SamplerBank.prototype = e.deriveFrom(c.ComponentContainer); return { + throttleDelay: THROTTLE_DELAY, init: function() { /* @@ -251,14 +253,16 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: [note, 0x3F], key: "pfl", type: toggle, sendShifted: true } }, - {type: c.Button, options: {midi: [note, 0x03], inKey: ""}}, // Mode - {type: c.Button, options: {midi: [cc, 0x38], outKey: ""}}, // Mode: Multi - {type: c.Button, options: {midi: [cc, 0x37], outKey: ""}}, // Mode: Single + {type: c.Button, options: {midi: [note, 0x03], inKey: null}}, // Mode + {type: c.Button, options: {midi: [cc, 0x38], outKey: null}}, // Mode: Multi + {type: c.Button, options: {midi: [cc, 0x37], outKey: null}}, // Mode: Single ], equalizerUnit: { // P3 / Low, P2 / Mid, P1 / High midi: { // eslint-disable-next-line key-spacing parameterKnobs: {1: [cc, 0x06], 2: [cc, 0x05], 3: [cc, 0x04]}, parameterButtons: {1: [note, 0x02], 2: [note, 0x01], 3: [note, 0x00]}, + enabled: null, + super1: null, }, output: { parameterButtons: {1: [cc, 0x3D], 2: [cc, 0x3B], 3: [cc, 0x39]}, // Amber @@ -281,14 +285,16 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: [note, 0x49], key: "pfl", type: toggle, sendShifted: true } }, - {type: c.Button, options: {midi: [note, 0x07], inKey: ""}}, // Mode - {type: c.Button, options: {midi: [cc, 0x42], outKey: ""}}, // Mode: Multi - {type: c.Button, options: {midi: [cc, 0x41], outKey: ""}}, // Mode: Single + {type: c.Button, options: {midi: [note, 0x07], inKey: null}}, // Mode + {type: c.Button, options: {midi: [cc, 0x42], outKey: null}}, // Mode: Multi + {type: c.Button, options: {midi: [cc, 0x41], outKey: null}}, // Mode: Single ], equalizerUnit: { // P3 / Low, P2 / Mid, P1 / High midi: { // eslint-disable-next-line key-spacing parameterKnobs: {1: [cc, 0x0A], 2: [cc, 0x09], 3: [cc, 0x08]}, parameterButtons: {1: [note, 0x06], 2: [note, 0x05], 3: [note, 0x04]}, + enabled: null, + super1: null, }, output: { parameterButtons: {1: [cc, 0x47], 2: [cc, 0x45], 3: [cc, 0x43]}, // Amber @@ -311,14 +317,16 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: [note, 0x53], key: "pfl", type: toggle, sendShifted: true } }, - {type: c.Button, options: {midi: [note, 0x0B], inKey: ""}}, // Mode - {type: c.Button, options: {midi: [cc, 0x4C], outKey: ""}}, // Mode: Multi - {type: c.Button, options: {midi: [cc, 0x4B], outKey: ""}}, // Mode: Single + {type: c.Button, options: {midi: [note, 0x0B], inKey: null}}, // Mode + {type: c.Button, options: {midi: [cc, 0x4C], outKey: null}}, // Mode: Multi + {type: c.Button, options: {midi: [cc, 0x4B], outKey: null}}, // Mode: Single ], equalizerUnit: { // P3 / Low, P2 / Mid, P1 / High midi: { // eslint-disable-next-line key-spacing parameterKnobs: {1: [cc, 0x0E], 2: [cc, 0x0D], 3: [cc, 0x0C]}, parameterButtons: {1: [note, 0x0A], 2: [note, 0x09], 3: [note, 0x08]}, + enabled: null, + super1: null, }, output: { parameterButtons: {1: [cc, 0x51], 2: [cc, 0x4F], 3: [cc, 0x4D]}, // Amber @@ -341,14 +349,16 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: [note, 0x5D], key: "pfl", type: toggle, sendShifted: true } }, - {type: c.Button, options: {midi: [note, 0x0F], inKey: ""}}, // Mode - {type: c.Button, options: {midi: [cc, 0x56], outKey: ""}}, // Mode: Multi - {type: c.Button, options: {midi: [cc, 0x55], outKey: ""}}, // Mode: Single + {type: c.Button, options: {midi: [note, 0x0F], inKey: null}}, // Mode + {type: c.Button, options: {midi: [cc, 0x56], outKey: null}}, // Mode: Multi + {type: c.Button, options: {midi: [cc, 0x55], outKey: null}}, // Mode: Single ], equalizerUnit: { // P3 / Low, P2 / Mid, P1 / High midi: { // eslint-disable-next-line key-spacing parameterKnobs: {1: [cc, 0x12], 2: [cc, 0x11], 3: [cc, 0x10]}, parameterButtons: {1: [note, 0x0E], 2: [note, 0x0D], 3: [note, 0x0C]}, + enabled: null, + super1: null, }, output: { parameterButtons: {1: [cc, 0x5B], 2: [cc, 0x59], 3: [cc, 0x57]}, // Amber @@ -361,14 +371,14 @@ var DDM4000 = new behringer.extension.GenericMidiController({ { // Microphone defaultDefinition: {type: c.Button, options: {group: "[Microphone]"}}, components: [ - {options: {midi: [cc, 0x02], inKey: ""}, type: c.Pot}, // Mic: EQ Low - {options: {midi: [cc, 0x01], inKey: ""}, type: c.Pot}, // Mic: EQ Mid - {options: {midi: [cc, 0x00], inKey: ""}, type: c.Pot}, // Mic: EQ High - {options: {midi: [note, 0x31], key: "", sendShifted: true}}, // Mic: Setup - {options: {midi: [note, 0x32], key: "", sendShifted: true}}, // Mic: XMC On - {options: {midi: [note, 0x33], key: "", sendShifted: true}}, // Mic: FX On + {options: {midi: [cc, 0x02], inKey: null}, type: c.Pot}, // Mic: EQ Low + {options: {midi: [cc, 0x01], inKey: null}, type: c.Pot}, // Mic: EQ Mid + {options: {midi: [cc, 0x00], inKey: null}, type: c.Pot}, // Mic: EQ High + {options: {midi: [note, 0x31], key: null, sendShifted: true}}, // Mic: Setup + {options: {midi: [note, 0x32], key: null, sendShifted: true}}, // Mic: XMC On + {options: {midi: [note, 0x33], key: null, sendShifted: true}}, // Mic: FX On {options: {midi: [note, 0x34], key: "talkover", sendShifted: true}}, // Mic: Talk On - {options: {midi: [note, 0x35], key: "", sendShifted: true}}, // Mic: On/Off + {options: {midi: [note, 0x35], key: null, sendShifted: true}}, // Mic: On/Off ] }, { // Crossfader @@ -382,44 +392,43 @@ var DDM4000 = new behringer.extension.GenericMidiController({ { // Crossfader defaultDefinition: {type: c.Button, options: {group: "[Master]"}}, components: [ - {options: {midi: [note, 0x17], key: "", sendShifted: true}}, // Crossfader: A Full Freq - {options: {midi: [note, 0x18], key: "", sendShifted: true}}, // Crossfader: A High - {options: {midi: [note, 0x19], key: "", sendShifted: true}}, // Crossfader: A Mid - {options: {midi: [note, 0x1A], key: "", sendShifted: true}}, // Crossfader: A Low - {options: {midi: [note, 0x1B], key: "", sendShifted: true}}, // Crossfader: B Full Freq - {options: {midi: [note, 0x1C], key: "", sendShifted: true}}, // Crossfader: B High - {options: {midi: [note, 0x1D], key: "", sendShifted: true}}, // Crossfader: B Mid - {options: {midi: [note, 0x1E], key: "", sendShifted: true}}, // Crossfader: B Low { // Crossfader: On - type: CrossfaderUnit, - options: { + type: CrossfaderUnit, options: { + crossfader: {midi: [cc, 0x15]}, button: {group: "[Skin]", midi: [note, 0x1F], sendShifted: true}, - crossfader: {midi: [cc, 0x15]} }, }, - {options: {midi: [note, 0x2A], key: "", sendShifted: true}}, // Crossfader: Bounce to MIDI Clock - {options: {midi: [note, 0x2B], inKey: ""}}, // Crossfader: Beat (Left) - {options: {midi: [note, 0x2C], inKey: ""}}, // Crossfader: Beat (Right) - {options: {midi: [cc, 0x2B], outKey: ""}}, // Crossfader: Beat 1 - {options: {midi: [cc, 0x2C], outKey: ""}}, // Crossfader: Beat 2 - {options: {midi: [cc, 0x2D], outKey: ""}}, // Crossfader: Beat 4 - {options: {midi: [cc, 0x2E], outKey: ""}}, // Crossfader: Beat 8 - {options: {midi: [cc, 0x2F], outKey: ""}}, // Crossfader: Beat 16 + {options: {midi: [note, 0x17], key: null, sendShifted: true}}, // Crossfader: A Full Freq + {options: {midi: [note, 0x18], key: null, sendShifted: true}}, // Crossfader: A High + {options: {midi: [note, 0x19], key: null, sendShifted: true}}, // Crossfader: A Mid + {options: {midi: [note, 0x1A], key: null, sendShifted: true}}, // Crossfader: A Low + {options: {midi: [note, 0x1B], key: null, sendShifted: true}}, // Crossfader: B Full Freq + {options: {midi: [note, 0x1C], key: null, sendShifted: true}}, // Crossfader: B High + {options: {midi: [note, 0x1D], key: null, sendShifted: true}}, // Crossfader: B Mid + {options: {midi: [note, 0x1E], key: null, sendShifted: true}}, // Crossfader: B Low + {options: {midi: [note, 0x2A], key: null, sendShifted: true}}, // Crossfader: Bounce to MIDI Clock + {options: {midi: [note, 0x2B], inKey: null}}, // Crossfader: Beat (Left) + {options: {midi: [note, 0x2C], inKey: null}}, // Crossfader: Beat (Right) + {options: {midi: [cc, 0x2B], outKey: null}}, // Crossfader: Beat 1 + {options: {midi: [cc, 0x2C], outKey: null}}, // Crossfader: Beat 2 + {options: {midi: [cc, 0x2D], outKey: null}}, // Crossfader: Beat 4 + {options: {midi: [cc, 0x2E], outKey: null}}, // Crossfader: Beat 8 + {options: {midi: [cc, 0x2F], outKey: null}}, // Crossfader: Beat 16 ] }, { // Sampler defaultDefinition: {type: c.Button, options: {group: "[Sampler1]"}}, components: [ {options: {midi: [cc, 0x03], inKey: "volume"}, type: c.Pot}, // Sampler: Volume/Mix - {options: {midi: [note, 0x5F], key: "", sendShifted: true}}, // Sampler: Insert - {options: {midi: [note, 0x60], inKey: ""}}, // Sampler: REC Source (Right) - {options: {midi: [note, 0x61], inKey: ""}}, // Sampler: REC Source (Left) - {options: {midi: [cc, 0x60], outKey: ""}}, // Sampler: REC Source 1 - {options: {midi: [cc, 0x61], outKey: ""}}, // Sampler: REC Source 2 - {options: {midi: [cc, 0x62], outKey: ""}}, // Sampler: REC Source 3 - {options: {midi: [cc, 0x63], outKey: ""}}, // Sampler: REC Source 4 - {options: {midi: [cc, 0x64], outKey: ""}}, // Sampler: REC Source Microphone - {options: {midi: [cc, 0x65], outKey: ""}}, // Sampler: REC Source Master + {options: {midi: [note, 0x5F], key: null, sendShifted: true}}, // Sampler: Insert + {options: {midi: [note, 0x60], inKey: null}}, // Sampler: REC Source (Right) + {options: {midi: [note, 0x61], inKey: null}}, // Sampler: REC Source (Left) + {options: {midi: [cc, 0x60], outKey: null}}, // Sampler: REC Source 1 + {options: {midi: [cc, 0x61], outKey: null}}, // Sampler: REC Source 2 + {options: {midi: [cc, 0x62], outKey: null}}, // Sampler: REC Source 3 + {options: {midi: [cc, 0x63], outKey: null}}, // Sampler: REC Source 4 + {options: {midi: [cc, 0x64], outKey: null}}, // Sampler: REC Source Microphone + {options: {midi: [cc, 0x65], outKey: null}}, // Sampler: REC Source Master {options: {midi: [note, 0x66], key: "pfl", sendShifted: true}}, // Sampler: PFL {options: {midi: [note, 0x67], inKey: "beatloop_size", values: [1, 2, 4, 8, 16, 256]}, type: e.EnumToggleButton}, // Sampler: Sample Length (Right) {options: {midi: [note, 0x68], inKey: "beatloop_size", values: [256, 16, 8, 4, 2, 1]}, type: e.EnumToggleButton}, // Sampler: Sample Length (Left) @@ -429,8 +438,8 @@ var DDM4000 = new behringer.extension.GenericMidiController({ {options: {midi: [cc, 0x6A], outKey: "beatloop_size", onValue: 8}, type: e.CustomButton}, // Sampler: Sample Length 8 {options: {midi: [cc, 0x6B], outKey: "beatloop_size", onValue: 16}, type: e.CustomButton}, // Sampler: Sample Length 16 {options: {midi: [cc, 0x6C], outKey: "beatloop_size", onValue: 256}, type: e.CustomButton}, // Sampler: Sample Length ∞ - {options: {midi: [note, 0x6D], key: "", sendShifted: true}}, // Sampler: Record / In - {options: {midi: [note, 0x6C], inKey: ""}}, // Sampler: Bank Assign + {options: {midi: [note, 0x6D], key: null, sendShifted: true}}, // Sampler: Record / In + {options: {midi: [note, 0x6C], inKey: null}}, // Sampler: Bank Assign { // Sampler Bank 1 type: SamplerBank, options: { @@ -465,14 +474,14 @@ var DDM4000 = new behringer.extension.GenericMidiController({ blinkDuration: DEFAULT_BLINK_DURATION, } }, - {options: {midi: [note, 0x79], key: "", sendShifted: true}}, // Sampler: Select + {options: {midi: [note, 0x79], key: null, sendShifted: true}}, // Sampler: Select { // Sampler: CF Assign type: e.EnumToggleButton, - options: {midi: [note, 0x7A], inKey: "orientation", values: [center, left, right]}, + options: {midi: [note, 0x7A], inKey: "orientation", values: [center, left, right]}, }, {type: CrossfaderAssignLED, options: {midi: [cc, 0x7A], onValue: left}}, // Sampler: CF Assign A {type: CrossfaderAssignLED, options: {midi: [cc, 0x7B], onValue: right}}, // Sampler: CF Assign B - {options: {midi: [note, 0x7C], key: "", sendShifted: true}}, // Sampler: CF Start + {options: {midi: [note, 0x7C], key: null, sendShifted: true}}, // Sampler: CF Start ] } ], diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index d2050ff18ba..dda62a9174a 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -55,14 +55,18 @@ * @private */ var stringifyComponent = function(component) { - if (component === undefined) { + if (!component) { return; } - var id = findComponentId(component.midi); - if (id !== undefined) { - var key = component.inKey || component.outKey; - return "(" + id + ": " + component.group + "," + key + ")"; + var key = component.inKey || component.outKey; + var value = component.group + "," + key; + if (component.midi) { + var id = findComponentId(component.midi); + if (id !== undefined) { + value = id + ": " + value; + } } + return "(" + value + ")"; }; /** @@ -95,6 +99,43 @@ return _.merge(Object.create(parent.prototype), members || {}); }; + /** + * Perform an action, throttled if the owner supports throttling. + * + * @param {function} action The action to perform + * @param {object} owner Object used as `this` for the action + * @private + * @see `Throttler` + */ + var throttle = function(action, owner) { + if (owner.throttler) { + owner.throttler.schedule(action, owner); + } else { + action.call(owner); + } + }; + + /** + * A component that uses the parameter instead of the value as output. + * + * @constructor + * @extends {components.Component} + * @param {object} options Options object + * @public + */ + var ParameterComponent = function(options) { + components.Component.call(this, options); + }; + ParameterComponent.prototype = deriveFrom(components.Component, { + outValueScale: function(_value) { + /* + * We ignore the argument and use the parameter (0..1) instead because value scale is + * arbitrary and thus cannot be mapped to MIDI values (0..127) properly. + */ + return convertToMidiValue.call(this, this.outGetParameter()); + }, + }); + /** * A button to toggle un-/shift on a target component. * @@ -181,7 +222,7 @@ * Use `start(action)` to start and `reset()` to reset. * * @constructor - * @param {number} options.timeout Duration between start and action + * @param {number} options.timeout Duration between start and action (in ms) * @param {boolean} options.oneShot If `true`, the action is run once; * otherwise, it is run periodically until the timer is reset. * @param {function} options.action Function that is executed whenever the timer expires @@ -221,6 +262,53 @@ } }; + /** + * An object that enforces a constant delay between the execution of consecutive actions. + * + * Use `schedule(action, owner)` to perform an action on the owner as soon as the delay has + * elapsed after the preceding action has finished. + * + * @constructor + * @param {number} options.delay Minimal delay between two consecutive actions (in ms) + * @public + */ + var Throttler = function(options) { + options = options || {}; + options.delay = options.delay || 0; + _.assign(this, options); + this.locked = false; + this.jobs = []; + this.unlockTimer = new Timer( + {timeout: this.delay, oneShot: true, action: this.unlock, owner: this}); + }; + Throttler.prototype = { + schedule: function(action, owner) { + this.jobs.push({action: action, owner: owner}); + this.notify(); + }, + + notify: function() { + if (this.jobs.length > 0 && this.acquireLock()) { + var job = this.jobs.shift(); + job.action.call(job.owner); + this.unlockTimer.start(); + } + }, + + acquireLock: function() { + var unlocked = !this.locked; + if (unlocked) { + this.locked = true; + } + return unlocked; + }, + + unlock: function() { + this.locked = false; + this.notify(); + } + }; + /** * A button that supports different actions on short and long press. * @@ -415,7 +503,9 @@ * @extends {components.Encoder} * @param {object} options Options object * @param {Array} options.values An array containing the enumeration values + * @param {boolean} options.softTakeover (optional) Enable soft-takeover; default: `true` * @public + * @see https://github.com/mixxxdj/mixxx/wiki/Midi-Scripting#soft-takeover */ var EnumEncoder = function(options) { options = options || {}; @@ -423,10 +513,22 @@ log.error("EnumEncoder constructor was called without specifying enum values."); options.values = []; } + if (options.softTakeover === undefined) { // do not use '||' to allow false + options.softTakeover = true; + } options.maxIndex = options.values.length - 1; components.Encoder.call(this, options); }; EnumEncoder.prototype = deriveFrom(components.Encoder, { + input: function(_channel, _control, value, _status, _group) { + var scaledValue = this.inValueScale(value); + if (!this.softTakeover + || this.previousValue === undefined + || this.previousValue === this.inGetValue()) { + this.inSetParameter(scaledValue); + } + this.previousValue = scaledValue; + }, inValueScale: function(value) { var normalizedValue = value / this.max; var index = Math.round(normalizedValue * this.maxIndex); @@ -470,7 +572,7 @@ * `sizeControl` being preferred. * * @constructor - * @extends {components.Encoder} + * @extends {DirectionEncoder} * @param {object} options Options object * @param {number} options.size (optional) Size given in number of beats; default: 0.5 * @param {string} options.sizeControl (optional) Name of a control that contains `size` @@ -580,16 +682,9 @@ } this.source = options.source; this.sync(); - components.Component.call(this, options); + ParameterComponent.call(this, options); }; - Publisher.prototype = deriveFrom(components.Component, { - outValueScale: function(_value) { - /* - * We ignore the argument and use the parameter (0..1) instead because value scale is - * arbitrary and thus cannot be mapped to MIDI values (0..127) properly. - */ - return convertToMidiValue.call(this, this.outGetParameter()); - }, + Publisher.prototype = deriveFrom(ParameterComponent, { sync: function() { this.midi = this.source.midi; this.group = this.source.group; @@ -759,6 +854,11 @@ log.error("Missing component"); return; } + if (!component.midi) { + log.debug(containerName + ": ignore " + + stringifyComponent(component) + " without MIDI address"); + return; + } var id = findComponentId(component.midi); if (!Object.prototype.hasOwnProperty.call(this.containers, containerName)) { this.createContainer(containerName); @@ -1020,6 +1120,11 @@ * +- init: (optional) A function that is called when Mixxx is started * +- shutdown: (optional) A function that is called when Mixxx is shutting down * | + * +- throttleDelay (optional): A positive number (in ms) that is used to slow down the + * | initialization of the controller; this option is useful if + * | the hardware is limited to process a certain number of MIDI + * | messages per time. + * | * +- decks: An array of deck definitions (may be empty or omitted) * | +- deck: * | +- deckNumbers: As defined by {components.Deck} @@ -1045,7 +1150,7 @@ * | | component. * | +- output: Additional output definitions (optional). * | The structure of this object is the same as the structure of - * | `components`. Every value change of a component contained in `output` + * | `midi`. Every value change of a component contained in `output` * | causes a MIDI message to be sent to the hardware controller, using * | the configured address instead of the component's `midi` property. * | This option is independent of the `feedback` option. @@ -1104,14 +1209,20 @@ this.controllerId = controllerId; this.debug = debug; + var delay = this.config.throttleDelay; + if (delay > 0) { + log.debug("Component registration is throttled using a delay of " + delay + "ms"); + this.throttler = new Throttler({delay: delay}); + } + if (typeof this.config.init === "function") { this.config.init(controllerId, debug); } /* - * Contains all decks and effect units so that a (un)shift operation - * is delegated to the decks, effect units and their children. - */ + * Contains all decks and effect units so that a (un)shift operation + * is delegated to the decks, effect units and their children. + */ this.componentContainers = []; this.layerManager = this.createLayerManager( @@ -1201,9 +1312,11 @@ }].forEach(function(context) { if (Array.isArray(context.definitions)) { context.definitions.forEach(function(definition) { - var implementation = context.factory.call(this, definition, target); - target.push(implementation); - context.register(definition, implementation); + throttle(function() { + var implementation = context.factory.call(this, definition, target); + target.push(implementation); + context.register(definition, implementation); + }, this); }, this); } else { log.error(this.controllerId + ": Skipping a part of the configuration because " @@ -1226,14 +1339,9 @@ createDeck: function(deckDefinition, componentStorage) { var deck = new components.Deck(deckDefinition.deckNumbers); deckDefinition.components.forEach(function(componentDefinition, index) { - if (componentDefinition && componentDefinition.type) { - var options = _.merge({group: deck.currentDeck}, componentDefinition.options); - deck[index] = new componentDefinition.type(options); - } else { - log.error("Skipping component without type on Deck of " + deck.currentDeck - + ": " + stringifyObject(componentDefinition)); - deck[index] = null; - } + var options = _.merge({group: deck.currentDeck}, componentDefinition.options); + var definition = _.merge(componentDefinition, {options: options}); + deck[index] = this.createComponent(definition); }, this); if (deckDefinition.equalizerUnit) { deck.equalizerUnit = this.setupMidi( @@ -1254,7 +1362,7 @@ * @private */ processMidiAddresses: function(definition, implementation, action) { - if (Array.isArray(definition)) { + if (Array.isArray(definition) || !definition) { action.call(this, definition, implementation); } else if (typeof definition === "object") { Object.keys(definition).forEach(function(name) { @@ -1432,6 +1540,7 @@ var exports = {}; exports.deriveFrom = deriveFrom; + exports.ParameterComponent = ParameterComponent; exports.ShiftButton = ShiftButton; exports.Trigger = Trigger; exports.CustomButton = CustomButton;