UNPKG

dualsense-ts

Version:

The natural interface for your DualSense and DualSense Access controllers, with Typescript

168 lines 7.11 kB
"use strict"; 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