v0.18
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user