dualsense-ts
Version:
The natural interface for your DualSense and DualSense Access controllers, with Typescript
168 lines • 7.11 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TriggerFeedback = exports.TriggerEffect = void 0;
exports.buildTriggerEffectBlock = buildTriggerEffectBlock;
/** Canonical trigger effect types from the DualSense firmware */
var TriggerEffect;
(function (TriggerEffect) {
/** No resistance — default linear feel */
TriggerEffect["Off"] = "off";
/** Zone-based continuous resistance */
TriggerEffect["Feedback"] = "feedback";
/** Resistance with a snap release point, like a weapon trigger */
TriggerEffect["Weapon"] = "weapon";
/** Weapon feel with snap-back force, like drawing a bow */
TriggerEffect["Bow"] = "bow";
/** Rhythmic two-stroke oscillation */
TriggerEffect["Galloping"] = "galloping";
/** Zone-based oscillation with configurable amplitude and frequency */
TriggerEffect["Vibration"] = "vibration";
/** Dual-amplitude vibration with frequency and period control */
TriggerEffect["Machine"] = "machine";
})(TriggerEffect || (exports.TriggerEffect = TriggerEffect = {}));
// --- Internal translation to raw 11-byte trigger effect block ---
/** Mode byte values per effect type */
const EFFECT_MODE = {
[TriggerEffect.Off]: 0x05,
[TriggerEffect.Feedback]: 0x21,
[TriggerEffect.Weapon]: 0x25,
[TriggerEffect.Bow]: 0x22,
[TriggerEffect.Galloping]: 0x23,
[TriggerEffect.Vibration]: 0x26,
[TriggerEffect.Machine]: 0x27,
};
/** Clamp and round a 0-1 value to an integer range */
function scale(value, max, min = 0) {
return Math.round(Math.min(max, Math.max(min, value * max)));
}
/** Build the 11-byte trigger effect block from a config */
function buildTriggerEffectBlock(config) {
const block = new Uint8Array(11).fill(0);
if (config.effect === TriggerEffect.Off) {
block[0] = EFFECT_MODE[TriggerEffect.Off];
return block;
}
block[0] = EFFECT_MODE[config.effect];
switch (config.effect) {
case TriggerEffect.Feedback: {
const position = scale(config.position, 9);
const strength = scale(config.strength, 8, 1);
const forceValue = (strength - 1) & 0x07;
let forceZones = 0;
let activeZones = 0;
for (let i = position; i < 10; i++) {
forceZones |= forceValue << (3 * i);
activeZones |= 1 << i;
}
block[1] = activeZones & 0xff;
block[2] = (activeZones >> 8) & 0xff;
block[3] = forceZones & 0xff;
block[4] = (forceZones >> 8) & 0xff;
block[5] = (forceZones >> 16) & 0xff;
block[6] = (forceZones >> 24) & 0xff;
break;
}
case TriggerEffect.Weapon: {
const start = Math.min(7, Math.max(2, scale(config.start, 9)));
const end = Math.min(8, Math.max(start + 1, scale(config.end, 9)));
const strength = scale(config.strength, 8, 1);
const startAndStopZones = (1 << start) | (1 << end);
block[1] = startAndStopZones & 0xff;
block[2] = (startAndStopZones >> 8) & 0xff;
block[3] = strength - 1;
break;
}
case TriggerEffect.Bow: {
const start = scale(config.start, 8);
const end = Math.max(start + 1, scale(config.end, 8));
const strength = scale(config.strength, 8, 1);
const snapForce = scale(config.snapForce, 8, 1);
const startAndStopZones = (1 << start) | (1 << end);
const forcePair = ((strength - 1) & 0x07) | (((snapForce - 1) & 0x07) << 3);
block[1] = startAndStopZones & 0xff;
block[2] = (startAndStopZones >> 8) & 0xff;
block[3] = forcePair & 0xff;
block[4] = (forcePair >> 8) & 0xff;
break;
}
case TriggerEffect.Galloping: {
const start = scale(config.start, 8);
const end = Math.max(start + 1, scale(config.end, 9));
const firstFoot = scale(config.firstFoot, 6);
const secondFoot = Math.max(firstFoot + 1, scale(config.secondFoot, 7));
const startAndStopZones = (1 << start) | (1 << end);
const timeAndRatio = (secondFoot & 0x07) | ((firstFoot & 0x07) << 3);
block[1] = startAndStopZones & 0xff;
block[2] = (startAndStopZones >> 8) & 0xff;
block[3] = timeAndRatio & 0xff;
block[4] = Math.min(255, Math.max(1, Math.round(config.frequency)));
break;
}
case TriggerEffect.Vibration: {
const position = scale(config.position, 9);
const amplitude = scale(config.amplitude, 8, 1);
const strengthValue = (amplitude - 1) & 0x07;
let amplitudeZones = 0;
let activeZones = 0;
for (let i = position; i < 10; i++) {
amplitudeZones |= strengthValue << (3 * i);
activeZones |= 1 << i;
}
block[1] = activeZones & 0xff;
block[2] = (activeZones >> 8) & 0xff;
block[3] = amplitudeZones & 0xff;
block[4] = (amplitudeZones >> 8) & 0xff;
block[5] = (amplitudeZones >> 16) & 0xff;
block[6] = (amplitudeZones >> 24) & 0xff;
block[9] = Math.min(255, Math.max(1, Math.round(config.frequency)));
break;
}
case TriggerEffect.Machine: {
const start = scale(config.start, 8);
const end = Math.max(start + 1, scale(config.end, 9));
const amplitudeA = scale(config.amplitudeA, 7);
const amplitudeB = scale(config.amplitudeB, 7);
const startAndStopZones = (1 << start) | (1 << end);
const strengthPair = (amplitudeA & 0x07) | ((amplitudeB & 0x07) << 3);
block[1] = startAndStopZones & 0xff;
block[2] = (startAndStopZones >> 8) & 0xff;
block[3] = strengthPair & 0xff;
block[4] = Math.min(255, Math.max(1, Math.round(config.frequency)));
block[5] = Math.round(config.period);
break;
}
}
return block;
}
/** Holds the desired adaptive trigger feedback state and translates it for HID output */
class TriggerFeedback {
constructor() {
this._config = { effect: TriggerEffect.Off };
}
/** The current feedback configuration */
get config() {
return this._config;
}
/** The current effect type */
get effect() {
return this._config.effect;
}
/** Set adaptive trigger feedback */
set(config) {
this._config = config;
}
/** Reset to no resistance */
reset() {
this._config = { effect: TriggerEffect.Off };
}
/** Build the raw 11-byte effect block for HID output */
toBytes() {
return buildTriggerEffectBlock(this._config);
}
/** String key for change detection in the polling loop */
toKey() {
return JSON.stringify(this._config);
}
}
exports.TriggerFeedback = TriggerFeedback;
//# sourceMappingURL=trigger_feedback.js.map