From d5296779ecd2b0e0bd19898bec83d2eab05728cb Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sun, 18 Oct 2026 14:16:38 +0200 Subject: [PATCH] Store Wii calibration per remote (#299) * Store Wii calibration per remote * show calibration state per port * tie calibration message to remote --- runtime/include/runtime_config.h | 85 +++++++----- runtime/include/wii_remote_input.h | 20 ++- runtime/src/settings_overlay.cpp | 14 +- runtime/src/wii_remote_input.cpp | 208 ++++++++++++++++++++++------- 4 files changed, 233 insertions(+), 94 deletions(-) diff --git a/runtime/include/runtime_config.h b/runtime/include/runtime_config.h index 5d0a60a..99d6c2b 100644 --- a/runtime/include/runtime_config.h +++ b/runtime/include/runtime_config.h @@ -69,10 +69,11 @@ struct RuntimeUserConfig { // SDL's sensor frame (x right, y out of the button face, z towards the user). // SDL's Wii driver falls back to a nominal zero point when its read of the // remote's calibration block times out (common over Bluetooth), so this is - // measured in the overlay with the remote at rest. - std::optional wiiAccelOffsetX; - std::optional wiiAccelOffsetY; - std::optional wiiAccelOffsetZ; + // measured in the overlay with the remote at rest. Stored per remote under + // [controller.wii_accel_offsets]; the old shared + // wii_accel_offset_* keys are only detected, to report that they are ignored. + std::map> wiiAccelOffsets; + bool hasLegacyWiiAccelOffset = false; // Debugging aid: append every KPAD sample of the Bluetooth remote (raw and // corrected accelerometer, buttons) to wii_accel_trace.csv next to Config.toml. std::optional wiiAccelTrace; @@ -381,6 +382,28 @@ inline void AppendOverlayRoots(RuntimeUserConfig& config, const std::string& roo } // Reads every supported setting out of a parsed Config.toml document. +// [x, y, z] of finite numbers (integers allowed), else nullopt. +inline std::optional> ParseAccelOffset(const toml::value& value) { + if (!value.is_array() || value.as_array().size() != 3) { + return std::nullopt; + } + std::array offset{}; + for (size_t i = 0; i < 3; ++i) { + const toml::value& element = value.as_array()[i]; + if (element.is_floating()) { + offset[i] = element.as_floating(); + } else if (element.is_integer()) { + offset[i] = static_cast(element.as_integer()); + } else { + return std::nullopt; + } + if (!std::isfinite(offset[i])) { + return std::nullopt; + } + } + return offset; +} + inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) { RuntimeUserConfig config; @@ -404,6 +427,13 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) { config.controllerExpressions[key] = value.as_string(); } } + if (section->contains("wii_accel_offsets") && section->at("wii_accel_offsets").is_table()) { + for (const auto& [key, value] : section->at("wii_accel_offsets").as_table()) { + if (const auto offset = ParseAccelOffset(value)) { + config.wiiAccelOffsets[key] = *offset; + } + } + } } config.widescreen = FindConfigValue(document, "video", "widescreen"); @@ -465,9 +495,9 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) { FindConfigValue(document, "audio", "attenuate_music_when_media_plays"); config.wiiRemotes = FindConfigValue(document, "controller", "wii_remotes"); config.wiiContinuousScan = FindConfigValue(document, "controller", "wii_continuous_scan"); - config.wiiAccelOffsetX = FindConfigValue(document, "controller", "wii_accel_offset_x"); - config.wiiAccelOffsetY = FindConfigValue(document, "controller", "wii_accel_offset_y"); - config.wiiAccelOffsetZ = FindConfigValue(document, "controller", "wii_accel_offset_z"); + for (const char* key : {"wii_accel_offset_x", "wii_accel_offset_y", "wii_accel_offset_z"}) { + config.hasLegacyWiiAccelOffset |= FindConfigValue(document, "controller", key).value_or(0.0) != 0.0; + } config.wiiAccelTrace = FindConfigValue(document, "controller", "wii_accel_trace"); config.networkEnabled = FindConfigValue(document, "network", "enabled"); config.discordPresenceEnabled = FindConfigValue(document, "discord", "enabled"); @@ -842,12 +872,13 @@ inline bool SetWiiContinuousScanEnabled(bool value) { return WriteSetting("controller", "wii_continuous_scan", value ? "true" : "false"); } -// Wii Remote accelerometer zero-point correction (g, SDL sensor frame); all zero -// when the remote has not been calibrated. -inline std::array WiiAccelOffset() { - const RuntimeUserConfig& config = Get(); - return {config.wiiAccelOffsetX.value_or(0.0), config.wiiAccelOffsetY.value_or(0.0), - config.wiiAccelOffsetZ.value_or(0.0)}; +// Stored correction of the remote identified by `remoteKey`, if it has one. +inline std::optional> WiiAccelOffsetFor(const std::string& remoteKey) { + const auto it = Get().wiiAccelOffsets.find(remoteKey); + if (it == Get().wiiAccelOffsets.end()) { + return std::nullopt; + } + return it->second; } // Whether to write the per-frame accelerometer trace (off unless asked for). @@ -855,26 +886,14 @@ inline bool WiiAccelTraceEnabled(bool fallback = false) { return Get().wiiAccelTrace.value_or(fallback); } -// True while a non-zero correction is stored ("Clear calibration" writes zeros). -inline bool HasWiiAccelOffset() { - const std::array offset = WiiAccelOffset(); - return offset[0] != 0.0 || offset[1] != 0.0 || offset[2] != 0.0; -} - -// Persists the accelerometer correction measured by the overlay's calibration. -inline bool SetWiiAccelOffset(const std::array& offset) { - Mutable().wiiAccelOffsetX = offset[0]; - Mutable().wiiAccelOffsetY = offset[1]; - Mutable().wiiAccelOffsetZ = offset[2]; - bool ok = true; - const char* keys[3] = {"wii_accel_offset_x", "wii_accel_offset_y", "wii_accel_offset_z"}; - for (size_t i = 0; i < 3; ++i) { - // Always a float literal, so a whole-number offset does not come back as a TOML integer. - std::ostringstream formatted; - formatted << std::fixed << std::setprecision(4) << offset[i]; - ok = WriteSetting("controller", keys[i], formatted.str()) && ok; - } - return ok; +// Persists the accelerometer correction of one remote. `remoteKey` must be a bare TOML key. +inline bool SetWiiAccelOffsetFor(const std::string& remoteKey, const std::array& offset) { + Mutable().wiiAccelOffsets[remoteKey] = offset; + // Always float literals, so a whole-number offset does not come back as a TOML integer. + std::ostringstream formatted; + formatted << std::fixed << std::setprecision(4) << '[' << offset[0] << ", " << offset[1] << ", " << offset[2] + << ']'; + return WriteSetting("controller.wii_accel_offsets", remoteKey, formatted.str()); } // Target frame rate for frame interpolation, or 0 to disable it. diff --git a/runtime/include/wii_remote_input.h b/runtime/include/wii_remote_input.h index 1c21fdb..d6780cd 100644 --- a/runtime/include/wii_remote_input.h +++ b/runtime/include/wii_remote_input.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include struct PADStatus; @@ -73,16 +75,20 @@ bool ReadKpadSample(uint32_t chan, KpadSample& sample); // no remote or SDL has not delivered a sample yet. bool ReadAccelDebug(uint32_t chan, float sdlG[3], float kpadAcc[3]); -// Residual zero-point trim on top of SDL's factory-calibrated decode. Expects the -// remote lying still, buttons up; Poll() averages about a second and stores it. +// Residual zero-point trim on top of SDL's factory-calibrated decode, stored per +// physical remote under [controller.wii_accel_offsets]. Expects the remote lying +// still, buttons up; Poll() averages about a second and stores it for that remote. void StartAccelCalibration(uint32_t chan); -// Forgets the stored correction. -void ClearAccelCalibration(); -bool IsAccelCalibrating(); +// Forgets the stored correction of the remote on `chan`. +void ClearAccelCalibration(uint32_t chan); +bool IsAccelCalibrating(uint32_t chan); // 0..1 while a calibration is collecting samples. float AccelCalibrationProgress(); -// Outcome of the last calibration attempt for the overlay, or nullptr. -const char* AccelCalibrationMessage(); +// Correction in effect for the remote on `chan` (zeros when uncalibrated), or +// nullopt when the port has no remote. +std::optional> ReadAccelOffset(uint32_t chan); +// Outcome of the last calibration attempt on `chan` for the overlay, or nullptr. +const char* AccelCalibrationMessage(uint32_t chan); // Reports "no controller" on the GameCube side for every port served through // KPAD, so the game never sees the same remote twice. Runs after aurora's PADRead. diff --git a/runtime/src/settings_overlay.cpp b/runtime/src/settings_overlay.cpp index e0c7f73..9d37567 100644 --- a/runtime/src/settings_overlay.cpp +++ b/runtime/src/settings_overlay.cpp @@ -311,7 +311,7 @@ void DrawWiiRemoteAccelerometer(uint32_t port) { } // Residual per-axis bias left after SDL's factory-calibrated decode, measured // here with the remote at rest. - if (WiiRemoteInput::IsAccelCalibrating()) { + if (WiiRemoteInput::IsAccelCalibrating(port)) { ImGui::ProgressBar(WiiRemoteInput::AccelCalibrationProgress(), ImVec2(220.0f, 0.0f), "Hold still..."); } else if (ImGui::Button("Calibrate (remote lying flat, buttons up)")) { WiiRemoteInput::StartAccelCalibration(port); @@ -321,15 +321,17 @@ void DrawWiiRemoteAccelerometer(uint32_t port) { "for about two seconds. Corrects the steering offset of a remote held sideways."); } ImGui::SameLine(); - ImGui::BeginDisabled(!RuntimeConfigFile::HasWiiAccelOffset() || WiiRemoteInput::IsAccelCalibrating()); + const auto stored = WiiRemoteInput::ReadAccelOffset(port); + const std::array offset = stored.value_or(std::array{}); + const bool calibrated = offset[0] != 0.0 || offset[1] != 0.0 || offset[2] != 0.0; + ImGui::BeginDisabled(!calibrated || WiiRemoteInput::IsAccelCalibrating(port)); if (ImGui::Button("Clear calibration")) { - WiiRemoteInput::ClearAccelCalibration(); + WiiRemoteInput::ClearAccelCalibration(port); } ImGui::EndDisabled(); - if (const char* message = WiiRemoteInput::AccelCalibrationMessage()) { + if (const char* message = WiiRemoteInput::AccelCalibrationMessage(port)) { ImGui::TextWrapped("%s", message); - } else if (RuntimeConfigFile::HasWiiAccelOffset()) { - const std::array offset = RuntimeConfigFile::WiiAccelOffset(); + } else if (calibrated) { ImGui::TextDisabled("Stored offset: x %+.3f y %+.3f z %+.3f g", offset[0], offset[1], offset[2]); } else { ImGui::TextDisabled("No trim stored."); diff --git a/runtime/src/wii_remote_input.cpp b/runtime/src/wii_remote_input.cpp index 41aeb2a..c6b2247 100644 --- a/runtime/src/wii_remote_input.cpp +++ b/runtime/src/wii_remote_input.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -12,10 +13,12 @@ #include #include +#include #include #include #include #include +#include #include #include @@ -92,11 +95,17 @@ uint64_t g_lostAtMs = 0; // remote gets a fresh id and is enabled again. std::array g_sensorsEnabledFor{}; -// Zero-point correction subtracted from the remote's accelerometer, in g and in -// SDL's sensor frame; loaded from Config.toml on first use, replaced by a -// calibration run. The Nunchuk accelerometer is left uncorrected. -std::array g_accelOffset{}; -bool g_accelOffsetLoaded = false; +// Zero-point correction subtracted from a remote's accelerometer, in g and in +// SDL's sensor frame, with the config key it is stored under. Resolved once per +// gamepad instance (a re-created joystick of the same remote resolves to the same +// key) and replaced by a calibration run. The Nunchuk accelerometer is left +// uncorrected. +struct RemoteCalibration { + SDL_JoystickID instance = 0; + std::string key; + std::array offset{}; +}; +std::array g_remoteCalibration{}; // Frames sampled by a calibration run (about 1.5 s at 60 Hz) and how far a // sample may stray from the first one before the run is declared "moved". @@ -110,12 +119,15 @@ constexpr double kCalibrationMinTiltCosine = 0.9396926; struct AccelCalibration { bool active = false; uint32_t chan = 0; + SDL_JoystickID instance = 0; int count = 0; double sum[3] = {}; float first[3] = {}; }; AccelCalibration g_calibration; char g_calibrationMessage[160] = {}; +uint32_t g_calibrationMessageChan = 0; +SDL_JoystickID g_calibrationMessageRemote = 0; // Last accepted KPAD acc per port, for the remote and for the Nunchuk. Reused on // frames that bring no sample or a glitched one, so the game never sees a jump. @@ -155,14 +167,73 @@ void EnsureSensors(SDL_Gamepad* gamepad, uint32_t port) { } } -// Loads the stored zero-point correction once. -const std::array& AccelOffset() { - if (!g_accelOffsetLoaded) { - const std::array stored = RuntimeConfigFile::WiiAccelOffset(); - for (size_t i = 0; i < 3; ++i) g_accelOffset[i] = static_cast(stored[i]); - g_accelOffsetLoaded = true; +// USB interface number of the HID device at `path`, or -1 when it is not a USB +// interface or cannot be found. A DolphinBar shows each remote slot as one. +int UsbInterfaceForPath(const char* path, Uint16 vendor, Uint16 product) { + if (path == nullptr) { + return -1; } - return g_accelOffset; + int interfaceNumber = -1; + SDL_hid_device_info* list = SDL_hid_enumerate(vendor, product); + for (const SDL_hid_device_info* info = list; info != nullptr; info = info->next) { + if (info->path != nullptr && std::strcmp(info->path, path) == 0) { + interfaceNumber = info->interface_number; + break; + } + } + SDL_hid_free_enumeration(list); + return interfaceNumber; +} + +// Config key identifying the physical remote behind a gamepad: its HID serial +// (the Bluetooth address when paired directly), else its USB interface number +// (a DolphinBar has no serial and one interface per remote slot) and the game +// port, else the game port. Only characters valid in a bare TOML key. +std::string RemoteKey(SDL_Gamepad* gamepad, uint32_t port) { + std::string serial; + if (const char* raw = SDL_GetGamepadSerial(gamepad)) { + for (const char* c = raw; *c != '\0'; ++c) { + if (std::isalnum(static_cast(*c))) { + serial += static_cast(std::tolower(static_cast(*c))); + } + } + } + if (serial.find_first_not_of('0') != std::string::npos) { + return "serial_" + serial; + } + const Uint16 vendor = SDL_GetGamepadVendor(gamepad); + const Uint16 product = SDL_GetGamepadProduct(gamepad); + const int interfaceNumber = UsbInterfaceForPath(SDL_GetGamepadPath(gamepad), vendor, product); + char key[48]; + if (interfaceNumber >= 0) { + std::snprintf(key, sizeof(key), "usb_%04x_%04x_if%d_port%u", vendor, product, interfaceNumber, + port + 1); + } else { + std::snprintf(key, sizeof(key), "port_%u", port + 1); + } + return key; +} + +// Calibration of the remote currently on `port`, resolved on first use per gamepad +// instance. A remote without a stored entry has a zero offset. +RemoteCalibration& CalibrationFor(SDL_Gamepad* gamepad, uint32_t port) { + RemoteCalibration& calibration = g_remoteCalibration[port]; + const SDL_JoystickID id = SDL_GetGamepadID(gamepad); + if (calibration.instance != id) { + static bool s_legacyReported = false; + if (!s_legacyReported && RuntimeConfigFile::Get().hasLegacyWiiAccelOffset) { + s_legacyReported = true; + RT_LOG(RT_TAG_CONFIG) << "Old shared Wii Remote calibration ignored; recalibrate each remote in F10" + << std::endl; + } + calibration.instance = id; + calibration.key = RemoteKey(gamepad, port); + const std::optional> stored = RuntimeConfigFile::WiiAccelOffsetFor(calibration.key); + for (size_t i = 0; i < 3; ++i) calibration.offset[i] = stored ? static_cast((*stored)[i]) : 0.0f; + RT_LOG(RT_TAG_CONFIG) << "Wii Remote on port " << (port + 1) << ": calibration key " << calibration.key + << (stored ? "" : " (no stored offset)") << std::endl; + } + return calibration; } // Raw SDL sample in m/s^2, rejecting anything SDL has not delivered yet. @@ -200,9 +271,10 @@ bool IsGlitchedSample(SDL_SensorType sensor, const float* g) { // +x left, +y towards the user, +z out of the button face (wiibrew, Dolphin); // SDL_hidapi_wii.c posts (-wiiX, wiiZ, wiiY): x right across the face, y out of // the face (+1 at rest, buttons up), z towards the user, i.e. away from the tip. -// The remote's sample gets the zero-point correction; the Nunchuk's does not. -// False when there is no sample yet or the sample is a glitch (see above). -bool ReadAccelG(SDL_Gamepad* gamepad, SDL_SensorType sensor, float* g) { +// The remote's sample gets the zero-point correction of the remote on `port`; the +// Nunchuk's does not. False when there is no sample yet or the sample is a glitch +// (see above). +bool ReadAccelG(SDL_Gamepad* gamepad, uint32_t port, SDL_SensorType sensor, float* g) { float sdl[3] = {}; if (!ReadSdlAccel(gamepad, sensor, sdl)) { return false; @@ -212,7 +284,7 @@ bool ReadAccelG(SDL_Gamepad* gamepad, SDL_SensorType sensor, float* g) { return false; } if (sensor == SDL_SENSOR_ACCEL) { - const std::array& offset = AccelOffset(); + const std::array& offset = CalibrationFor(gamepad, port).offset; for (int i = 0; i < 3; ++i) g[i] -= offset[i]; } return true; @@ -231,9 +303,9 @@ void AccelGToKpad(const float* g, float* kpad) { } // Sensor -> KPAD acc for ReadKpadSample. -bool ReadAccelAsKpad(SDL_Gamepad* gamepad, SDL_SensorType sensor, float* kpad) { +bool ReadAccelAsKpad(SDL_Gamepad* gamepad, uint32_t port, SDL_SensorType sensor, float* kpad) { float g[3] = {}; - if (!ReadAccelG(gamepad, sensor, g)) { + if (!ReadAccelG(gamepad, port, sensor, g)) { return false; } AccelGToKpad(g, kpad); @@ -275,10 +347,25 @@ void TraceSample(uint32_t chan, const KpadSample& sample, const float* rawG, boo trace.flush(); } -// Ends a calibration run with a message for the overlay. -void FinishAccelCalibration(const char* message) { - g_calibration.active = false; +// Gamepad of the Wii Remote on `chan`, or nullptr. +SDL_Gamepad* RemoteGamepad(uint32_t chan) { + if (chan >= PAD_MAX_CONTROLLERS || !IsRemoteChannel(chan)) { + return nullptr; + } + return SDL_GetGamepadFromPlayerIndex(static_cast(chan)); +} + +void SetCalibrationMessage(uint32_t chan, const char* message) { + SDL_Gamepad* gamepad = RemoteGamepad(chan); + g_calibrationMessageChan = chan; + g_calibrationMessageRemote = gamepad != nullptr ? SDL_GetGamepadID(gamepad) : 0; std::snprintf(g_calibrationMessage, sizeof(g_calibrationMessage), "%s", message); +} + +// Ends a calibration run with a message for the overlay. +void FinishAccelCalibration(uint32_t chan, const char* message) { + g_calibration.active = false; + SetCalibrationMessage(chan, message); RT_LOG(RT_TAG_CONFIG) << "Wii Remote accelerometer calibration: " << message << std::endl; } @@ -288,9 +375,10 @@ void StepAccelCalibration() { if (!g_calibration.active) { return; } - SDL_Gamepad* gamepad = SDL_GetGamepadFromPlayerIndex(static_cast(g_calibration.chan)); - if (gamepad == nullptr || !IsRemoteChannel(g_calibration.chan)) { - FinishAccelCalibration("Cancelled: the Wii Remote went away."); + SDL_Gamepad* gamepad = RemoteGamepad(g_calibration.chan); + if (gamepad == nullptr || + SDL_GetGamepadID(gamepad) != g_calibration.instance) { + FinishAccelCalibration(g_calibration.chan, "Cancelled: the Wii Remote went away."); return; } EnsureSensors(gamepad, g_calibration.chan); @@ -308,7 +396,7 @@ void StepAccelCalibration() { } else { for (int i = 0; i < 3; ++i) { if (std::fabs(g[i] - g_calibration.first[i]) > kCalibrationMaxDeviationG) { - FinishAccelCalibration("Failed: the remote moved. Put it down, buttons up, and try again."); + FinishAccelCalibration(g_calibration.chan, "Failed: the remote moved. Put it down, buttons up, and try again."); return; } } @@ -322,18 +410,18 @@ void StepAccelCalibration() { const double length = std::sqrt(mean[0] * mean[0] + mean[1] * mean[1] + mean[2] * mean[2]); if (length < kCalibrationMinGravityG || length > kCalibrationMaxGravityG || mean[1] / length < kCalibrationMinTiltCosine) { - FinishAccelCalibration("Failed: the remote was not resting flat with the buttons up."); + FinishAccelCalibration(g_calibration.chan, "Failed: the remote was not resting flat with the buttons up."); return; } const std::array offset = {mean[0], mean[1] - 1.0, mean[2]}; - for (int i = 0; i < 3; ++i) g_accelOffset[i] = static_cast(offset[i]); - g_accelOffsetLoaded = true; - const bool saved = RuntimeConfigFile::SetWiiAccelOffset(offset); + RemoteCalibration& calibration = CalibrationFor(gamepad, g_calibration.chan); + for (int i = 0; i < 3; ++i) calibration.offset[i] = static_cast(offset[i]); + const bool saved = RuntimeConfigFile::SetWiiAccelOffsetFor(calibration.key, offset); char message[160]; std::snprintf(message, sizeof(message), "%s offset x %+.3f y %+.3f z %+.3f g", saved ? "Calibrated:" : "Calibrated (could not write Config.toml):", offset[0], offset[1], offset[2]); - FinishAccelCalibration(message); + FinishAccelCalibration(g_calibration.chan, message); } // True when any controller aurora knows about is a Wii device. @@ -655,7 +743,7 @@ bool ReadKpadSample(uint32_t chan, KpadSample& sample) { const bool haveRaw = ReadSdlAccel(gamepad, SDL_SENSOR_ACCEL, rawG); for (float& v : rawG) v /= kStandardGravity; LastAcc& last = g_lastAcc[chan]; - const bool accepted = ReadAccelAsKpad(gamepad, SDL_SENSOR_ACCEL, sample.acc); + const bool accepted = ReadAccelAsKpad(gamepad, chan, SDL_SENSOR_ACCEL, sample.acc); if (accepted) { last.valid = true; for (int i = 0; i < 3; ++i) last.acc[i] = sample.acc[i]; @@ -719,7 +807,7 @@ bool ReadKpadSample(uint32_t chan, KpadSample& sample) { sample.stick[1] = -static_cast(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY)) / 32767.0f; for (float& v : sample.stick) v = std::clamp(v, -1.0f, 1.0f); LastAcc& lastNunchuk = g_lastNunchukAcc[chan]; - if (ReadAccelAsKpad(gamepad, SDL_SENSOR_ACCEL_L, sample.nunchukAcc)) { + if (ReadAccelAsKpad(gamepad, chan, SDL_SENSOR_ACCEL_L, sample.nunchukAcc)) { lastNunchuk.valid = true; for (int i = 0; i < 3; ++i) lastNunchuk.acc[i] = sample.nunchukAcc[i]; } else { @@ -739,7 +827,7 @@ bool ReadAccelDebug(uint32_t chan, float sdlG[3], float kpadAcc[3]) { return false; } EnsureSensors(gamepad, chan); - if (!ReadAccelG(gamepad, SDL_SENSOR_ACCEL, sdlG)) { + if (!ReadAccelG(gamepad, chan, SDL_SENSOR_ACCEL, sdlG)) { return false; } AccelGToKpad(sdlG, kpadAcc); @@ -748,28 +836,47 @@ bool ReadAccelDebug(uint32_t chan, float sdlG[3], float kpadAcc[3]) { // Begins collecting rest samples from the remote on `chan`. void StartAccelCalibration(uint32_t chan) { - if (!IsRemoteChannel(chan)) { - FinishAccelCalibration("No Wii Remote on this port."); + SDL_Gamepad* gamepad = RemoteGamepad(chan); + if (gamepad == nullptr) { + FinishAccelCalibration(chan, "No Wii Remote on this port."); return; } g_calibration = {}; g_calibration.active = true; g_calibration.chan = chan; + g_calibration.instance = SDL_GetGamepadID(gamepad); g_calibrationMessage[0] = '\0'; } -// Drops the stored correction and goes back to SDL's raw reading. -void ClearAccelCalibration() { - g_calibration.active = false; - g_accelOffset = {}; - g_accelOffsetLoaded = true; - RuntimeConfigFile::SetWiiAccelOffset({0.0, 0.0, 0.0}); - std::snprintf(g_calibrationMessage, sizeof(g_calibrationMessage), "Calibration cleared."); +// Zeroes the stored correction of the remote on `chan`. +void ClearAccelCalibration(uint32_t chan) { + if (g_calibration.chan == chan) { + g_calibration.active = false; + } + SDL_Gamepad* gamepad = RemoteGamepad(chan); + if (gamepad == nullptr) { + SetCalibrationMessage(chan, "No Wii Remote on this port."); + return; + } + RemoteCalibration& calibration = CalibrationFor(gamepad, chan); + calibration.offset = {}; + RuntimeConfigFile::SetWiiAccelOffsetFor(calibration.key, {0.0, 0.0, 0.0}); + SetCalibrationMessage(chan, "Calibration cleared."); } -// True while a calibration run is collecting samples. -bool IsAccelCalibrating() { - return g_calibration.active; +// Correction in effect for the remote on `chan`, for the overlay. +std::optional> ReadAccelOffset(uint32_t chan) { + SDL_Gamepad* gamepad = RemoteGamepad(chan); + if (gamepad == nullptr) { + return std::nullopt; + } + const RemoteCalibration& calibration = CalibrationFor(gamepad, chan); + return std::array{calibration.offset[0], calibration.offset[1], calibration.offset[2]}; +} + +// True while a calibration run is collecting samples from `chan`. +bool IsAccelCalibrating(uint32_t chan) { + return g_calibration.active && g_calibration.chan == chan; } // Share of the calibration samples collected so far, 0..1; 0 when idle. @@ -777,9 +884,14 @@ float AccelCalibrationProgress() { return g_calibration.active ? static_cast(g_calibration.count) / kCalibrationSamples : 0.0f; } -// Outcome of the last calibration run for the overlay, or nullptr before any. -const char* AccelCalibrationMessage() { - return g_calibrationMessage[0] != '\0' ? g_calibrationMessage : nullptr; +// Outcome of the last calibration run on `chan` for the overlay, or nullptr. +const char* AccelCalibrationMessage(uint32_t chan) { + if (g_calibrationMessage[0] == '\0' || g_calibrationMessageChan != chan) { + return nullptr; + } + SDL_Gamepad* gamepad = RemoteGamepad(chan); + const SDL_JoystickID remote = gamepad != nullptr ? SDL_GetGamepadID(gamepad) : 0; + return remote == g_calibrationMessageRemote ? g_calibrationMessage : nullptr; } } // namespace WiiRemoteInput