This commit is contained in:
souldbminersmwc
2025-12-23 19:10:41 -05:00
parent 152f92a9a9
commit b5022ea839
43 changed files with 5143 additions and 3939 deletions

View File

@@ -30,10 +30,13 @@ namespace ult {
static HidVibrationDeviceHandle vibPlayer1Right;
static u64 rumbleStartTick = 0;
static u64 doubleClickTick = 0;
static u8 doubleClickPulse = 0;
static u8 doubleClickPulse = 0;
static u32 cachedHandheldStyle = 0;
static u32 cachedPlayer1Style = 0;
// ===== Shared flags (accessible globally) =====
std::atomic<bool> rumbleActive{false};
std::atomic<bool> clickActive{false};
std::atomic<bool> doubleClickActive{false};
// ===== Constants =====
@@ -41,144 +44,182 @@ namespace ult {
static constexpr u64 DOUBLE_CLICK_PULSE_DURATION_NS = 30'000'000ULL;
static constexpr u64 DOUBLE_CLICK_GAP_NS = 100'000'000ULL;
static constexpr HidVibrationValue clickDocked = {
//static constexpr HidVibrationValue clickDocked = {
// .amp_low = 0.20f,
// .freq_low = 100.0f,
// .amp_high = 0.80f,
// .freq_high = 300.0f
//};
//
//static constexpr HidVibrationValue clickHandheld = {
// .amp_low = 0.20f,
// .freq_low = 100.0f,
// .amp_high = 0.80f,
// .freq_high = 300.0f
//};
static constexpr HidVibrationValue hapticsPreset = {
.amp_low = 0.20f,
.freq_low = 100.0f,
.amp_high = 0.80f,
.freq_high = 300.0f
};
static constexpr HidVibrationValue clickHandheld = {
.amp_low = 0.25f,
.freq_low = 100.0f,
.amp_high = 1.0f,
.freq_high = 300.0f
};
static constexpr HidVibrationValue vibrationStop{0};
// ===== Internal helpers =====
static void initController(HidNpadIdType npad, HidVibrationDeviceHandle* handles, int count) {
const u32 styleMask = hidGetNpadStyleSet(npad);
if (styleMask)
hidInitializeVibrationDevices(handles, count, npad, static_cast<HidNpadStyleTag>(styleMask));
}
static void sendVibration(const HidVibrationValue* value) {
if (hidGetNpadStyleSet(HidNpadIdType_Handheld))
static inline void sendVibration(const HidVibrationValue* value) {
if (cachedHandheldStyle)
hidSendVibrationValue(vibHandheld, value);
if (hidGetNpadStyleSet(HidNpadIdType_No1)) {
if (cachedPlayer1Style) {
hidSendVibrationValue(vibPlayer1Left, value);
hidSendVibrationValue(vibPlayer1Right, value);
}
}
// ===== Public API =====
void initRumble() {
//if (rumbleInitialized) return;
// Try to initialize whatever is available
// Don't check if controllers exist - let initController handle it
initController(HidNpadIdType_Handheld, &vibHandheld, 1);
HidVibrationDeviceHandle handles[2];
initController(HidNpadIdType_No1, handles, 2);
vibPlayer1Left = handles[0];
vibPlayer1Right = handles[1];
// Only mark as initialized if at least one controller was found
hidGetNpadStyleSet(HidNpadIdType_Handheld);
hidGetNpadStyleSet(HidNpadIdType_No1);
//rumbleInitialized = (handheldStyle || player1Style);
// If neither exist, stay uninitialized so we retry later
static inline void sendVibration2x(const HidVibrationValue* value) {
sendVibration(value);
sendVibration(value);
}
//void deinitRumble() {
// ===== Public API =====
void initHaptics() {
const u32 handheldStyle = hidGetNpadStyleSet(HidNpadIdType_Handheld);
const u32 player1Style = hidGetNpadStyleSet(HidNpadIdType_No1);
// Clear previous handles to avoid using stale handles if controllers were removed
vibHandheld = (HidVibrationDeviceHandle)0;
vibPlayer1Left = (HidVibrationDeviceHandle)0;
vibPlayer1Right = (HidVibrationDeviceHandle)0;
// Handheld
if (handheldStyle) {
hidInitializeVibrationDevices(&vibHandheld, 1,
HidNpadIdType_Handheld,
(HidNpadStyleTag)handheldStyle);
}
// Player 1 (left + right Joy-Con or Pro Controller)
if (player1Style) {
HidVibrationDeviceHandle tmp[2] = { (HidVibrationDeviceHandle)0, (HidVibrationDeviceHandle)0 };
hidInitializeVibrationDevices(tmp, 2,
HidNpadIdType_No1,
(HidNpadStyleTag)player1Style);
vibPlayer1Left = tmp[0];
vibPlayer1Right = tmp[1];
}
// Ensure cache is valid immediately after initHaptics()
cachedHandheldStyle = handheldStyle;
cachedPlayer1Style = player1Style;
}
//void deinitHaptics() {
// rumbleInitialized = false;
//}
void checkAndReinitRumble() {
void checkAndReinitHaptics() {
static u32 lastHandheldStyle = 0;
static u32 lastPlayer1Style = 0;
static u32 lastPlayer1Style = 0;
const u32 currentHandheldStyle = hidGetNpadStyleSet(HidNpadIdType_Handheld);
const u32 currentPlayer1Style = hidGetNpadStyleSet(HidNpadIdType_No1);
const u32 currentPlayer1Style = hidGetNpadStyleSet(HidNpadIdType_No1);
// If not initialized but controllers exist, try to init
// This handles the boot race condition where HID reports controllers
// but vibration subsystem isn't ready yet
//if (!rumbleInitialized && (currentHandheldStyle || currentPlayer1Style)) {
// initRumble();
//}
// Reinitialize only if something changed (appearance/disappearance or style change)
//const bool changed =
// (currentHandheldStyle != lastHandheldStyle) || (currentPlayer1Style != lastPlayer1Style);
// Reinit if controller configuration changed
if (currentHandheldStyle != lastHandheldStyle || currentPlayer1Style != lastPlayer1Style) {
//rumbleInitialized = false;
initRumble();
if ((currentHandheldStyle != lastHandheldStyle) || (currentPlayer1Style != lastPlayer1Style)) {
initHaptics();
}
// Update last style tracking regardless
// Update last-known styles for change detection
lastHandheldStyle = currentHandheldStyle;
lastPlayer1Style = currentPlayer1Style;
lastPlayer1Style = currentPlayer1Style;
// Update cached styles used by sendVibration()/rumble paths
cachedHandheldStyle = currentHandheldStyle;
cachedPlayer1Style = currentPlayer1Style;
}
void rumbleClick() {
//if (!rumbleInitialized) {
// initRumble();
// if (!rumbleInitialized) return;
// Use cached style bit instead of querying hid each call
//const HidVibrationValue* pattern = cachedHandheldStyle ? &clickHandheld : &clickDocked;
sendVibration(&vibrationStop);
//if (cachedHandheldStyle) {
// sendVibration(&clickHandheld);
// sendVibration(&clickHandheld);
//} else {
// sendVibration(&clickDocked);
// sendVibration(&clickDocked);
//}
sendVibration(hidGetNpadStyleSet(HidNpadIdType_Handheld) ? &clickHandheld : &clickDocked);
rumbleActive.store(true, std::memory_order_release);
sendVibration2x(&hapticsPreset);
clickActive.store(true, std::memory_order_release);
rumbleStartTick = armGetSystemTick();
}
void rumbleDoubleClick() {
//if (!rumbleInitialized) {
// initRumble();
// if (!rumbleInitialized) return;
//onst HidVibrationValue* pattern = cachedHandheldStyle ? &clickHandheld : &clickDocked;
sendVibration(&vibrationStop);
//if (cachedHandheldStyle) {
// sendVibration(&clickHandheld);
// sendVibration(&clickHandheld);
//} else {
// sendVibration(&clickDocked);
// sendVibration(&clickDocked);
//}
sendVibration(hidGetNpadStyleSet(HidNpadIdType_Handheld) ? &clickHandheld : &clickDocked);
sendVibration2x(&hapticsPreset);
doubleClickActive.store(true, std::memory_order_release);
doubleClickPulse = 1;
doubleClickTick = armGetSystemTick();
doubleClickTick = armGetSystemTick(); // Set ONCE
}
void processRumbleStop(u64 nowNs) {
if (rumbleActive.load(std::memory_order_acquire) &&
if (clickActive.load(std::memory_order_acquire) &&
nowNs - armTicksToNs(rumbleStartTick) >= RUMBLE_DURATION_NS) {
sendVibration(&vibrationStop);
rumbleActive.store(false, std::memory_order_release);
clickActive.store(false, std::memory_order_release);
}
}
void processRumbleDoubleClick(u64 nowNs) {
if (!doubleClickActive.load(std::memory_order_acquire)) return;
const u64 elapsed = nowNs - armTicksToNs(doubleClickTick);
const u64 elapsed = nowNs - armTicksToNs(doubleClickTick); // Always from original start
switch (doubleClickPulse) {
case 1:
if (elapsed >= DOUBLE_CLICK_PULSE_DURATION_NS) {
sendVibration(&vibrationStop);
doubleClickPulse = 2;
doubleClickTick = armGetSystemTick();
// Don't reset tick!
}
break;
case 2:
if (elapsed >= DOUBLE_CLICK_GAP_NS) {
sendVibration(hidGetNpadStyleSet(HidNpadIdType_Handheld) ? &clickHandheld : &clickDocked);
if (elapsed >= DOUBLE_CLICK_PULSE_DURATION_NS + DOUBLE_CLICK_GAP_NS) {
// Use cached style here too
//if (cachedHandheldStyle) {
// sendVibration(&clickHandheld);
// sendVibration(&clickHandheld);
//} else {
// sendVibration(&clickDocked);
// sendVibration(&clickDocked);
//}
sendVibration2x(&hapticsPreset);
doubleClickPulse = 3;
doubleClickTick = armGetSystemTick();
// Don't reset tick!
}
break;
case 3:
if (elapsed >= DOUBLE_CLICK_PULSE_DURATION_NS) {
if (elapsed >= (DOUBLE_CLICK_PULSE_DURATION_NS * 2) + DOUBLE_CLICK_GAP_NS) {
sendVibration(&vibrationStop);
doubleClickActive.store(false, std::memory_order_release);
doubleClickPulse = 0;
@@ -187,16 +228,34 @@ namespace ult {
}
}
void rumbleDoubleClickStandalone() {
sendVibration(hidGetNpadStyleSet(HidNpadIdType_Handheld) ? &clickHandheld : &clickDocked);
// Standalone uses sleeps, but still use cached style for decision
//const HidVibrationValue* pattern = cachedHandheldStyle ? &clickHandheld : &clickDocked;
sendVibration(&vibrationStop);
//if (cachedHandheldStyle) {
// sendVibration(&clickHandheld);
// sendVibration(&clickHandheld);
//} else {
// sendVibration(&clickDocked);
// sendVibration(&clickDocked);
//}
sendVibration2x(&hapticsPreset);
svcSleepThread(DOUBLE_CLICK_PULSE_DURATION_NS);
sendVibration(&vibrationStop);
svcSleepThread(DOUBLE_CLICK_GAP_NS);
sendVibration(hidGetNpadStyleSet(HidNpadIdType_Handheld) ? &clickHandheld : &clickDocked);
//if (cachedHandheldStyle) {
// sendVibration(&clickHandheld);
// sendVibration(&clickHandheld);
//} else {
// sendVibration(&clickDocked);
// sendVibration(&clickDocked);
//}
sendVibration2x(&hapticsPreset);
svcSleepThread(DOUBLE_CLICK_PULSE_DURATION_NS);
sendVibration(&vibrationStop);
}
}