Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 112 additions & 17 deletions res/controllers/midi-components-0.0.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,50 @@
*/

(function(global) {
/**
* @typedef {[number, number] | [[number, number], [number, number]]} MidiBytesVariations
*
* @typedef {object} ComponentOptions
* @property {MidiBytesVariations} [midi] MIDI events
* @property {MidiBytesVariations} [midiIn] MIDI events
* @property {MidiBytesVariations} [midiOut] MIDI events
*/

const NO_TIMER = 0;
const Component = function(options) {
if (Array.isArray(options) && typeof options[0] === "number") {
this.midi = options;
} else {
Object.assign(this, options);

/**
* @param {ComponentOptions | [number, number]} options Component configuration
* @class
*/
const Component = function(options = {}) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented here the default value = {} as suggested here. #15030 (comment)
But after testing the last weeks I strongly recommend to replace that solution for the early return solution.
With options = {} I'm able to let mixxx crash pretty easy.

if (typeof options === "undefined") {
  return;
}

Does anyone know is there should be a usecase for providing the undefined options argument with an empty object {} or isn't it and is it valid to abort execution of that function by using the early return.

The easiest way for me to trigger the errors is by switching functionality with connect and disconnect as discussed here: https://mixxx.zulipchat.com/#narrow/channel/113295-controller-mapping/topic/How.20to.20use.20disconnect.28.29.20to.20toggle.20layers.3F/with/543714830
Mixxx then either crashes or trigger other controls then configured.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to say that I only have these issues with mixxx 2.6-beta. I don't have them with 2.5. So the issues are not entirely due to the mapping or the components js file. But it seems that they are triggered less with the early return.

this.normalizeMidi = (midiBytesVar) => {
if (!Array.isArray(midiBytesVar)) {
return [];
}

const isArrayOfArrays = midiBytesVar.every(Array.isArray);

if (isArrayOfArrays) {
return midiBytesVar;
} else {
return [midiBytesVar];
}
};

if (Array.isArray(options)) {
const [midiStatus, midiNo] = options;
options = {midiOut: [midiStatus, midiNo]};
} else if (Array.isArray(options.midi)) {
console.warn("Passing `midi` configuration is deprecated in favor `midiOut`");
options.midiOut = options.midi;
delete options.midi;
}

options.midiIn = this.normalizeMidi(options.midiIn);
options.midiOut = this.normalizeMidi(options.midiOut);

Object.assign(this, options);

if (typeof this.unshift === "function") {
this.unshift();
}
Expand All @@ -47,6 +83,9 @@
this.outKey = options.key;
}

/** @type {MidiInputHandlerController[]} */
this.inputHandlers = [];

if (this.outConnect && this.group !== undefined && this.outKey !== undefined) {
this.connect();
if (this.outTrigger) {
Expand Down Expand Up @@ -78,6 +117,23 @@

max: 127, // for MIDI. When adapting for HID this may change.

/**
* @deprecated
* @returns {[number, number]}
*/
get midi() {
console.warn("`midi` property is deprecated, please use `midiOut` instead");
return this.midiOut.length > 0 ? this.midiOut[0] : undefined;
},
/**
* @deprecated
* @param {MidiBytesVariations} value New midi value
*/
set midi(value) {
console.warn("`midi` property is deprecated, please use `midiOut` instead");
this.midiOut = this.normalizeMidi(value);
},

// common functions
// In most cases, you should not overwrite these.
inGetParameter: function() {
Expand Down Expand Up @@ -112,13 +168,13 @@
this.outSetValue(!this.outGetValue());
},

connect: function() {
connectMidiOut() {
/**
Override this method with a custom one to connect multiple Mixxx COs for a single Component.
Add the connection objects to the this.connections array so they all get disconnected just
by calling this.disconnect(). This can be helpful for multicolor LEDs that show a
different color depending on the state of different Mixxx COs. Refer to
SamplerButton.connect() and SamplerButton.output() for an example.
Override this method with a custom one to connect multiple Mixxx COs for a single Component.
Add the connection objects to the this.connections array so they all get disconnected just
by calling this.disconnect(). This can be helpful for multicolor LEDs that show a
different color depending on the state of different Mixxx COs. Refer to
SamplerButton.connect() and SamplerButton.output() for an example.
*/
if (undefined !== this.group &&
undefined !== this.outKey &&
Expand All @@ -127,12 +183,24 @@
this.connections[0] = engine.makeConnection(this.group, this.outKey, this.output.bind(this));
}
},
connectMidiIn() {
for (const [midiStatus, midino] of this.midiIn) {
this.inputHandlers.push(midi.makeInputHandler(midiStatus, midino, this.input.bind(this)));
}
},
connect() {
this.connectMidiIn();
this.connectMidiOut();
},
disconnect: function() {
if (this.connections[0] !== undefined) {
this.connections.forEach(function(conn) {
conn.disconnect();
});
}
for (const inputHandler of this.inputHandlers) {
inputHandler.disconnect();
}
},
trigger: function() {
if (this.connections[0] !== undefined) {
Expand All @@ -146,15 +214,19 @@
shiftChannel: false,
shiftControl: false,
send: function(value) {
if (this.midi === undefined || this.midi[0] === undefined || this.midi[1] === undefined) {
if (this.midiOut.length <= 0) {
return;
}
midi.sendShortMsg(this.midi[0], this.midi[1], value);
for (const [midiStatus, midino] of this.midiOut) {
midi.sendShortMsg(midiStatus, midino, value);
}

if (this.sendShifted) {
console.assert(this.midiOut.length === 1, "use nested `midiOut: [[...], [...], ...]` instead of `sendShifted`!");
if (this.shiftChannel) {
midi.sendShortMsg(this.midi[0] + this.shiftOffset, this.midi[1], value);
midi.sendShortMsg(this.midiOut[0][0] + this.shiftOffset, this.midiOut[0][1], value);
} else if (this.shiftControl) {
midi.sendShortMsg(this.midi[0], this.midi[1] + this.shiftOffset, value);
midi.sendShortMsg(this.midiOut[0][0], this.midiOut[0][1] + this.shiftOffset, value);
}
}
},
Expand Down Expand Up @@ -499,7 +571,19 @@
this._firstLSB = value;
}
},
connect: function() {
connectMidiIn() {
if (this.midiIn.length >= 1) {
const [midiStatus, midino] = this.midiIn[0];
this.inputHandlers.push(midi.makeInputHandler(midiStatus, midino, this.inputMSB.bind(this)));
}
if (this.midiIn.length === 2) {
const [midiStatus, midino] = this.midiIn[1];
this.inputHandlers.push(midi.makeInputHandler(midiStatus, midino, this.inputLSB.bind(this)));
}
},
connect() {
this.connectMidiIn();
this.connectMidiOut();
if (this.firstValueReceived && !this.relative && this.softTakeover) {
engine.softTakeover(this.group, this.inKey, true);
}
Expand Down Expand Up @@ -844,6 +928,17 @@
"Please bind jogwheel-related messages to inputWheel and inputTouch!\n";
},
reset() {},

connectMidiIn() {
if (this.midiIn.length >= 1) {
const [midiStatus, midino] = this.midiIn[0];
this.inputHandlers.push(midi.makeInputHandler(midiStatus, midino, this.inputTouch.bind(this)));
}
if (this.midiIn.length === 2) {
const [midiStatus, midino] = this.midiIn[1];
this.inputHandlers.push(midi.makeInputHandler(midiStatus, midino, this.inputWheel.bind(this)));
}
},
});

const EffectUnit = function(unitNumbers, allowFocusWhenParametersHidden, colors) {
Expand Down Expand Up @@ -1266,7 +1361,7 @@
this.effectFocusButton.trigger();

this.enableOnChannelButtons.forEachComponent(function(button) {
if (button.midi !== undefined) {
if (button.midiOut.length > 0) {
button.disconnect();
button.connect();
button.trigger();
Expand Down