summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAntoine C <mixxx@acolombier.dev>2023-02-22 13:25:01 +0000
committerAntoine C <mixxx@acolombier.dev>2023-06-04 17:25:04 +0100
commitc5aa244b9b00f855f4dace6dfb793de7b59a0fdf (patch)
tree9cd21afb5894ba3d3280a895273893f582b2049e
parent9a61748ba93231217b129f7f8507b7eb4cd8127a (diff)
Kontrol S4 Mk3: better motor stability and smoother support for 45 RPM mode
-rw-r--r--res/controllers/Traktor-Kontrol-S4-MK3.js193
1 files changed, 121 insertions, 72 deletions
diff --git a/res/controllers/Traktor-Kontrol-S4-MK3.js b/res/controllers/Traktor-Kontrol-S4-MK3.js
index 21a769be45..50d2cfd444 100644
--- a/res/controllers/Traktor-Kontrol-S4-MK3.js
+++ b/res/controllers/Traktor-Kontrol-S4-MK3.js
@@ -98,6 +98,15 @@ const gridButtonBlinkOverBeat = true;
// less responsive it gets in Mixxx. Default: 5
const wheelSpeedSample = 5;
+// Make the sampler tab a beatlooproll tab instead
+// Default: false
+const useBeatloopRoolInsteadOfSampler = false;
+
+// Define the speed of the jogwheel. This will impact the speed of the LED playback indicator, the sratch, and the speed of
+// the motor if enable. Recommended value are 33 + 1/3 or 45.
+// Default: 33 + 1/3
+const baseRevolutionsPerMinute = 33 + 1/3;
+
// Define whether or not to use motors.
// This is a BETA feature! Please use at your own risk. Setting this off means that below settings are inactive
// Default: false
@@ -115,9 +124,8 @@ const turnTableSpeedSample = 40;
const tightnessFactor = 0.5;
// Define how much force can the motor use. This defines how much the wheel will "fight" you when you block it in TT mode
-// This will also impact resistance of the wheel if you are using a tight setting (tightnessFactor< 0.5)
-// Default: 24000.
-const maxWheelForce = 24000;
+// This will also im
+const maxWheelForce = 25000; // Traktor seems to cap the max value at 60000, which just sounds insane
@@ -161,7 +169,7 @@ const quickEffectPresetColors = [
// assign samplers to the crossfader on startup
const samplerCrossfaderAssign = true;
-const motorWindUpMilliseconds = 600;
+const motorWindUpMilliseconds = 1200;
const motorWindDownMilliseconds = 900;
/*
@@ -501,6 +509,8 @@ class Button extends Component {
this.inBitLength = 1;
}
}
+ unshift() {}
+ shift() {}
setKey(key) {
this.inKey = key;
if (key === this.outKey) {
@@ -581,7 +591,10 @@ class TriggerButton extends Button {
super(options);
}
onShortPress() {
- script.triggerControl(this.group, this.inKey, true);
+ engine.setValue(this.group, this.inKey, true);
+ }
+ onShortRelease() {
+ engine.setValue(this.group, this.inKey, false);
}
}
@@ -636,8 +649,16 @@ class CueButton extends PushButton {
input(pressed) {
if (this.deck.moveMode === moveModes.keyboard) {
this.deck.assignKeyboardPlayMode(this.group, this.inKey);
+ } else if (this.deck.wheelMode === wheelModes.motor && engine.getValue(this.group, "play") && pressed) {
+ engine.setValue(this.group, "cue_goto", pressed);
} else {
engine.setValue(this.group, this.inKey, pressed);
+ if (this.deck.wheelMode === wheelModes.motor) {
+ engine.setValue(this.group, "scratch2_enable", false);
+ engine.beginTimer(motorWindDownMilliseconds, function() {
+ engine.setValue(this.group, "scratch2_enable", true);
+ }, true);
+ }
}
}
}
@@ -772,38 +793,22 @@ class KeyboardButton extends PushButton {
}
}
-class BeatLoopRollButton extends Button {
+const beatLoopRolls = [0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8];
+class BeatLoopRollButton extends TriggerButton {
constructor(options) {
- super(options);
- if (this.number === undefined || !Number.isInteger(this.number) || this.number < 1 || this.number > 8) {
- throw Error("BeatLoopRollButton must have a number property of an integer between 1 and 8");
+ if (options.number === undefined || !Number.isInteger(options.number) || options.number < 0 || options.number > 7) {
+ throw Error("BeatLoopRollButton must have a number property of an integer between 0 and 7");
}
+ options.key = "beatlooproll_"+beatLoopRolls[options.number]+"_activate";
+ super(options);
if (this.deck === undefined) {
throw Error("BeatLoopRollButton must have a deck attached to it");
}
+
this.outConnect();
}
- unshift() {
- this.outTrigger();
- }
- shift() {
- this.outTrigger();
- }
output(value) {
- // In us
- }
- outConnect() {
- if (undefined !== this.group) {
- const connection = engine.makeConnection(this.group, "key", (key) => {
- const offset = this.deck.keyboardOffset - (this.shifted ? 8 : 0);
- this.output(key === this.number + offset);
- });
- if (connection) {
- this.outConnections[0] = connection;
- } else {
- console.warn("Unable to connect '" + this.group + ".key' to the controller output. The control appears to be unaivailable.");
- }
- }
+ this.send(LEDColors.white + (value ? this.brightnessOn : this.brightnessOff));
}
}
@@ -816,8 +821,6 @@ class SamplerButton extends Button {
this.group = "[Sampler" + this.number + "]";
this.outConnect();
}
- unshift() { }
- shift() { }
onShortPress() {
if (!this.shifted) {
if (engine.getValue(this.group, "track_loaded") === 0) {
@@ -1275,7 +1278,6 @@ const wheelAbsoluteMax = 2879;
const wheelTimerMax = 2 ** 32 - 1;
const wheelTimerTicksPerSecond = 100000000;
-const baseRevolutionsPerMinute = 33 + 1 / 3;
const baseRevolutionsPerSecond = baseRevolutionsPerMinute / 60;
const wheelTicksPerTimerTicksToRevolutionsPerSecond = wheelTimerTicksPerSecond / wheelAbsoluteMax;
@@ -2043,7 +2045,7 @@ class S4Mk3Deck extends Deck {
];
const hotcuePage2 = Array(8).fill({});
const hotcuePage3 = Array(8).fill({});
- const samplerPage = Array(8).fill({});
+ const samplerOrBeatloopRoolPage = Array(8).fill({});
this.keyboard = Array(8).fill({});
let i = 0;
/* eslint no-unused-vars: "off" */
@@ -2051,20 +2053,30 @@ class S4Mk3Deck extends Deck {
// start with hotcue 5; hotcues 1-4 are in defaultPadLayer
hotcuePage2[i] = new HotcueButton({number: i + 1});
hotcuePage3[i] = new HotcueButton({number: i + 13});
- let samplerNumber = i + 1;
- if (samplerNumber > 4) {
- samplerNumber += 4;
- }
- if (decks[0] > 1) {
- samplerNumber += 4;
- }
- samplerPage[i] = new SamplerButton({number: samplerNumber});
- if (samplerCrossfaderAssign) {
- engine.setValue(
- "[Sampler" + samplerNumber + "]",
- "orientation",
- (decks[0] === 1) ? 0 : 2
- );
+ if (useBeatloopRoolInsteadOfSampler) {
+ samplerOrBeatloopRoolPage[i] = new BeatLoopRollButton({
+ number: i,
+ deck: this,
+ });
+
+ } else {
+ let samplerNumber = i + 1;
+ if (samplerNumber > 4) {
+ samplerNumber += 4;
+ }
+ if (decks[0] > 1) {
+ samplerNumber += 4;
+ }
+ samplerOrBeatloopRoolPage[i] = new SamplerButton({
+ number: samplerNumber,
+ });
+ if (samplerCrossfaderAssign) {
+ engine.setValue(
+ "[Sampler" + samplerNumber + "]",
+ "orientation",
+ (decks[0] === 1) ? 0 : 2
+ );
+ }
}
this.keyboard[i] = new KeyboardButton({
number: i + 1,
@@ -2083,7 +2095,6 @@ class S4Mk3Deck extends Deck {
Object.assign(pad, io.pads[index]);
if (!(pad instanceof HotcueButton)) {
pad.color = deck.color;
- pad.unshift();
}
// don't change the group of SamplerButtons
if (!(pad instanceof SamplerButton)) {
@@ -2092,6 +2103,7 @@ class S4Mk3Deck extends Deck {
if (pad.inPacket === undefined) {
pad.inPacket = inPackets[1];
}
+ pad.unshift();
pad.outPacket = outPacket;
pad.inConnect();
pad.outConnect();
@@ -2164,7 +2176,7 @@ class S4Mk3Deck extends Deck {
deck: this,
onShortPress: function() {
if (this.deck.currentPadLayer !== this.deck.padLayers.samplerPage) {
- switchPadLayer(this.deck, samplerPage);
+ switchPadLayer(this.deck, samplerOrBeatloopRoolPage);
engine.setValue("[Samplers]", "show_samplers", true);
this.deck.currentPadLayer = this.deck.padLayers.samplerPage;
} else {
@@ -2368,7 +2380,6 @@ class S4Mk3Deck extends Deck {
switch (this.deck.wheelMode) {
case wheelModes.motor:
- // engine.setValue(this.group, "scratch2", 1.0);
engine.setValue(this.group, "scratch2", this.ttAvgSpeed / baseRevolutionsPerSecond);
break;
case wheelModes.loopIn:
@@ -2791,6 +2802,18 @@ class S4MK3 {
});
if (useMotors) {
engine.beginTimer(20, this.motorCallback.bind(this));
+ this.leftVelocityFactor = wheelAbsoluteMax * baseRevolutionsPerSecond * 2;
+ this.rightVelocityFactor = wheelAbsoluteMax * baseRevolutionsPerSecond * 2;
+
+ this.leftFactor = [this.leftVelocityFactor];
+ this.leftFactorIdx = 1;
+ this.rightFactor = [this.rightVelocityFactor];
+ this.rightFactorIdx = 1;
+
+ this.averageLeftCorrectness = [];
+ this.averageLeftCorrectnessIdx = 0;
+ this.averageRightCorrectness = [];
+ this.averageRightCorrectnessIdx = 0;
}
}
@@ -2800,7 +2823,6 @@ class S4MK3 {
1, 0x20, 1, 0, 0,
];
- const velocityFactor = 4500;
const maxVelocity = 10;
let velocityLeft = 0;
@@ -2819,15 +2841,15 @@ class S4MK3 {
expectedRightSpeed = engine.getValue(this.rightDeck.group, "rate_ratio");
}
- const currentLeftSpeed = (this.leftDeck.wheelRelative.avgSpeed + this.leftDeck.wheelRelative.ttAvgSpeed) / (2 * baseRevolutionsPerSecond);
- const currentRightSpeed = (this.rightDeck.wheelRelative.avgSpeed + this.rightDeck.wheelRelative.ttAvgSpeed) / (2 * baseRevolutionsPerSecond);
+ const currentLeftSpeed = this.leftDeck.wheelRelative.avgSpeed / baseRevolutionsPerSecond;
+ const currentRightSpeed = this.rightDeck.wheelRelative.avgSpeed / baseRevolutionsPerSecond;
if (expectedLeftSpeed) {
velocityLeft = expectedLeftSpeed + Math.min(
maxVelocity,
Math.max(
-maxVelocity,
- (expectedLeftSpeed - currentLeftSpeed) * 2
+ (expectedLeftSpeed - currentLeftSpeed)
)
);
} else {
@@ -2837,12 +2859,12 @@ class S4MK3 {
velocityLeft = currentLeftSpeed * reduceFactor;
} else if (tightnessFactor < 0.5) {
// Super tight
- const reduceFactor = (Math.min(0, tightnessFactor) * 4);
+ const reduceFactor = (2 - Math.max(0, tightnessFactor) * 4);
velocityLeft = expectedLeftSpeed + Math.min(
maxVelocity,
Math.max(
-maxVelocity,
- (expectedLeftSpeed - currentLeftSpeed) * 2
+ (expectedLeftSpeed - currentLeftSpeed) * reduceFactor
)
);
@@ -2864,12 +2886,13 @@ class S4MK3 {
velocityRight = currentRightSpeed * reduceFactor;
} else if (tightnessFactor < 0.5) {
// Super tight
- const reduceFactor = (Math.min(0, tightnessFactor) * 4);
+ const reduceFactor = (2 - Math.max(0, tightnessFactor) * 4);
+ console.log(reduceFactor);
velocityRight = expectedRightSpeed + Math.min(
maxVelocity,
Math.max(
-maxVelocity,
- (expectedRightSpeed - currentRightSpeed) * 2
+ (expectedRightSpeed - currentRightSpeed) * reduceFactor
)
);
@@ -2888,17 +2911,49 @@ class S4MK3 {
velocityRight = -velocityRight;
}
+ const roundedCurrentLeftSpeed = Math.round(currentLeftSpeed * 100);
+ const roundedCurrentRightSpeed = Math.round(currentRightSpeed * 100);
- if (expectedLeftSpeed) {
- velocityLeft = Math.pow(velocityLeft, 2) * velocityFactor;
- } else {
- velocityLeft = velocityLeft * velocityFactor;
+ velocityLeft = velocityLeft * this.leftVelocityFactor;
+ velocityRight = velocityRight * this.rightVelocityFactor;
+
+ const minNormalFactor = 0.8 * wheelAbsoluteMax * baseRevolutionsPerSecond * 2;
+ const maxNormalFactor = 1.2 * wheelAbsoluteMax * baseRevolutionsPerSecond * 2;
+
+ if (velocityLeft > minNormalFactor && velocityLeft < maxNormalFactor) {
+ this.averageLeftCorrectness[this.averageLeftCorrectnessIdx] = roundedCurrentLeftSpeed;
+ this.averageLeftCorrectnessIdx = (this.averageLeftCorrectnessIdx + 1) % 10;
+ const averageCorrectness = Math.round(this.averageLeftCorrectness.reduce((a, b) => a+b, 0) / this.averageLeftCorrectness.length);
+ this.leftFactor[this.leftFactorIdx] = velocityLeft;
+ this.leftFactorIdx = (this.leftFactorIdx + 1) % 10;
+ const averageFactor = Math.round(this.leftFactor.reduce((a, b) => a+b, 0) / this.leftFactor.length);
+
+
+ if ((averageCorrectness < 100 && velocityLeft > this.leftVelocityFactor) || (averageCorrectness > 100 && velocityLeft < this.leftVelocityFactor)) {
+ this.leftVelocityFactor = averageFactor;
+ }
}
- if (expectedRightSpeed) {
- velocityRight = Math.pow(velocityRight, 2) * velocityFactor;
- } else {
- velocityRight = velocityRight * velocityFactor;
+ if (velocityRight > minNormalFactor && velocityRight < maxNormalFactor) {
+ this.averageRightCorrectness[this.averageRightCorrectnessIdx] = roundedCurrentRightSpeed / (expectedRightSpeed || 0.001);
+ this.averageRightCorrectnessIdx = (this.averageRightCorrectnessIdx + 1) % 20;
+ const averageCorrectness = Math.round(this.averageRightCorrectness.reduce((a, b) => a+b, 0) / this.averageRightCorrectness.length);
+ this.rightFactor[this.rightFactorIdx] = velocityRight;
+ this.rightFactorIdx = (this.rightFactorIdx + 1) % 20;
+ const averageFactor = Math.round(this.rightFactor.reduce((a, b) => a+b, 0) / this.rightFactor.length);
+
+
+ if ((averageCorrectness < 100 && velocityRight > this.rightVelocityFactor) || (averageCorrectness > 100 && velocityRight < this.rightVelocityFactor)) {
+ this.rightVelocityFactor = averageFactor;
+ }
+ }
+
+ if (velocityLeft) {
+ velocityLeft += wheelAbsoluteMax / 2;
+ }
+
+ if (velocityRight) {
+ velocityRight += wheelAbsoluteMax / 2;
}
velocityLeft = Math.min(
@@ -2916,12 +2971,6 @@ class S4MK3 {
motorData[8] = velocityRight & 0xff;
motorData[9] = velocityRight >> 8;
-
- //// byte 2 > 127 rotates backward
- if (Math.round(currentLeftSpeed * 100) !== Math.round(expectedLeftSpeed * 100)) {
- console.log(expectedLeftSpeed + " " + Math.round(currentLeftSpeed * 100) + " -> " + velocityLeft + "\t" + expectedRightSpeed + " -> " + currentRightSpeed);
- }
-
controller.send(motorData, null, 49, true);
}
incomingData(data) {