Skip to content

Commit 2f95dc3

Browse files
committed
Merge remote-tracking branch 'upstream/2.6' into StemInfoImporterImprovements
2 parents f3c364b + 436ac50 commit 2f95dc3

62 files changed

Lines changed: 632 additions & 297 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ repos:
6666
]
6767
exclude: ^(packaging/wix/LICENSE.rtf.in|src/dialog/dlgabout\.cpp|res/linux/org\.mixxx\.Mixxx\.desktop|.*\.(?:pot?|(?<!\.d\.)ts|wxl|svg))$
6868
- repo: https://github.com/pre-commit/mirrors-eslint
69-
rev: v10.4.0
69+
rev: v10.4.1
7070
hooks:
7171
- id: eslint
7272
args: [--fix, --report-unused-disable-directives]
@@ -77,7 +77,7 @@ repos:
7777
- pre-commit
7878
- manual
7979
additional_dependencies:
80-
- eslint@^10.4.0
80+
- eslint@^10.4.1
8181
- "@eslint/js"
8282
- typescript-eslint
8383
- eslint-plugin-jsdoc@^v50.4.3

CMakeLists.txt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3047,6 +3047,12 @@ target_link_libraries(mixxx-lib PRIVATE Chromaprint::Chromaprint)
30473047

30483048
# Locale Aware Compare for SQLite
30493049
find_package(SQLite3)
3050+
if(NOT TARGET SQLite3::SQLite3) # CMake < 4.3
3051+
# This will allow to use the new name while still permitting it to
3052+
# compile with older versions of CMake.
3053+
add_library(SQLite3::SQLite3 ALIAS SQLite::SQLite3)
3054+
endif()
3055+
30503056
# For LOCALECOMPARE we call directly sqlite functions to the database opened by
30513057
# Qt. It only works without crashing when Mixxx links to the same sqlite
30523058
# library as Qt.
@@ -3078,10 +3084,10 @@ if(LOCALECOMPARE)
30783084
)
30793085
endif()
30803086
target_compile_definitions(mixxx-lib PUBLIC __SQLITE3__)
3081-
target_link_libraries(mixxx-lib PRIVATE SQLite::SQLite3)
3087+
target_link_libraries(mixxx-lib PRIVATE SQLite3::SQLite3)
30823088
elseif(SQLite3_IS_STATIC)
30833089
# in the static case we need to link SQLite3 uncoditionally
3084-
target_link_libraries(mixxx-lib PRIVATE SQLite::SQLite3)
3090+
target_link_libraries(mixxx-lib PRIVATE SQLite3::SQLite3)
30853091
endif()
30863092

30873093
# Denon Engine Prime library export support (using libdjinterop)
@@ -3582,6 +3588,7 @@ if(QT6)
35823588
if(QT_VERSION VERSION_GREATER_EQUAL 6.10)
35833589
# from Qt 6.10 GuiPrivate required for QShader/rendergraph (rhi/qshader.h)
35843590
list(APPEND QT_EXTRA_COMPONENTS "GuiPrivate")
3591+
set(QT_NO_PRIVATE_MODULE_WARNING ON)
35853592
endif()
35863593
else()
35873594
find_package(QT 5.12 NAMES Qt5 COMPONENTS Core REQUIRED)
@@ -5020,7 +5027,7 @@ target_precompile_headers(
50205027
mixxx-lib
50215028
PUBLIC ${MIXXX_LIB_PRECOMPILED_HEADER} ${MIXXX_COMMON_PRECOMPILED_HEADER}
50225029
)
5023-
if(BUILD_TESTING)
5030+
if(BUILD_TESTING AND NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
50245031
target_precompile_headers(mixxx-test REUSE_FROM mixxx-lib)
50255032
endif()
50265033

res/controllers/Traktor-Kontrol-S2-MK3-hid-scripts.js

Lines changed: 150 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,49 @@
44
/* jshint -W016 */
55
///////////////////////////////////////////////////////////////////////////////////
66
/* */
7-
/* Traktor Kontrol S2 MK3 HID controller script v1.01 */
8-
/* Last modification: February 2021 */
7+
/* Traktor Kontrol S2 MK3 HID controller script v1.02 */
8+
/* Last modification: January 2026 */
99
/* Author: Michael Schmidt */
1010
/* https://github.com/mixxxdj/mixxx/wiki/Native%20Instruments%20Traktor%20Kontrol%20S2%20MK3 */
1111
/* */
1212
///////////////////////////////////////////////////////////////////////////////////
1313

14+
/* ============================================================================
15+
* User Settings
16+
* These values are intended to be adjusted by end users to tune jog wheel feel.
17+
* ========================================================================== */
18+
19+
// Affects how sensitive jogging/nudging (turning the wheel without touching the top) is.
20+
// A constant of 0.5 makes jogging/nudging roughly as fast as scratching.
21+
const JOG_SENSITIVITY = 0.25;
22+
23+
// Coefficient for the jogwheel input's low pass filter.
24+
// Range: 0-1. Lower = more smoothing. A value of 1 results in no smoothing at all.
25+
const JOGWHEEL_ALPHA = 0.5;
26+
27+
// Threshold for the jogwheel input's dead zone. When the raw velocity (ticks/clock Hz) is lower than this,
28+
// the jogwheel is considered to be stopped, allowing it to change directions or exit scratching mode instantly.
29+
const JOGWHEEL_EPSILON = 0.001;
30+
31+
32+
/* ============================================================================
33+
* Internal Tuning Constants
34+
* Do not change these unless you know what you're doing.
35+
* ========================================================================== */
36+
37+
// Interval (ms) at which jog velocity is polled after release to determine whether scratching should stop. Minimum is 20ms.
38+
const JOGWHEEL_STOP_POLL_TIME = 20;
39+
40+
// Interval (ms) at which jog velocity is reduced stepwise after release. Minimum is 20ms.
41+
const JOGWHEEL_DECAY_POLL_TIME = 20;
42+
43+
// Constants used to scale raw velocity (tick delta / time delta) to the appropriate scratch2 value.
44+
const TICKS_PER_REV = 600;
45+
const JOGWHEEL_CLOCK_HZ = 100000;
46+
const TARGET_RPM = 33 + 1/3;
47+
const VELOCITY_TO_SCRATCH = JOGWHEEL_CLOCK_HZ / (TICKS_PER_REV * TARGET_RPM / 60);
48+
const VELOCITY_TO_JOG = VELOCITY_TO_SCRATCH * JOG_SENSITIVITY;
49+
1450
var TraktorS2MK3 = new function() {
1551
this.controller = new HIDController();
1652
this.shiftPressed = {"[Channel1]": false, "[Channel2]": false};
@@ -30,9 +66,12 @@ var TraktorS2MK3 = new function() {
3066
this.syncPressedTimer = {"[Channel1]": 0, "[Channel2]": 0}; // Timer to distinguish between short and long press
3167

3268
// Jog wheels
33-
this.pitchBendMultiplier = 1.1;
3469
this.lastTickVal = [0, 0];
35-
this.lastTickTime = [0.0, 0.0];
70+
this.lastTimestamp = [0, 0];
71+
this.lastVelocity = [0.0, 0.0];
72+
this.lastWallClock = [0, 0];
73+
this.jogStopTimerId = [null, null];
74+
this.jogDecayTimerId = [null, null];
3675

3776
// VuMeter
3877
this.vuLeftConnection = {};
@@ -617,62 +656,138 @@ TraktorS2MK3.samplerPregainHandler = function(field) {
617656
};
618657

619658
TraktorS2MK3.jogTouchHandler = function(field) {
620-
const deckNumber = TraktorS2MK3.controller.resolveDeck(field.group);
659+
const deckIndex = TraktorS2MK3.controller.resolveDeck(field.group) - 1;
660+
621661
if (field.value > 0) {
622-
engine.scratchEnable(deckNumber, 1024, 33 + 1 / 3, 0.125, 0.125 / 8, true);
662+
// Cancel any existing stop timers
663+
TraktorS2MK3.stopTimer(TraktorS2MK3.jogStopTimerId, deckIndex);
664+
engine.setValue(field.group, "scratch2_enable", true);
665+
} else {
666+
TraktorS2MK3.jogStopper(field);
667+
}
668+
};
669+
670+
// Called after the wheel is released. Stops scratching when the wheel is slow enough, allowing for inertia.
671+
TraktorS2MK3.jogStopper = function(field) {
672+
const deckIndex = TraktorS2MK3.controller.resolveDeck(field.group) - 1;
673+
674+
// If the wheel is stopped, exit scratching mode
675+
if (Math.abs(engine.getValue(field.group, "scratch2")) <= JOGWHEEL_EPSILON * VELOCITY_TO_SCRATCH) {
676+
engine.setValue(field.group, "scratch2", 0);
677+
engine.setValue(field.group, "scratch2_enable", false);
678+
TraktorS2MK3.lastVelocity[deckIndex] = 0;
679+
TraktorS2MK3.jogStopTimerId[deckIndex] = null;
680+
// Otherwise, check again after a while
623681
} else {
624-
engine.scratchDisable(deckNumber);
682+
TraktorS2MK3.jogStopTimerId[deckIndex] = engine.beginTimer(JOGWHEEL_STOP_POLL_TIME, () => TraktorS2MK3.jogStopper(field), true);
625683
}
626684
};
627685

628686
TraktorS2MK3.jogHandler = function(field) {
629-
const deckNumber = TraktorS2MK3.controller.resolveDeck(field.group);
630-
const deltas = TraktorS2MK3.wheelDeltas(deckNumber, field.value);
631-
const tickDelta = deltas[0];
632-
const timeDelta = deltas[1];
687+
const deckIndex = TraktorS2MK3.controller.resolveDeck(field.group) - 1;
688+
const velocity = TraktorS2MK3.wheelVelocity(deckIndex, field.value);
633689

634-
if (engine.isScratching(deckNumber)) {
635-
engine.scratchTick(deckNumber, tickDelta);
690+
if (engine.getValue(field.group, "scratch2_enable")) {
691+
engine.setValue(field.group, "scratch2", velocity * VELOCITY_TO_SCRATCH);
692+
693+
// Cancel any existing decay timers
694+
TraktorS2MK3.stopTimer(TraktorS2MK3.jogDecayTimerId, deckIndex);
695+
// Start timer to manually decay the velocity after a while
696+
TraktorS2MK3.jogDecayTimerId[deckIndex] = engine.beginTimer(JOGWHEEL_DECAY_POLL_TIME, () => {
697+
TraktorS2MK3.jogDecayer(field);
698+
}, true);
699+
700+
} else {
701+
engine.setValue(field.group, "jog", velocity * VELOCITY_TO_JOG);
702+
}
703+
};
704+
705+
// Called continuously after jogwheel stops sending packets. Gradually slows the jogwheel.
706+
TraktorS2MK3.jogDecayer = function(field) {
707+
const deckIndex = TraktorS2MK3.controller.resolveDeck(field.group) - 1;
708+
709+
// If wheel is slow enough, immediately set scratch2 to 0
710+
if (Math.abs(engine.getValue(field.group, "scratch2")) <= JOGWHEEL_EPSILON * VELOCITY_TO_SCRATCH) {
711+
TraktorS2MK3.lastVelocity[deckIndex] = 0;
712+
engine.setValue(field.group, "scratch2", 0);
713+
TraktorS2MK3.jogDecayTimerId[deckIndex] = null;
714+
// Otherwise, decay the velocity and call itself again after a while
636715
} else {
637-
const velocity = (tickDelta / timeDelta) * TraktorS2MK3.pitchBendMultiplier;
638-
engine.setValue(field.group, "jog", velocity);
716+
const decayedVelocity = TraktorS2MK3.lastVelocity[deckIndex] * (1 - JOGWHEEL_ALPHA);
717+
TraktorS2MK3.lastVelocity[deckIndex] = decayedVelocity;
718+
engine.setValue(field.group, "scratch2", decayedVelocity * VELOCITY_TO_SCRATCH);
719+
TraktorS2MK3.jogDecayTimerId[deckIndex] = engine.beginTimer(JOGWHEEL_DECAY_POLL_TIME, () => TraktorS2MK3.jogDecayer(field), true);
639720
}
640721
};
641722

642-
TraktorS2MK3.wheelDeltas = function(deckNumber, value) {
643-
// When the wheel is touched, four bytes change, but only the first behaves predictably.
644-
// It looks like the wheel is 1024 ticks per revolution.
645-
const tickval = value & 0xFF;
646-
let timeval = value >>> 16;
647-
let prevTick = 0;
648-
let prevTime = 0;
723+
// Helper function that checks if a timer is running and stop it if it is
724+
TraktorS2MK3.stopTimer = function(timerArray, deckIndex) {
725+
const id = timerArray[deckIndex];
726+
727+
if (id !== null && id !== undefined) {
728+
engine.stopTimer(id);
729+
timerArray[deckIndex] = null;
730+
}
731+
};
732+
733+
TraktorS2MK3.wheelVelocity = function(deckIndex, value) {
734+
// When the wheel is touched, four bytes change.
735+
// The first 10 bits change when the wheel is turned.
736+
// The last 22 bits are a counter, which constantly increments and overflows at 100kHz.
737+
const tickval = value & 0x3FF;
738+
let timeval = value >>> 10;
649739

650740
// Group 1 and 2 -> Array index 0 and 1
651-
prevTick = this.lastTickVal[deckNumber - 1];
652-
prevTime = this.lastTickTime[deckNumber - 1];
653-
this.lastTickVal[deckNumber - 1] = tickval;
654-
this.lastTickTime[deckNumber - 1] = timeval;
741+
const prevTick = this.lastTickVal[deckIndex];
742+
const prevTime = this.lastTimestamp[deckIndex];
743+
const prevWallClock = this.lastWallClock[deckIndex];
744+
this.lastTickVal[deckIndex] = tickval;
745+
this.lastTimestamp[deckIndex] = timeval;
746+
this.lastWallClock[deckIndex] = Date.now();
747+
748+
// If the user hasn't touched the jog wheel for a long time, the
749+
// internal timer may have looped around more than once. We have nothing
750+
// to go by so return 0
751+
if (this.lastWallClock[deckIndex] - prevWallClock > 40000) {
752+
this.lastVelocity[deckIndex] = 0;
753+
return 0;
754+
}
655755

656756
if (prevTime > timeval) {
657757
// We looped around. Adjust current time so that subtraction works.
658-
timeval += 0x10000;
758+
timeval += 0x400000;
659759
}
660760
let timeDelta = timeval - prevTime;
661761
if (timeDelta === 0) {
662-
// Spinning too fast to detect speed! By not dividing we are guessing it took 1ms.
762+
// Spinning too fast to detect speed! By not dividing we are guessing it took 10us.
663763
timeDelta = 1;
664764
}
665765

666-
let tickDelta = 0;
667-
if (prevTick >= 200 && tickval <= 100) {
668-
tickDelta = tickval + 256 - prevTick;
669-
} else if (prevTick <= 100 && tickval >= 200) {
670-
tickDelta = tickval - prevTick - 256;
766+
let tickDelta = tickval - prevTick;
767+
// Check if we looped around
768+
if (tickDelta > 512) {
769+
// Looped around from 0 to max
770+
tickDelta -= 1024;
771+
} else if (tickDelta < -512) {
772+
// Looped around from max to 0
773+
tickDelta += 1024;
774+
}
775+
776+
// Velocity smoothing
777+
const velocity = tickDelta / timeDelta;
778+
const prevVelocity = this.lastVelocity[deckIndex];
779+
let nextVelocity;
780+
// Check if the jogwheel is currently stopped or changing directions.
781+
// If so, set the velocity to the new value instantly.
782+
if ((Math.abs(prevVelocity) < JOGWHEEL_EPSILON) || (velocity * prevVelocity < 0)) {
783+
nextVelocity = velocity;
784+
// Otherwise, smooth the velocity.
671785
} else {
672-
tickDelta = tickval - prevTick;
786+
nextVelocity = JOGWHEEL_ALPHA * velocity + (1 - JOGWHEEL_ALPHA) * prevVelocity;
673787
}
788+
this.lastVelocity[deckIndex] = nextVelocity;
674789

675-
return [tickDelta, timeDelta];
790+
return nextVelocity;
676791
};
677792

678793
TraktorS2MK3.fxHandler = function(field) {

res/controllers/Traktor-MX2-hid-scripts.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
/////////////////////////////////////////////////////////////////////////////////////////
1414

1515
// Constants used to scale raw velocity (tick delta / time delta) to the appropriate scratch2 value.
16+
const JOGWHEEL_TICKS_MAX_VAL = 2 ** 32;
1617
const TICKS_PER_REV = 1024;
1718
const JOGWHEEL_CLOCK_HZ = 100000000;
1819
const TARGET_RPM = 33 + 1 / 3;
@@ -957,12 +958,12 @@ class TraktorMX2Class {
957958

958959
let tickDelta = tickval - prevTick;
959960
// Check if we looped around
960-
if (tickDelta > TICKS_PER_REV / 2) {
961+
if (tickDelta > JOGWHEEL_TICKS_MAX_VAL / 2) {
961962
// Looped around from 0 to max
962-
tickDelta -= TICKS_PER_REV;
963-
} else if (tickDelta < -TICKS_PER_REV / 2) {
963+
tickDelta -= JOGWHEEL_TICKS_MAX_VAL;
964+
} else if (tickDelta < -JOGWHEEL_TICKS_MAX_VAL / 2) {
964965
// Looped around from max to 0
965-
tickDelta += TICKS_PER_REV;
966+
tickDelta += JOGWHEEL_TICKS_MAX_VAL;
966967
}
967968

968969
// Velocity smoothing

res/controllers/mixxx-controls.d.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1897,7 +1897,11 @@ declare namespace MixxxControls {
18971897
| 'pfl'
18981898

18991899
/**
1900-
* The total adjustment to the track’s pitch, including changes from the rate slider if keylock is off as well as pitch_adjust.
1900+
* The total adjustment to the track’s pitch, including changes from the rate slider
1901+
* if keylock is off as well as pitch_adjust.
1902+
* It is reset to 0 after loading a new track when “Key/Pitch” is ticked in
1903+
* Options ‣ Preferences ‣ Decks ‣ Reset On Track Load and when
1904+
* “Keylock mode” is “Current key”.
19011905
* This is a ControlPotMeter control.
19021906
*
19031907
* @groups [ChannelN], [PreviewDeckN], [SamplerN]
@@ -1909,7 +1913,10 @@ declare namespace MixxxControls {
19091913
| `pitch${PotMeterSuffix}`
19101914

19111915
/**
1912-
* Adjusts the pitch in addition to the tempo slider pitch and keylock. It is reset after loading a new track.
1916+
* Adjusts the pitch in addition to the tempo slider pitch and keylock.
1917+
* It is reset to 0 after loading a new track when “Key/Pitch” is ticked in
1918+
* Options ‣ Preferences ‣ Decks ‣ Reset On Track Load
1919+
* and when “Keylock mode” is “Original key”.
19131920
* This is a ControlPotMeter control.
19141921
*
19151922
* @groups [ChannelN], [PreviewDeckN], [SamplerN]

src/controllers/dlgprefcontrollers.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,9 @@ void DlgPrefControllers::destroyControllerWidgets() {
177177
}
178178
while (!m_controllerPages.isEmpty()) {
179179
DlgPrefController* pControllerDlg = m_controllerPages.takeLast();
180+
181+
// this triggers the deletion of pControllerDlg, it no longer may be used
180182
m_pDlgPreferences->removePageWidget(pControllerDlg);
181-
delete pControllerDlg;
182183
}
183184

184185
m_controllerTreeItems.clear();

src/controllers/midi/midicontroller.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,11 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping,
643643
QJSValue MidiController::makeInputHandler(unsigned char status,
644644
unsigned char control,
645645
const QJSValue& scriptCode) {
646-
auto pJsEngine = getScriptEngine()->jsEngine();
646+
auto pEngine = getScriptEngine();
647+
if (pEngine == nullptr) {
648+
return QJSValue();
649+
}
650+
auto pJsEngine = pEngine->jsEngine();
647651
VERIFY_OR_DEBUG_ASSERT(pJsEngine) {
648652
return QJSValue();
649653
}

0 commit comments

Comments
 (0)