Skip to content

Reloop Mixtour: initial implementation - #15790

Open
absorb-it wants to merge 9 commits into
mixxxdj:2.6from
absorb-it:reloop_mixtour
Open

Reloop Mixtour: initial implementation#15790
absorb-it wants to merge 9 commits into
mixxxdj:2.6from
absorb-it:reloop_mixtour

Conversation

@absorb-it

@absorb-it absorb-it commented Dec 26, 2025

Copy link
Copy Markdown
Contributor

This is a implementation for the midi-controller Reloop Mixtour. It is a two deck controller and you configure the Decks for which to use this controller, if you have two of them you can control 4 decks with it.

Related documentation is at mixxxdj/manual#825

rene added 2 commits December 27, 2025 11:41
added feature for Back button to toggle display of maximized Library

@acolombier acolombier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Small nit regarding the use of the midi-components's connections list. For context, this array stores active connection, so upon disconnect, you should remove it, instead of undefining it

Looks good otherwise, though haven't tested it!

Comment thread res/controllers/Reloop-Mixtour-scripts.js
Comment thread res/controllers/Reloop-Mixtour-scripts.js
Comment thread res/controllers/Reloop-Mixtour-scripts.js
Comment thread res/controllers/Reloop-Mixtour-scripts.js Outdated
@andrasore

andrasore commented Jan 27, 2026

Copy link
Copy Markdown

Hi there! I've tried to use the mapping in a 4 deck setup. It seems like selecting deck 3 and 4 in the controller setup menu does not work as expected.
The EQ knobs are always getting mapped to deck 1 and 2, regardless of the chosen settings.

Edit: found the culprit. In the implementation of the Mixer class, control group names should be created using theMixer.currentDeck instead of midiChannel + 1

Comment thread res/controllers/Reloop-Mixtour-scripts.js Outdated
Comment thread res/controllers/Reloop-Mixtour-scripts.js Outdated
Comment thread res/controllers/Reloop-Mixtour-scripts.js Outdated
Comment thread res/controllers/Reloop-Mixtour-scripts.js Outdated
Comment thread res/controllers/Reloop-Mixtour-scripts.js Outdated
Comment thread res/controllers/Reloop-Mixtour-scripts.js Outdated
@absorb-it

Copy link
Copy Markdown
Contributor Author

Hi there! I've tried to use the mapping in a 4 deck setup. It seems like selecting deck 3 and 4 in the controller setup menu does not work as expected. The EQ knobs are always getting mapped to deck 1 and 2, regardless of the chosen settings.

Edit: found the culprit. In the implementation of the Mixer class, control group names should be created using theMixer.currentDeck instead of midiChannel + 1

Hi, thanks for the report and the suggested fix. I'm traveling now and will have access to a development system in two weeks again, but than I will miss Reloop Mixtour hardware access for a while. I will check your suggestions and come back. Again, thanks a lot for your suggested patches, regards, Rene

absorb-it and others added 2 commits February 8, 2026 17:27
group indications should reflect the currentDeck, not the midi channel. Thanks for @andrasore for the  fix.

Co-authored-by: András Sőre <andras08@gmail.com>
group indications should reflect the currentDeck, not the midi channel.
Thanks for @andrasore for the pointing to the issue.

@andrasore andrasore left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hi @absorb-it ! Sorry for the late reply. I've done a quick testing of the mapping fixes. I've noticed two things at a glance:

  • When pressing the right "c" button on the controller (for putting the right side control buttons into cue mode), the second button (labeled "sync") does not create a cue. An error dialog is shown
  • I think the FX buttons' main functionality should be switching between filter mode and selected quick FX for the channel. This is consistent with the buttons' labels, and is encouraged as per the contributing guidelines. Maybe shift + fx could select the next FX in the quick effects selector list.

fixed boken button, thanks to andrasore for finding he bug!
@absorb-it

Copy link
Copy Markdown
Contributor Author

Hi @andrasore ,

thanks for testing the implementation and filing the bugs...

When pressing the right "c" button on the controller (for putting the right side control buttons into cue mode), the second button (labeled "sync") does not create a cue. An error dialog is shown

found this issue and fixed it with def62f7

I think the FX buttons' main functionality should be switching between filter mode and selected quick FX for the channel. This is consistent with the buttons' labels, and is encouraged as per the contributing guidelines. Maybe shift + fx could select the next FX in the quick effects selector list.

I get your point, but there is no chance for me to change this now without the real hardware in front of me. Therefore this has to wait until May / June.

Thanks and regards, Rene

@andrasore

andrasore commented Mar 11, 2026

Copy link
Copy Markdown

Hey @absorb-it !

I had some spare time on my hands and managed to change the FX buttons functionality to switch between Filter / Quick FX. Also Shift + FX button should now select the next quick effect in the list.

I couldnt push to your repo though so I attached the files here.
Reloop Mixtour.midi.xml
Reloop-Mixtour-scripts.js

Edit: I forgot I could have just started a PR there 😅

@absorb-it

absorb-it commented Mar 13, 2026

Copy link
Copy Markdown
Contributor Author

Hi @andrasore ,

thanks for providing the patch, just had a look into it. The midi.xml is ok, no issues. The *js seemed a little bit to complicated, there are some predefined functions in midi-components-0.0.js which really help a lot simplifying the code.

But while trying to clean it a little bit I realized some bigger issue with it. Looks like you assumed that the 'Filter' is always at id '11', but this depends on the configuration in your preferences. It's just another FX and can be put at every position, as well as the name can be changed or some Echo can be added. I assumed this should be at position '1', which makes everything easier and that's how it was on my software setup. At the end, this should be mentioned in the documentation.

Beside, you missed implementing the option to enable and disable the FX directly in the software interface, this should be mapped back to the state of the Indicator (I missed this as well for the small blue LED, should be fixed now).

I changed a lot, but your help is really appreciated - I would not have been able to dig into this without some example code showing me some direction. I have no chance to test the code as of now, therefore also as some attachment, it would be great if you can check if this works at all.

All the best, Rene

(edit, added fixed file)
Reloop-Mixtour-scripts.js

@acolombier acolombier added this to the 2.6.0 milestone Mar 13, 2026
@acolombier
acolombier changed the base branch from 2.5 to 2.6 March 13, 2026 16:32
@andrasore

Copy link
Copy Markdown

I've tested the changes in the included js file. Everything is working correctly, and the code looks nicer too! LGTM

Filter/FX button now changes between the Quickfilter number 1 (best set
this to a plain Filter) and other FX - as labeled on the device. Thanks
to @andrasore for code and testing.
@absorb-it

Copy link
Copy Markdown
Contributor Author

Hi @andrasore ,

I've tested the changes in the included js file. Everything is working correctly, and the code looks nicer too! LGTM

committed with e08506d.

I like to mention your work in the midi header author line, if this is ok for you. (as of now: Rene (mixxx at absorb.it), zfhrp). Can I add your name and email?

Regards, Rene

@andrasore

andrasore commented Mar 14, 2026 via email

Copy link
Copy Markdown

@absorb-it

Copy link
Copy Markdown
Contributor Author

Okay

added with 027c777 , feel free to change this the way you like it with a PR. All the best,

Rene

@JoergAtGithub
JoergAtGithub requested a review from Copilot April 18, 2026 19:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds an initial Mixxx controller mapping for the Reloop Mixtour, including a JavaScript controller script and an accompanying MIDI XML preset with configurable deck assignment (2-deck usage with optional 4-deck following/highlight behavior).

Changes:

  • Introduces ReloopMixtour controller script implementing deck + mixer controls, shift layers, library navigation, and LEDs/meters.
  • Adds a .midi.xml preset defining the device, script loading, and control bindings plus user settings for left/right deck assignment.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
res/controllers/Reloop-Mixtour-scripts.js Implements Mixtour behavior using midi-components, including deck/mixer abstractions and LED/meter output.
res/controllers/Reloop Mixtour.midi.xml Defines the Mixxx controller preset, loads the script, and maps MIDI messages + exposes deck-selection settings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


this.masterMeter = new components.Component({
midi: [0x90 + midiChannel, 0x12],
key: (this.currentDeck === "[Channel1]")?"vu_meter_left":"vu_meter_right",

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

masterMeter chooses between vu_meter_left/vu_meter_right based on this.currentDeck. When the mixer follows deck selection (e.g. switching between Channel1 and Channel3), this will flip which master meter is shown and can result in both sides showing the same channel. Tie the selection to the physical side (e.g. midiChannel === 0 => left, midiChannel === 1 => right) rather than the selected deck.

Suggested change
key: (this.currentDeck === "[Channel1]")?"vu_meter_left":"vu_meter_right",
key: (midiChannel === 0) ? "vu_meter_left" : "vu_meter_right",

Copilot uses AI. Check for mistakes.
Comment on lines +329 to +330
outValueScale(value) {
return (engine.getValue(this.group, this.secondOutKey) && (Math.abs(0.5 - value) > 0.01))?0x7F:0x00;

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

fxIndicator connects to both super1 and enabled, but outValueScale(value) treats its argument as the super1 value. When the enabled connection triggers, value will be 0/1 (enabled state), causing the center-detent check to be wrong and the LED to light even if super1 is centered. Make outValueScale independent of the callback argument by reading the current super1 value from Mixxx (or override output() to fetch both super1 and enabled).

Suggested change
outValueScale(value) {
return (engine.getValue(this.group, this.secondOutKey) && (Math.abs(0.5 - value) > 0.01))?0x7F:0x00;
outValueScale() {
var super1Value = engine.getValue(this.group, this.outKey);
var enabledValue = engine.getValue(this.group, this.secondOutKey);
return (enabledValue && (Math.abs(0.5 - super1Value) > 0.01)) ? 0x7F : 0x00;

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +32
// override Component prototype to prevent double connections
// original connect function will override connections[0] and
// this way looses control of existing connection
components.Component.prototype.connect = function() {
if (this.connections[0] === undefined && // <-- added this condition
undefined !== this.group &&
undefined !== this.outKey &&
undefined !== this.output &&
typeof this.output === "function") {
this.connections[0] = engine.makeConnection(this.group, this.outKey, this.output.bind(this));
}
};
// override Component prototype to reset connection after disconnect
// original disconnect will never reset connections[0] to undefined
components.Component.prototype.disconnect = function() {
if (this.connections[0] !== undefined) {
this.connections.forEach(function(conn) {
conn.disconnect();
});
}
this.connections = []; // <-- added this
};

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

This script globally overrides components.Component.prototype.connect/disconnect. Because controller scripts share the same JS runtime, this monkey-patch can affect other mappings that use midi-components-0.0.js when multiple controllers are enabled, and it makes behavior depend on load order. Consider avoiding prototype overrides (e.g. fix usage sites by ensuring disconnect() clears connections before reconnecting, or wrap/patch in a guarded, idempotent way and restore originals on shutdown).

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +71
this.connectFourDeckControl = function() {
for (let i = 1; i <= 4; i++) {
engine.makeConnection("[Skin]", `highlight_deck_[Channel${i}]`, function(_value, _group, _control) {
ReloopMixtour.reSelectDecks();
});
engine.makeConnection("[Skin]", `highlight_mixer_[Channel${i}]`, function(_value, _group, _control) {
ReloopMixtour.reSelectMixer();
});
}
};

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

connectFourDeckControl() creates 8 engine.makeConnection(...) objects but does not keep references to them, so they cannot be disconnected in shutdown(). If the mapping is reloaded or the controller is unplugged/replugged, this can leave stale connections and cause callbacks to fire multiple times. Store the returned connection objects (e.g. on ReloopMixtour) and disconnect them in shutdown().

Copilot uses AI. Check for mistakes.
Comment on lines +421 to +430
<group>[Library]</group>
<key>ReloopMixtour.backBtn.input</key>
<description>Shift [BACK]</description>
<status>0x90</status>
<midino>0x47</midino>
<options>
<normal/>
</options>
</control>
<control>

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

This <control> duplicates the Shift [BACK] mapping for the same MIDI message (status 0x90, midino 0x47) but uses <normal/> while the <key> points to a script function (ReloopMixtour.backBtn.input). This will either be ignored or conflict with the earlier script-binding definition and can make Shift+BACK unreliable. Remove the duplicate entry or change it to a single <script-binding/> mapping.

Suggested change
<group>[Library]</group>
<key>ReloopMixtour.backBtn.input</key>
<description>Shift [BACK]</description>
<status>0x90</status>
<midino>0x47</midino>
<options>
<normal/>
</options>
</control>
<control>

Copilot uses AI. Check for mistakes.
Comment on lines +238 to +241
group: `[QuickEffectRack1_${theMixer.currentDeck}]`,
// set preset to first real fx. if available at all this is number 2
fxPreset: (engine.getValue(this.group, "num_chain_presets") > 2)?2:0,
nextPreset: function() {

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

fxPreset is initialized using engine.getValue(this.group, ...), but at this point this refers to the surrounding ReloopMixtour.Mixer instance (which has no group), not the components.Button being constructed. This will read from an undefined group (or throw) and can break FX selection. Compute the QuickEffectRack group in a local variable (or use theMixer.currentDeck) and use that in the engine.getValue(...) call instead of this.group during initialization.

Copilot uses AI. Check for mistakes.
}

setCurrentDeck(newGroup) {
this.effect.reconnect(newGroup);

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

setCurrentDeck() calls this.effect.reconnect(newGroup), but components.Button/components.Component does not define a reconnect method (only ComponentContainer.reconnectComponents). This will throw at runtime when changing decks. Remove this call or implement reconnection by calling disconnect()/updating group/connect() (or rely on super.setCurrentDeck() to handle group updates).

Suggested change
this.effect.reconnect(newGroup);

Copilot uses AI. Check for mistakes.
@absorb-it

Copy link
Copy Markdown
Contributor Author

Hi, I won't fix any hallucinated issues brought in by KI. Nor will I improve the quality of this KI with my comments on it's suggestions. If anybody else likes to take the time and go to this, feel free.

Rene

@ronso0 ronso0 modified the milestones: 2.6.0, 2.6.1 May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants