Skip to content

Traktor S4MK3: Add alternative modes for a few controls.#15664

Open
ywwg wants to merge 1 commit intomixxxdj:mainfrom
ywwg:traktor-s4mk3-alt-modes
Open

Traktor S4MK3: Add alternative modes for a few controls.#15664
ywwg wants to merge 1 commit intomixxxdj:mainfrom
ywwg:traktor-s4mk3-alt-modes

Conversation

@ywwg
Copy link
Member

@ywwg ywwg commented Nov 23, 2025

Relative Tempo Fader mode: Tempo fader only responds to changes, not absolute position. Use shift to change position without adjusting rate. (great for sync lock mode)

Relative Tempo Fader, Shifted mode: Only respond to changes when shift is held. Prevents accidental bumps when DJing in poor tactile environments (wearing fursuit paws)

Jog Wheel Adjustment factor: Adjust sensitivity of jog wheel when making sync adjustments. (current default is very sensitive)

Alternative Move mode: for the move encoder, change assignments: unshifted to change beatjump size, shift to do a beatjump, push and change to adjust pitch. (another case where I don't want to accidentally bump the control and jump beats)

@ronso0
Copy link
Member

ronso0 commented Nov 25, 2025

Thank you for this!

The relative tempo mode is indeed helpful, for me when I want to match another track's BPM which is > +- 8% but I don't want to change the rate range.

Though there are a few issue with the current implementation:

  • I didn't find a way to get back to regular/absolute mode, mainly to reset the rate
  • it seems the LEDs are completely bypassed in this mode
  • relative mode allows to set rates < -100 %
    (actually this means to go backwards, but this is clamped by the engine, just the elapsed/remaining display goes wild)

How can we implement a 'relative mode' toggle?
Currently the Sync and Master button are already crammed with lots of functions, so not even Shift + Sync longpress + Master longpress is available.
How about binding this to Shift+JOG?
Proper feedback is another story, maybe permanently light the center LED with the 'centered' color? (deck color I think)

@ywwg
Copy link
Member Author

ywwg commented Nov 25, 2025

Though there are a few issue with the current implementation:

  • I didn't find a way to get back to regular/absolute mode, mainly to reset the rate

(see below)

  • it seems the LEDs are completely bypassed in this mode

what would you want them to do here?

  • relative mode allows to set rates < -100 %

I can clamp that for sure. And an upper bound just for safety seems useful too.

(actually this means to go backwards, but this is clamped by the engine, just the elapsed/remaining display goes wild)

How can we implement a 'relative mode' toggle? Currently the Sync and Master button are already crammed with lots of functions, so not even Shift + Sync longpress + Master longpress is available. How about binding this to Shift+JOG? Proper feedback is another story, maybe permanently light the center LED with the 'centered' color? (deck color I think)

I'll think about this -- currently I think it's ok to keep it as a controller setting and not something accessible from the controller hardware.

@ywwg
Copy link
Member Author

ywwg commented Nov 25, 2025

I guess shift+jog can work? But I think "shift+master long press" is available: https://github.com/mixxxdj/mixxx/blob/main/res/controllers/Traktor-Kontrol-S4-MK3.js#L1989-L1998

@ronso0
Copy link
Member

ronso0 commented Nov 26, 2025

  • it seems the LEDs are completely bypassed in this mode

what would you want them to do here?

Actually I was missing the center indictaor, but that's d be kind of useless anyway because, without the (scripted) center zone, the center can actually not be reached.
So disregard my note.

Will test the latest update soonish.

@ywwg
Copy link
Member Author

ywwg commented Nov 26, 2025

I suppose we could light up the light when the pitch is zero, regardless of where the slider is. (That would show how far off from centered the slider is)

@ronso0
Copy link
Member

ronso0 commented Nov 26, 2025

Yeah, thing is, what I tried to explain: with the high-res faders I never managed to hit zero -- that's why I added the center zone #14735, and with relative mode we currently don't have this center zone.

Is there a way to engage relative mode on request? And auto-quit?
Maybe like this:

  • make a Shift move = fader enters relative mode
  • move out of bounds, or not, doesn't matter
  • while moving in relative mode (1), as soon as the engine fader reaches zero, we'd enable softTakeover and the physical fader would be decoupled
  • move fader to center = pick up engine fader, return to absolute mode

(1) I suspect the crucial/tricky part would be to distinguish rate changes done by the engine (sync locked) and the physial fader

Kind of related to #14985

@ywwg
Copy link
Member Author

ywwg commented Nov 26, 2025

Is there a way to engage relative mode on request? And auto-quit?

  • while moving in relative mode (1), as soon as the engine fader reaches zero, the physical fader would be decoupled (not set rate anymore)

is the goal here "I want to reset the pitch to zero"? I think that could be accomplished by moving the physical fader to the center, disabling relative mode and reengaging relative mode. Personally I would just right-click on the screen to do this rather than try to do it with the controller.

If you can say more about the use-case for this it would help me understand. I don't quite understand what would be happening in a DJ set that you want to do.

@ronso0
Copy link
Member

ronso0 commented Nov 26, 2025

Right. First of all, I'm not requesting to expand the scope here. Relative mode is nice, even if it can only be toggled in the settings.

My use case is beat matching to external sources. I usually try to match by ear or use a BPM tap tool.
If the target BPM is outside the +- 8%, I usually pick a broader rate range, though I don't like that too much as I'm loosing the precision of the 8% range -- which I like because it allows me to get somewhat close to target BPM with quick up-and-down moves. ("riding the fader" as Ella Skins called it)
Relative mode with 8% would be great for this case.

What (in my mind) would feel natural is moving the fader to the upper/lower limit, Shift-move it back to (somewhere near) the center and continue in relative mode. And later on return to absolute mode as described.

Copy link
Member

@acolombier acolombier left a comment

Choose a reason for hiding this comment

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

These new features are nice! 👍
Perhaps it would be worth documenting them as well.

Comment on lines +302 to +303
// For alternativeMoveMode
beatAlternative: 5,
Copy link
Member

@acolombier acolombier Dec 5, 2025

Choose a reason for hiding this comment

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

Is this redundant? I am wondering if it would be best to handle the alternative status as part of the beat mode, rather than duplicating the mode entirely. The issue being, any new feature looking into the mode (e.g feedback on the active mode on screen) would require to handle both of these modes, while they are actually the same, just swapping the shifted/unshifted behaviour
(See below suggestion inside the main switch)

Copy link
Member Author

Choose a reason for hiding this comment

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

I am being a little sneaky here -- using a component pattern to change behavior instead of logic switches. That makes it easier to debug and switch between behaviors because the behavior is self-contained. But doing it halfway like this does result in a lot of visible duplication. It gets shorter if the behavior (a beatjump size adjust) is assigned to a specific action in one line (unpressed + unshifted).

Note that all three branches of logic change with the Alt version -- so it would get pretty messy indeed to interleave the mode conditional with the shift conditional with the encoder-press conditional and then with the direction conditional.

I can mash them together but I think it's going to look pretty bad all spread out -- I think it would be better to create little behavior functions to clean it up.

}
this.secondDeckModes = null;
this.selectedHotcue = null;
this.relativeTempoMode = RelativeTempoMode;
Copy link
Member

Choose a reason for hiding this comment

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

Is this now redundant with Shift+ long press Master? Or would we want to have a DefaultRelativeTempoMode option?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes there's a config setting to make relative the default mode (line 207), and this loads the default.

engine.setValue(this.deck.group, `hotcue_${this.deck.selectedHotcue}_color`, Object.keys(LedColorMap)[currentColorIdx]);
break;
}
case moveModes.beatAlternative:
Copy link
Member

Choose a reason for hiding this comment

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

Aligned with my comment above, creating a new mode feels suboptimal. Here you could factorise the beat mode case (currently handled in default) as such, so you don't need to duplicate the branch.

(Note that I am suggesting AlternativeBeatMoveMode instead of AlternativeMoveMode since this last one is confusing, as none of the other move mode's behaviour are changing)

                default:
                    if (!this.shifted) {
                        if (!this.deck.leftEncoderPress.pressed && !AlternativeBeatMoveMode) {
                            script.triggerControl(this.group, right ? "beatjump_forward" : "beatjump_backward");
                        } else if (this.deck.leftEncoderPress.pressed && AlternativeBeatMoveMode) {
                            script.triggerControl(this.group, right ? "pitch_up_small" : "pitch_down_small");
                        } else {
                            let beatjumpSize = engine.getValue(this.group, "beatjump_size");
                            engine.setValue(this.group, "beatjump_size", beatjumpSize * (right ? 2 : 1/2));
                        }
                    } 
                    else if (AlternativeBeatMoveMode) {
                        script.triggerControl(this.group, right ? "beatjump_forward" : "beatjump_backward");
                    } else {
                        script.triggerControl(this.group, right ? "pitch_up_small" : "pitch_down_small");
                    }
                    break;
                }

Copy link
Member Author

Choose a reason for hiding this comment

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

I find this really hard to read and follow the logic, especially with all the inversions ("NOT shifted and NOT pressed..."), but I agree the duplicated version is not great either. (I am not even sure what set of conditionals trigger the bare else! uhhhh "encoderPressed NOT Alternativebeatmodemode"?)

Copy link
Member Author

@ywwg ywwg Dec 7, 2025

Choose a reason for hiding this comment

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

I'll play with it and see what I can come up with, getting rid of the new mode

@ywwg
Copy link
Member Author

ywwg commented Dec 7, 2025

I think this is a decent balance between ease of reading and duplication. I can now read through the branches and know what will happen with each operation. The one-liners for action help, good suggestion.

@ywwg ywwg force-pushed the traktor-s4mk3-alt-modes branch from c000bb4 to 270372e Compare December 8, 2025 18:46
Copy link
Member

@acolombier acolombier left a comment

Choose a reason for hiding this comment

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

Code's looking good, I'll try to take that for another test during the week end, except if @ronso0 wants to press merge before?
Feel free to prepare the manual PR as well

@ronso0
Copy link
Member

ronso0 commented Dec 8, 2025

Yes, I may have some time with my S4 before the weekend so could do a test -- no guarantee though

@ywwg
Copy link
Member Author

ywwg commented Dec 10, 2025

manual: mixxxdj/manual#819

Copy link
Member

@acolombier acolombier left a comment

Choose a reason for hiding this comment

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

Tested and works great! I actually like the relative tempo mode and will likely use this feature often!
I just pushed a small fixup to use the tempo led to display feedback on the current active tempo mode: f94e82c

@ywwg
Copy link
Member Author

ywwg commented Dec 16, 2025

if it's ok by you then you are good to merge the two PRs 😎

@acolombier
Copy link
Member

Are you happy with getting the above fixup in? Happy to push it to your branch and merge if that's fine by you!

Relative Tempo Fader mode: Tempo fader only responds to changes, not absolute position. Use shift to change position without adjusting rate.
Relative Tempo Fader, Shifted mode: Only respond to changes when shift is held.  Prevents accidental bumps when DJing in poor tactile environments (wearing fursuit paws)
Jog Wheel Adjustment factor: Adjust sensitivity of jog wheel when making sync adjustments.
Alternative Mode mode: for the move encoder, change assignments: unshifted to change beatjump size, shift to do a beatjump, push and change to adjust pitch.

Signed-off-by: Owen Williams <owilliams@mixxx.org>
@ywwg ywwg force-pushed the traktor-s4mk3-alt-modes branch from 001fe38 to 763666f Compare December 16, 2025 23:42
@ywwg
Copy link
Member Author

ywwg commented Dec 16, 2025

squashed and pushed

Copy link
Member

@acolombier acolombier left a comment

Choose a reason for hiding this comment

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

I just had the opportunity to take this mapping for a spin and unfortunately, I encounter a few bug, I couldn't spot yet, which are:

  • Key reset doesn't work and seem to jump back to a very low key instead (0?)
  • when jumping between relative and absolute, the tempo led seem to get stuck in takeover low color

I'll see if I can find them and suggest a fixup if time allows it.

@ywwg
Copy link
Member Author

ywwg commented Jan 12, 2026

how are you doing key reset?

@acolombier
Copy link
Member

Shift+left encoder press. However, this seems to set the key to 0 (the lowest possible pitch, giving very unexpected result)

Copy link
Member

@ronso0 ronso0 left a comment

Choose a reason for hiding this comment

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

Just one thing I noticed


const TempoFaderSoftTakeoverColorLow = LedColors[engine.getSetting("tempoFaderSoftTakeoverColorLow")] || LedColors.white;
const TempoFaderSoftTakeoverColorHigh = LedColors[engine.getSetting("tempoFaderSoftTakeoverColorHigh")] || LedColors.green;
const TempoFaderRelativeMove = LedColors[engine.getSetting("tempoFaderRelativeMove")] || LedColors.blue;
Copy link
Member

Choose a reason for hiding this comment

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

The var name should contain 'color' IMO

Suggested change
const TempoFaderRelativeMove = LedColors[engine.getSetting("tempoFaderRelativeMove")] || LedColors.blue;
const TempoFaderRelativeModeColor = LedColors[engine.getSetting("tempoFaderRelativeModeColor")] || LedColors.blue;

@ronso0
Copy link
Member

ronso0 commented Jan 16, 2026

FWIW I didn't really get used to relative tempo mode. (tested it once during a session, ported to my mapping)
What was confusing me was the once I'm out of the pitch range I there is no reflection of the physical slider anymore (of course).

Some time ago I had rate_ultra #1767 in by branch for a while, incl. skin & mapping mods, and that felt more intuitive, though I didn't (have to) use it much back then. I really think for a wider availability of extended tempo control we should go with that, instead of implementing it in individual mappings.
TL;DR rate_ultra is a slider that determines the center of the current rate range, eg. with a rate_ultra +6% the bounds of +-8% rate would be -2% / +14%.
So essentially rate_ultra is the coarse tempo fader and rate is for fine adjustment.
The physical slider can always be in sync with the GUI slider, especially when you enable sync on the deck.
(yes, there is also a softtakeover situation when both are mapped to one fader, eg. rate_ultra = Shift+tempo, but that is manageable IMHO)

Wdyt?

Copy link
Member

@acolombier acolombier left a comment

Choose a reason for hiding this comment

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

I re-tested this mapping to try and find this issue with key reset, but couldn't reproduce and had everything working again.
Happy to merge as is and fix forward!

@ywwg
Copy link
Member Author

ywwg commented Jan 20, 2026

The physical slider can always be in sync with the GUI slider, especially when you enable sync on the deck. (yes, there is also a softtakeover situation when both are mapped to one fader, eg. rate_ultra = Shift+tempo, but that is manageable IMHO)

Wdyt?

I think this ultra slider is a good idea, and I would support merging it, but personally the relative mode is much more useful to me so I will use that instead. for me it's not about going beyond the bounds of the slider (and it's trivial to shift+move if I hit the edge), it's totally avoiding soft-takeover, which I do not personally like. I would say it makes sense to move all of these modes into mixxx instead of individually implementing them in controllers.

@ywwg
Copy link
Member Author

ywwg commented Jan 20, 2026

sorry again for the lack of work on this, I am still waiting for my new laptop and I cannot do any mixxx work until it gets here.

@ywwg
Copy link
Member Author

ywwg commented Jan 26, 2026

yay new laptop

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.

3 participants