@@ -10,6 +10,10 @@ namespace WiimoteCommon
{
constexpr u8 MAX_PAYLOAD = 23;

// Based on testing, old WiiLi.org docs, and WiiUse library:
// Max battery level seems to be 0xc8 (decimal 200)
constexpr u8 MAX_BATTERY_LEVEL = 0xc8;

enum class InputReportID : u8
{
Status = 0x20,
@@ -7,7 +7,9 @@
#include <vector>

#include "Common/CommonTypes.h"
#include "Common/Matrix.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"

#ifdef _MSC_VER
#pragma warning(push)
@@ -41,6 +43,8 @@ static_assert(sizeof(OutputReportGeneric) == 2, "Wrong size");

struct OutputReportRumble
{
static constexpr OutputReportID REPORT_ID = OutputReportID::Rumble;

u8 rumble : 1;
};
static_assert(sizeof(OutputReportRumble) == 1, "Wrong size");
@@ -55,8 +59,34 @@ struct OutputReportEnableFeature
};
static_assert(sizeof(OutputReportEnableFeature) == 1, "Wrong size");

struct OutputReportIRLogicEnable : OutputReportEnableFeature
{
static constexpr OutputReportID REPORT_ID = OutputReportID::IRLogicEnable;
};
static_assert(sizeof(OutputReportIRLogicEnable) == 1, "Wrong size");

struct OutputReportIRLogicEnable2 : OutputReportEnableFeature
{
static constexpr OutputReportID REPORT_ID = OutputReportID::IRLogicEnable2;
};
static_assert(sizeof(OutputReportIRLogicEnable2) == 1, "Wrong size");

struct OutputReportSpeakerEnable : OutputReportEnableFeature
{
static constexpr OutputReportID REPORT_ID = OutputReportID::SpeakerEnable;
};
static_assert(sizeof(OutputReportSpeakerEnable) == 1, "Wrong size");

struct OutputReportSpeakerMute : OutputReportEnableFeature
{
static constexpr OutputReportID REPORT_ID = OutputReportID::SpeakerMute;
};
static_assert(sizeof(OutputReportSpeakerMute) == 1, "Wrong size");

struct OutputReportLeds
{
static constexpr OutputReportID REPORT_ID = OutputReportID::LED;

u8 rumble : 1;
u8 ack : 1;
u8 : 2;
@@ -66,6 +96,8 @@ static_assert(sizeof(OutputReportLeds) == 1, "Wrong size");

struct OutputReportMode
{
static constexpr OutputReportID REPORT_ID = OutputReportID::ReportMode;

u8 rumble : 1;
u8 ack : 1;
u8 continuous : 1;
@@ -76,13 +108,17 @@ static_assert(sizeof(OutputReportMode) == 2, "Wrong size");

struct OutputReportRequestStatus
{
static constexpr OutputReportID REPORT_ID = OutputReportID::RequestStatus;

u8 rumble : 1;
u8 : 7;
};
static_assert(sizeof(OutputReportRequestStatus) == 1, "Wrong size");

struct OutputReportWriteData
{
static constexpr OutputReportID REPORT_ID = OutputReportID::WriteData;

u8 rumble : 1;
u8 : 1;
u8 space : 2;
@@ -100,6 +136,8 @@ static_assert(sizeof(OutputReportWriteData) == 21, "Wrong size");

struct OutputReportReadData
{
static constexpr OutputReportID REPORT_ID = OutputReportID::ReadData;

u8 rumble : 1;
u8 : 1;
u8 space : 2;
@@ -116,6 +154,8 @@ static_assert(sizeof(OutputReportReadData) == 6, "Wrong size");

struct OutputReportSpeakerData
{
static constexpr OutputReportID REPORT_ID = OutputReportID::SpeakerData;

u8 rumble : 1;
u8 : 2;
u8 length : 5;
@@ -157,6 +197,8 @@ static_assert(sizeof(ButtonData) == 2, "Wrong size");

struct InputReportStatus
{
static constexpr InputReportID REPORT_ID = InputReportID::Status;

ButtonData buttons;
u8 battery_low : 1;
u8 extension : 1;
@@ -170,6 +212,8 @@ static_assert(sizeof(InputReportStatus) == 6, "Wrong size");

struct InputReportAck
{
static constexpr InputReportID REPORT_ID = InputReportID::Ack;

ButtonData buttons;
OutputReportID rpt_id;
ErrorCode error_code;
@@ -178,6 +222,8 @@ static_assert(sizeof(InputReportAck) == 4, "Wrong size");

struct InputReportReadDataReply
{
static constexpr InputReportID REPORT_ID = InputReportID::ReadDataReply;

ButtonData buttons;
u8 error : 4;
u8 size_minus_one : 4;
@@ -187,6 +233,64 @@ struct InputReportReadDataReply
};
static_assert(sizeof(InputReportReadDataReply) == 21, "Wrong size");

// Accel data handled as if there were always 10 bits of precision.
using AccelType = Common::TVec3<u16>;
using AccelData = ControllerEmu::RawValue<AccelType, 10>;

// Found in Wiimote EEPROM and Nunchuk "register".
// 0g and 1g points exist.
struct AccelCalibrationPoint
{
// All components have 10 bits of precision.
u16 GetX() const { return x2 << 2 | x1; }
u16 GetY() const { return y2 << 2 | y1; }
u16 GetZ() const { return z2 << 2 | z1; }
auto Get() const { return AccelType{GetX(), GetY(), GetZ()}; }

void SetX(u16 x)
{
x2 = x >> 2;
x1 = x;
}
void SetY(u16 y)
{
y2 = y >> 2;
y1 = y;
}
void SetZ(u16 z)
{
z2 = z >> 2;
z1 = z;
}
void Set(AccelType accel)
{
SetX(accel.x);
SetY(accel.y);
SetZ(accel.z);
}

u8 x2, y2, z2;
u8 z1 : 2;
u8 y1 : 2;
u8 x1 : 2;
u8 : 2;
};

// Located at 0x16 and 0x20 of Wii Remote EEPROM.
struct AccelCalibrationData
{
using Calibration = ControllerEmu::TwoPointCalibration<AccelType, 10>;

auto GetCalibration() const { return Calibration(zero_g.Get(), one_g.Get()); }

AccelCalibrationPoint zero_g;
AccelCalibrationPoint one_g;

u8 volume : 7;
u8 motor : 1;
u8 checksum;
};

} // namespace WiimoteCommon

#pragma pack(pop)
@@ -60,14 +60,6 @@ void CameraLogic::Update(const Common::Matrix44& transform)
using Common::Vec3;
using Common::Vec4;

constexpr int CAMERA_WIDTH = 1024;
constexpr int CAMERA_HEIGHT = 768;

// Wiibrew claims the camera FOV is about 33 deg by 23 deg.
// Unconfirmed but it seems to work well enough.
constexpr int CAMERA_FOV_X_DEG = 33;
constexpr int CAMERA_FOV_Y_DEG = 23;

constexpr auto CAMERA_FOV_Y = float(CAMERA_FOV_Y_DEG * MathUtil::TAU / 360);
constexpr auto CAMERA_ASPECT_RATIO = float(CAMERA_FOV_X_DEG) / CAMERA_FOV_Y_DEG;

@@ -112,12 +104,12 @@ void CameraLogic::Update(const Common::Matrix44& transform)
if (point.z > 0)
{
// FYI: Casting down vs. rounding seems to produce more symmetrical output.
const auto x = s32((1 - point.x / point.w) * CAMERA_WIDTH / 2);
const auto y = s32((1 - point.y / point.w) * CAMERA_HEIGHT / 2);
const auto x = s32((1 - point.x / point.w) * CAMERA_RES_X / 2);
const auto y = s32((1 - point.y / point.w) * CAMERA_RES_Y / 2);

const auto point_size = std::lround(MAX_POINT_SIZE / point.w / 2);

if (x >= 0 && y >= 0 && x < CAMERA_WIDTH && y < CAMERA_HEIGHT)
if (x >= 0 && y >= 0 && x < CAMERA_RES_X && y < CAMERA_RES_Y)
return CameraPoint{u16(x), u16(y), u8(point_size)};
}

@@ -165,7 +157,7 @@ void CameraLogic::Update(const Common::Matrix44& transform)
for (std::size_t i = 0; i != camera_points.size(); ++i)
{
const auto& p = camera_points[i];
if (p.x < CAMERA_WIDTH)
if (p.x < CAMERA_RES_X)
{
IRExtended irdata = {};

@@ -186,7 +178,7 @@ void CameraLogic::Update(const Common::Matrix44& transform)
for (std::size_t i = 0; i != camera_points.size(); ++i)
{
const auto& p = camera_points[i];
if (p.x < CAMERA_WIDTH)
if (p.x < CAMERA_RES_X)
{
IRFull irdata = {};

@@ -203,8 +195,8 @@ void CameraLogic::Update(const Common::Matrix44& transform)

irdata.xmin = std::max(p.x - p.size, 0);
irdata.ymin = std::max(p.y - p.size, 0);
irdata.xmax = std::min(p.x + p.size, CAMERA_WIDTH);
irdata.ymax = std::min(p.y + p.size, CAMERA_HEIGHT);
irdata.xmax = std::min(p.x + p.size, CAMERA_RES_X);
irdata.ymax = std::min(p.y + p.size, CAMERA_RES_Y);

// TODO: Is this maybe MSbs of the "intensity" value?
irdata.zero = 0;
@@ -20,6 +20,8 @@ namespace WiimoteEmu
// Four bytes for two objects. Filled with 0xFF if empty
struct IRBasic
{
using IRObject = Common::TVec2<u16>;

u8 x1;
u8 y1;
u8 x2hi : 2;
@@ -28,6 +30,9 @@ struct IRBasic
u8 y1hi : 2;
u8 x2;
u8 y2;

auto GetObject1() const { return IRObject(x1hi << 8 | x1, y1hi << 8 | y1); }
auto GetObject2() const { return IRObject(x2hi << 8 | x2, y2hi << 8 | y2); }
};
static_assert(sizeof(IRBasic) == 5, "Wrong size");

@@ -62,6 +67,14 @@ static_assert(sizeof(IRFull) == 9, "Wrong size");
class CameraLogic : public I2CSlave
{
public:
static constexpr int CAMERA_RES_X = 1024;
static constexpr int CAMERA_RES_Y = 768;

// Wiibrew claims the camera FOV is about 33 deg by 23 deg.
// Unconfirmed but it seems to work well enough.
static constexpr int CAMERA_FOV_X_DEG = 33;
static constexpr int CAMERA_FOV_Y_DEG = 23;

enum : u8
{
IR_MODE_BASIC = 1,
@@ -54,11 +54,15 @@ double CalculateStopDistance(double velocity, double max_accel)
return velocity * velocity / (2 * std::copysign(max_accel, velocity));
}

// Note that 'gyroscope' is rotation of world around device.
Common::Matrix33 ComplementaryFilter(const Common::Vec3& accelerometer,
const Common::Matrix33& gyroscope, float accel_weight)
} // namespace

namespace WiimoteEmu
{
const auto gyro_vec = gyroscope * Common::Vec3{0, 0, 1};
Common::Matrix33 ComplementaryFilter(const Common::Matrix33& gyroscope,
const Common::Vec3& accelerometer, float accel_weight,
const Common::Vec3& accelerometer_normal)
{
const auto gyro_vec = gyroscope * accelerometer_normal;
const auto normalized_accel = accelerometer.Normalized();

const auto cos_angle = normalized_accel.Dot(gyro_vec);
@@ -76,10 +80,6 @@ Common::Matrix33 ComplementaryFilter(const Common::Vec3& accelerometer,
}
}

} // namespace

namespace WiimoteEmu
{
IMUCursorState::IMUCursorState() : rotation{Common::Matrix33::Identity()}
{
}
@@ -203,17 +203,17 @@ void EmulateSwing(MotionState* state, ControllerEmu::Force* swing_group, float t
}
}

WiimoteCommon::DataReportBuilder::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g,
u16 one_g)
WiimoteCommon::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g, u16 one_g)
{
const auto scaled_accel = accel * (one_g - zero_g) / float(GRAVITY_ACCELERATION);

// 10-bit integers.
constexpr long MAX_VALUE = (1 << 10) - 1;

return {u16(std::clamp(std::lround(scaled_accel.x + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.y + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.z + zero_g), 0l, MAX_VALUE))};
return WiimoteCommon::AccelData(
{u16(std::clamp(std::lround(scaled_accel.x + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.y + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.z + zero_g), 0l, MAX_VALUE))});
}

void EmulateCursor(MotionState* state, ControllerEmu::Cursor* ir_group, float time_elapsed)
@@ -311,28 +311,24 @@ void EmulateIMUCursor(IMUCursorState* state, ControllerEmu::IMUCursor* imu_ir_gr
}

// Apply rotation from gyro data.
const auto gyro_rotation = Common::Matrix33::FromQuaternion(ang_vel->x * time_elapsed / -2,
ang_vel->y * time_elapsed / -2,
ang_vel->z * time_elapsed / -2, 1);
const auto gyro_rotation = GetMatrixFromGyroscope(*ang_vel * -1 * time_elapsed);
state->rotation = gyro_rotation * state->rotation;

// If we have some non-zero accel data use it to adjust gyro drift.
constexpr auto ACCEL_WEIGHT = 0.02f;
auto const accel = imu_accelerometer_group->GetState().value_or(Common::Vec3{});
if (accel.LengthSquared())
state->rotation = ComplementaryFilter(accel, state->rotation, ACCEL_WEIGHT);

const auto inv_rotation = state->rotation.Inverted();
state->rotation = ComplementaryFilter(state->rotation, accel, ACCEL_WEIGHT);

// Clamp yaw within configured bounds.
const auto yaw = std::asin((inv_rotation * Common::Vec3{0, 1, 0}).x);
const auto yaw = GetYaw(state->rotation);
const auto max_yaw = float(imu_ir_group->GetTotalYaw() / 2);
auto target_yaw = std::clamp(yaw, -max_yaw, max_yaw);

// Handle the "Recenter" button being pressed.
if (imu_ir_group->controls[0]->GetState<bool>())
{
state->recentered_pitch = std::asin((inv_rotation * Common::Vec3{0, 1, 0}).z);
state->recentered_pitch = GetPitch(state->rotation);
target_yaw = 0;
}

@@ -390,10 +386,33 @@ Common::Matrix33 GetMatrixFromAcceleration(const Common::Vec3& accel)
axis.LengthSquared() ? axis.Normalized() : Common::Vec3{0, 1, 0});
}

Common::Matrix33 GetMatrixFromGyroscope(const Common::Vec3& gyro)
{
return Common::Matrix33::FromQuaternion(gyro.x / 2, gyro.y / 2, gyro.z / 2, 1);
}

Common::Matrix33 GetRotationalMatrix(const Common::Vec3& angle)
{
return Common::Matrix33::RotateZ(angle.z) * Common::Matrix33::RotateY(angle.y) *
Common::Matrix33::RotateX(angle.x);
}

float GetPitch(const Common::Matrix33& world_rotation)
{
const auto vec = world_rotation * Common::Vec3{0, 0, 1};
return std::atan2(vec.y, Common::Vec2(vec.x, vec.z).Length());
}

float GetRoll(const Common::Matrix33& world_rotation)
{
const auto vec = world_rotation * Common::Vec3{0, 0, 1};
return std::atan2(vec.x, vec.z);
}

float GetYaw(const Common::Matrix33& world_rotation)
{
const auto vec = world_rotation.Inverted() * Common::Vec3{0, 1, 0};
return std::atan2(vec.x, vec.y);
}

} // namespace WiimoteEmu
@@ -54,12 +54,26 @@ struct MotionState : PositionalState, RotationalState
{
};

// Note that 'gyroscope' is rotation of world around device.
// Alternative accelerometer_normal can be supplied to correct from non-accelerometer data.
// e.g. Used for yaw/pitch correction with IR data.
Common::Matrix33 ComplementaryFilter(const Common::Matrix33& gyroscope,
const Common::Vec3& accelerometer, float accel_weight,
const Common::Vec3& accelerometer_normal = {0, 0, 1});

// Estimate orientation from accelerometer data.
Common::Matrix33 GetMatrixFromAcceleration(const Common::Vec3& accel);

// Get a rotation matrix from current gyro data.
Common::Matrix33 GetMatrixFromGyroscope(const Common::Vec3& gyro);

// Build a rotational matrix from euler angles.
Common::Matrix33 GetRotationalMatrix(const Common::Vec3& angle);

float GetPitch(const Common::Matrix33& world_rotation);
float GetRoll(const Common::Matrix33& world_rotation);
float GetYaw(const Common::Matrix33& world_rotation);

void ApproachPositionWithJerk(PositionalState* state, const Common::Vec3& target,
const Common::Vec3& max_jerk, float time_elapsed);

@@ -75,7 +89,6 @@ void EmulateIMUCursor(IMUCursorState* state, ControllerEmu::IMUCursor* imu_ir_gr
ControllerEmu::IMUGyroscope* imu_gyroscope_group, float time_elapsed);

// Convert m/s/s acceleration data to the format used by Wiimote/Nunchuk (10-bit unsigned integers).
WiimoteCommon::DataReportBuilder::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g,
u16 one_g);
WiimoteCommon::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g, u16 one_g);

} // namespace WiimoteEmu
@@ -236,10 +236,6 @@ void Wiimote::HandleRequestStatus(const OutputReportRequestStatus&)
// Update status struct
m_status.extension = m_extension_port.IsDeviceConnected();

// Based on testing, old WiiLi.org docs, and WiiUse library:
// Max battery level seems to be 0xc8 (decimal 200)
constexpr u8 MAX_BATTERY_LEVEL = 0xc8;

m_status.battery = u8(std::lround(m_battery_setting.GetValue() / 100 * MAX_BATTERY_LEVEL));

if (Core::WantsDeterminism())
@@ -114,8 +114,10 @@ void Classic::Update()
{
const ControllerEmu::AnalogStick::StateData left_stick_state = m_left_stick->GetState();

classic_data.lx = static_cast<u8>(LEFT_STICK_CENTER + (left_stick_state.x * LEFT_STICK_RADIUS));
classic_data.ly = static_cast<u8>(LEFT_STICK_CENTER + (left_stick_state.y * LEFT_STICK_RADIUS));
const u8 x = static_cast<u8>(LEFT_STICK_CENTER + (left_stick_state.x * LEFT_STICK_RADIUS));
const u8 y = static_cast<u8>(LEFT_STICK_CENTER + (left_stick_state.y * LEFT_STICK_RADIUS));

classic_data.SetLeftStick({x, y});
}

// right stick
@@ -125,10 +127,7 @@ void Classic::Update()
const u8 x = static_cast<u8>(RIGHT_STICK_CENTER + (right_stick_data.x * RIGHT_STICK_RADIUS));
const u8 y = static_cast<u8>(RIGHT_STICK_CENTER + (right_stick_data.y * RIGHT_STICK_RADIUS));

classic_data.rx1 = x;
classic_data.rx2 = x >> 1;
classic_data.rx3 = x >> 3;
classic_data.ry = y;
classic_data.SetRightStick({x, y});
}

// triggers
@@ -139,18 +138,15 @@ void Classic::Update()
const u8 lt = static_cast<u8>(trigs[0] * TRIGGER_RANGE);
const u8 rt = static_cast<u8>(trigs[1] * TRIGGER_RANGE);

classic_data.lt1 = lt;
classic_data.lt2 = lt >> 3;
classic_data.rt = rt;
classic_data.SetLeftTrigger(lt);
classic_data.SetRightTrigger(rt);
}

// buttons
m_buttons->GetState(&classic_data.bt.hex, classic_button_bitmasks.data());
// dpad
m_dpad->GetState(&classic_data.bt.hex, classic_dpad_bitmasks.data());

// flip button bits
classic_data.bt.hex ^= 0xFFFF;
// buttons and dpad
u16 buttons = 0;
m_buttons->GetState(&buttons, classic_button_bitmasks.data());
m_dpad->GetState(&buttons, classic_dpad_bitmasks.data());
classic_data.SetButtons(buttons);

Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = classic_data;
}
@@ -4,6 +4,9 @@

#pragma once

#include <limits>

#include "Common/Matrix.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"

@@ -56,12 +59,59 @@ class Classic : public Extension1stParty
};
static_assert(sizeof(ButtonFormat) == 2, "Wrong size");

static constexpr int LEFT_STICK_BITS = 6;
static constexpr int RIGHT_STICK_BITS = 5;
static constexpr int TRIGGER_BITS = 5;

struct DataFormat
{
// lx/ly/lz; left joystick
// rx/ry/rz; right joystick
// lt; left trigger
// rt; left trigger
using StickType = Common::TVec2<u8>;
using LeftStickRawValue = ControllerEmu::RawValue<StickType, LEFT_STICK_BITS>;
using RightStickRawValue = ControllerEmu::RawValue<StickType, RIGHT_STICK_BITS>;

using TriggerType = u8;
using TriggerRawValue = ControllerEmu::RawValue<TriggerType, TRIGGER_BITS>;

// 6-bit X and Y values (0-63)
auto GetLeftStick() const { return LeftStickRawValue{StickType(lx, ly)}; };
void SetLeftStick(const StickType& value)
{
lx = value.x;
ly = value.y;
}
// 5-bit X and Y values (0-31)
auto GetRightStick() const
{
return RightStickRawValue{StickType(rx1 | rx2 << 1 | rx3 << 3, ry)};
};
void SetRightStick(const StickType& value)
{
rx1 = value.x & 0b1;
rx2 = (value.x >> 1) & 0b11;
rx3 = (value.x >> 3) & 0b11;
ry = value.y;
}
// 5-bit values (0-31)
auto GetLeftTrigger() const { return TriggerRawValue(lt1 | lt2 << 3); }
void SetLeftTrigger(TriggerType value)
{
lt1 = value & 0b111;
lt2 = (value >> 3) & 0b11;
}
auto GetRightTrigger() const { return TriggerRawValue(rt); }
void SetRightTrigger(TriggerType value) { rt = value; }

u16 GetButtons() const
{
// 0 == pressed.
return ~bt.hex;
}

void SetButtons(u16 value)
{
// 0 == pressed.
bt.hex = ~value;
}

u8 lx : 6; // byte 0
u8 rx3 : 2;
@@ -80,6 +130,53 @@ class Classic : public Extension1stParty
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");

static constexpr int CAL_STICK_BITS = 8;
static constexpr int CAL_TRIGGER_BITS = 8;

struct CalibrationData
{
using StickType = DataFormat::StickType;
using TriggerType = DataFormat::TriggerType;

using StickCalibration = ControllerEmu::ThreePointCalibration<StickType, CAL_STICK_BITS>;
using TriggerCalibration = ControllerEmu::TwoPointCalibration<TriggerType, CAL_TRIGGER_BITS>;

static constexpr TriggerType TRIGGER_MAX = std::numeric_limits<TriggerType>::max();

struct StickAxis
{
u8 max;
u8 min;
u8 center;
};

auto GetLeftStick() const
{
return StickCalibration{StickType{left_stick_x.min, left_stick_y.min},
StickType{left_stick_x.center, left_stick_y.center},
StickType{left_stick_x.max, left_stick_y.max}};
}
auto GetRightStick() const
{
return StickCalibration{StickType{right_stick_x.min, right_stick_y.min},
StickType{right_stick_x.center, right_stick_y.center},
StickType{right_stick_x.max, right_stick_y.max}};
}
auto GetLeftTrigger() const { return TriggerCalibration{left_trigger_zero, TRIGGER_MAX}; }
auto GetRightTrigger() const { return TriggerCalibration{right_trigger_zero, TRIGGER_MAX}; }

StickAxis left_stick_x;
StickAxis left_stick_y;
StickAxis right_stick_x;
StickAxis right_stick_y;

u8 left_trigger_zero;
u8 right_trigger_zero;

std::array<u8, 2> checksum;
};
static_assert(sizeof(CalibrationData) == 16, "Wrong size");

Classic();

void Update() override;
@@ -110,13 +207,10 @@ class Classic : public Extension1stParty

static constexpr u8 CAL_STICK_CENTER = 0x80;
static constexpr u8 CAL_STICK_RANGE = 0x7f;
static constexpr int CAL_STICK_BITS = 8;

static constexpr int LEFT_STICK_BITS = 6;
static constexpr u8 LEFT_STICK_CENTER = CAL_STICK_CENTER >> (CAL_STICK_BITS - LEFT_STICK_BITS);
static constexpr u8 LEFT_STICK_RADIUS = CAL_STICK_RANGE >> (CAL_STICK_BITS - LEFT_STICK_BITS);

static constexpr int RIGHT_STICK_BITS = 5;
static constexpr u8 RIGHT_STICK_CENTER = CAL_STICK_CENTER >> (CAL_STICK_BITS - RIGHT_STICK_BITS);
static constexpr u8 RIGHT_STICK_RADIUS = CAL_STICK_RANGE >> (CAL_STICK_BITS - RIGHT_STICK_BITS);

@@ -87,10 +87,9 @@ void Nunchuk::Update()
}

// buttons
m_buttons->GetState(&nc_data.bt.hex, nunchuk_button_bitmasks.data());

// flip the button bits :/
nc_data.bt.hex ^= 0x03;
u8 buttons = 0;
m_buttons->GetState(&buttons, nunchuk_button_bitmasks.data());
nc_data.SetButtons(buttons);

// Acceleration data:
EmulateSwing(&m_swing_state, m_swing, 1.f / ::Wiimote::UPDATE_FREQ);
@@ -109,13 +108,7 @@ void Nunchuk::Update()

// Calibration values are 8-bit but we want 10-bit precision, so << 2.
const auto acc = ConvertAccelData(accel, ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);

nc_data.ax = (acc.x >> 2) & 0xFF;
nc_data.ay = (acc.y >> 2) & 0xFF;
nc_data.az = (acc.z >> 2) & 0xFF;
nc_data.bt.acc_x_lsb = acc.x & 0x3;
nc_data.bt.acc_y_lsb = acc.y & 0x3;
nc_data.bt.acc_z_lsb = acc.z & 0x3;
nc_data.SetAccel(acc.value);

Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = nc_data;
}
@@ -51,24 +51,102 @@ class Nunchuk : public Extension1stParty
};
static_assert(sizeof(ButtonFormat) == 1, "Wrong size");

union DataFormat
struct DataFormat
{
struct
using StickType = Common::TVec2<u8>;
using StickRawValue = ControllerEmu::RawValue<StickType, 8>;

using AccelType = WiimoteCommon::AccelType;
using AccelData = WiimoteCommon::AccelData;

auto GetStick() const { return StickRawValue(StickType(jx, jy)); }

// Components have 10 bits of precision.
u16 GetAccelX() const { return ax << 2 | bt.acc_x_lsb; }
u16 GetAccelY() const { return ay << 2 | bt.acc_y_lsb; }
u16 GetAccelZ() const { return az << 2 | bt.acc_z_lsb; }
auto GetAccel() const { return AccelData{AccelType{GetAccelX(), GetAccelY(), GetAccelZ()}}; }

void SetAccelX(u16 val)
{
ax = val >> 2;
bt.acc_x_lsb = val & 0b11;
}
void SetAccelY(u16 val)
{
ay = val >> 2;
bt.acc_y_lsb = val & 0b11;
}
void SetAccelZ(u16 val)
{
az = val >> 2;
bt.acc_z_lsb = val & 0b11;
}
void SetAccel(const AccelType& accel)
{
SetAccelX(accel.x);
SetAccelY(accel.y);
SetAccelZ(accel.z);
}

u8 GetButtons() const
{
// 0 == pressed.
return ~bt.hex & (BUTTON_C | BUTTON_Z);
}
void SetButtons(u8 value)
{
// joystick x, y
u8 jx;
u8 jy;
// 0 == pressed.
bt.hex |= (BUTTON_C | BUTTON_Z);
bt.hex ^= value & (BUTTON_C | BUTTON_Z);
}

// joystick x, y
u8 jx;
u8 jy;

// accelerometer
u8 ax;
u8 ay;
u8 az;

// buttons + accelerometer LSBs
ButtonFormat bt;
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");

struct CalibrationData
{
using StickType = DataFormat::StickType;
using StickCalibration = ControllerEmu::ThreePointCalibration<StickType, 8>;

// accelerometer
u8 ax;
u8 ay;
u8 az;
using AccelType = WiimoteCommon::AccelType;
using AccelCalibration = ControllerEmu::TwoPointCalibration<AccelType, 10>;

// buttons + accelerometer LSBs
ButtonFormat bt;
struct Stick
{
u8 max;
u8 min;
u8 center;
};

auto GetStick() const
{
return StickCalibration(StickType{stick_x.min, stick_y.min},
StickType{stick_x.center, stick_y.center},
StickType{stick_x.max, stick_y.max});
}
auto GetAccel() const { return AccelCalibration(accel_zero_g.Get(), accel_one_g.Get()); }

WiimoteCommon::AccelCalibrationPoint accel_zero_g;
WiimoteCommon::AccelCalibrationPoint accel_one_g;

Stick stick_x;
Stick stick_y;

std::array<u8, 2> checksum;
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");
static_assert(sizeof(CalibrationData) == 16, "Wrong size");

Nunchuk();

@@ -16,7 +16,6 @@
#include "Common/Logging/Log.h"
#include "Common/MathUtil.h"
#include "Common/MsgHandler.h"
#include "Common/Swap.h"

#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/Dynamics.h"
@@ -56,6 +55,41 @@ struct MPI : mbedtls_mpi

namespace WiimoteEmu
{
Common::Vec3 MotionPlus::DataFormat::Data::GetAngularVelocity(const CalibrationBlocks& blocks) const
{
// Each axis may be using either slow or fast calibration.
const auto calibration = blocks.GetRelevantCalibration(is_slow);

// It seems M+ calibration data does not follow the "right-hand rule".
const auto sign_fix = Common::Vec3(-1, +1, -1);

// Adjust deg/s to rad/s.
constexpr auto scalar = float(MathUtil::TAU / 360);

return gyro.GetNormalizedValue(calibration.value) * sign_fix * Common::Vec3(calibration.degrees) *
scalar;
}

auto MotionPlus::CalibrationBlocks::GetRelevantCalibration(SlowType is_slow) const
-> RelevantCalibration
{
RelevantCalibration result;

const auto& pitch_block = is_slow.x ? slow : fast;
const auto& roll_block = is_slow.y ? slow : fast;
const auto& yaw_block = is_slow.z ? slow : fast;

result.value.max = {pitch_block.pitch_scale, roll_block.roll_scale, yaw_block.yaw_scale};

result.value.zero = {pitch_block.pitch_zero, roll_block.roll_zero, yaw_block.yaw_zero};

result.degrees.x = pitch_block.degrees_div_6 * 6;
result.degrees.y = roll_block.degrees_div_6 * 6;
result.degrees.z = yaw_block.degrees_div_6 * 6;

return result;
}

MotionPlus::MotionPlus() : Extension("MotionPlus")
{
}
@@ -82,35 +116,20 @@ void MotionPlus::Reset()
constexpr u16 ROLL_SCALE = CALIBRATION_ZERO + CALIBRATION_SCALE_OFFSET;
constexpr u16 PITCH_SCALE = CALIBRATION_ZERO - CALIBRATION_SCALE_OFFSET;

#pragma pack(push, 1)
struct CalibrationBlock
{
u16 yaw_zero = Common::swap16(CALIBRATION_ZERO);
u16 roll_zero = Common::swap16(CALIBRATION_ZERO);
u16 pitch_zero = Common::swap16(CALIBRATION_ZERO);
u16 yaw_scale = Common::swap16(YAW_SCALE);
u16 roll_scale = Common::swap16(ROLL_SCALE);
u16 pitch_scale = Common::swap16(PITCH_SCALE);
u8 degrees_div_6;
};

struct CalibrationData
{
CalibrationBlock fast;
u8 uid_1;
Common::BigEndianValue<u16> crc32_msb;
CalibrationBlock slow;
u8 uid_2;
Common::BigEndianValue<u16> crc32_lsb;
};
#pragma pack(pop)

static_assert(sizeof(CalibrationData) == 0x20, "Bad size.");

static_assert(CALIBRATION_FAST_SCALE_DEGREES % 6 == 0, "Value should be divisible by 6.");
static_assert(CALIBRATION_SLOW_SCALE_DEGREES % 6 == 0, "Value should be divisible by 6.");

CalibrationData calibration;
calibration.fast.yaw_zero = calibration.slow.yaw_zero = CALIBRATION_ZERO;
calibration.fast.roll_zero = calibration.slow.roll_zero = CALIBRATION_ZERO;
calibration.fast.pitch_zero = calibration.slow.pitch_zero = CALIBRATION_ZERO;

calibration.fast.yaw_scale = calibration.slow.yaw_scale = YAW_SCALE;
calibration.fast.roll_scale = calibration.slow.roll_scale = ROLL_SCALE;
calibration.fast.pitch_scale = calibration.slow.pitch_scale = PITCH_SCALE;

calibration.fast.degrees_div_6 = CALIBRATION_FAST_SCALE_DEGREES / 6;
calibration.slow.degrees_div_6 = CALIBRATION_SLOW_SCALE_DEGREES / 6;

@@ -120,17 +139,22 @@ void MotionPlus::Reset()
calibration.uid_1 = 0x0b;
calibration.uid_2 = 0xe9;

// Update checksum (crc32 of all data other than the checksum itself):
auto crc_result = crc32(0, Z_NULL, 0);
crc_result = crc32(crc_result, reinterpret_cast<const Bytef*>(&calibration), 0xe);
crc_result = crc32(crc_result, reinterpret_cast<const Bytef*>(&calibration) + 0x10, 0xe);

calibration.crc32_lsb = u16(crc_result);
calibration.crc32_msb = u16(crc_result >> 16);
calibration.UpdateChecksum();

Common::BitCastPtr<CalibrationData>(m_reg_data.calibration_data.data()) = calibration;
}

void MotionPlus::CalibrationData::UpdateChecksum()
{
// Checksum is crc32 of all data other than the checksum itself.
auto crc_result = crc32(0, Z_NULL, 0);
crc_result = crc32(crc_result, reinterpret_cast<const Bytef*>(this), 0xe);
crc_result = crc32(crc_result, reinterpret_cast<const Bytef*>(this) + 0x10, 0xe);

crc32_lsb = u16(crc_result);
crc32_msb = u16(crc_result >> 16);
}

void MotionPlus::DoState(PointerWrap& p)
{
p.Do(m_reg_data);
@@ -547,47 +571,10 @@ void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity)
break;
}
case PassthroughMode::Nunchuk:
{
if (EXT_AMT == m_i2c_bus.BusRead(EXT_SLAVE, EXT_ADDR, EXT_AMT, data))
{
// Passthrough data modifications via wiibrew.org
// Verified on real hardware via a test of every bit.
// Data passing through drops the least significant bit of the three accelerometer values.
// Bit 7 of byte 5 is moved to bit 6 of byte 5, overwriting it
Common::SetBit(data[5], 6, Common::ExtractBit(data[5], 7));
// Bit 0 of byte 4 is moved to bit 7 of byte 5
Common::SetBit(data[5], 7, Common::ExtractBit(data[4], 0));
// Bit 3 of byte 5 is moved to bit 4 of byte 5, overwriting it
Common::SetBit(data[5], 4, Common::ExtractBit(data[5], 3));
// Bit 1 of byte 5 is moved to bit 3 of byte 5
Common::SetBit(data[5], 3, Common::ExtractBit(data[5], 1));
// Bit 0 of byte 5 is moved to bit 2 of byte 5, overwriting it
Common::SetBit(data[5], 2, Common::ExtractBit(data[5], 0));

mplus_data = Common::BitCastPtr<DataFormat>(data);

// Bit 0 and 1 of byte 5 contain a M+ flag and a zero bit which is set below.
mplus_data.is_mp_data = false;
}
else
{
// Read failed (extension unplugged), Send M+ data instead
mplus_data.is_mp_data = true;
}
break;
}
case PassthroughMode::Classic:
{
if (EXT_AMT == m_i2c_bus.BusRead(EXT_SLAVE, EXT_ADDR, EXT_AMT, data))
{
// Passthrough data modifications via wiibrew.org
// Verified on real hardware via a test of every bit.
// Data passing through drops the least significant bit of the axes of the left (or only)
// joystick Bit 0 of Byte 4 is overwritten [by the 'extension_connected' flag] Bits 0 and
// 1 of Byte 5 are moved to bit 0 of Bytes 0 and 1, overwriting what was there before.
Common::SetBit(data[0], 0, Common::ExtractBit(data[5], 0));
Common::SetBit(data[1], 0, Common::ExtractBit(data[5], 1));

ApplyPassthroughModifications(GetPassthroughMode(), data);
mplus_data = Common::BitCastPtr<DataFormat>(data);

// Bit 0 and 1 of byte 5 contain a M+ flag and a zero bit which is set below.
@@ -599,7 +586,6 @@ void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity)
mplus_data.is_mp_data = true;
}
break;
}
default:
// This really shouldn't happen as the M+ deactivates on an invalid mode write.
ERROR_LOG(WIIMOTE, "M+ unknown passthrough-mode %d", int(GetPassthroughMode()));
@@ -664,4 +650,66 @@ void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity)
Common::BitCastPtr<DataFormat>(data) = mplus_data;
}

void MotionPlus::ApplyPassthroughModifications(PassthroughMode mode, u8* data)
{
if (mode == PassthroughMode::Nunchuk)
{
// Passthrough data modifications via wiibrew.org
// Verified on real hardware via a test of every bit.
// Data passing through drops the least significant bit of the three accelerometer values.
// Bit 7 of byte 5 is moved to bit 6 of byte 5, overwriting it
Common::SetBit<6>(data[5], Common::ExtractBit<7>(data[5]));
// Bit 0 of byte 4 is moved to bit 7 of byte 5
Common::SetBit<7>(data[5], Common::ExtractBit<0>(data[4]));
// Bit 3 of byte 5 is moved to bit 4 of byte 5, overwriting it
Common::SetBit<4>(data[5], Common::ExtractBit<3>(data[5]));
// Bit 1 of byte 5 is moved to bit 3 of byte 5
Common::SetBit<3>(data[5], Common::ExtractBit<1>(data[5]));
// Bit 0 of byte 5 is moved to bit 2 of byte 5, overwriting it
Common::SetBit<2>(data[5], Common::ExtractBit<0>(data[5]));
}
else if (mode == PassthroughMode::Classic)
{
// Passthrough data modifications via wiibrew.org
// Verified on real hardware via a test of every bit.
// Data passing through drops the least significant bit of the axes of the left (or only)
// joystick Bit 0 of Byte 4 is overwritten [by the 'extension_connected' flag] Bits 0 and
// 1 of Byte 5 are moved to bit 0 of Bytes 0 and 1, overwriting what was there before.
Common::SetBit<0>(data[0], Common::ExtractBit<0>(data[5]));
Common::SetBit<0>(data[1], Common::ExtractBit<1>(data[5]));
}
}

void MotionPlus::ReversePassthroughModifications(PassthroughMode mode, u8* data)
{
if (mode == PassthroughMode::Nunchuk)
{
// Undo M+'s "nunchuk passthrough" modifications.
Common::SetBit<0>(data[5], Common::ExtractBit<2>(data[5]));
Common::SetBit<1>(data[5], Common::ExtractBit<3>(data[5]));
Common::SetBit<3>(data[5], Common::ExtractBit<4>(data[5]));
Common::SetBit<0>(data[4], Common::ExtractBit<7>(data[5]));
Common::SetBit<7>(data[5], Common::ExtractBit<6>(data[5]));

// Set the overwritten bits from the next LSB.
Common::SetBit<2>(data[5], Common::ExtractBit<3>(data[5]));
Common::SetBit<4>(data[5], Common::ExtractBit<5>(data[5]));
Common::SetBit<6>(data[5], Common::ExtractBit<7>(data[5]));
}
else if (mode == PassthroughMode::Classic)
{
// Undo M+'s "classic controller passthrough" modifications.
Common::SetBit<0>(data[5], Common::ExtractBit<0>(data[0]));
Common::SetBit<1>(data[5], Common::ExtractBit<0>(data[1]));

// Set the overwritten bits from the next LSB.
Common::SetBit<0>(data[0], Common::ExtractBit<1>(data[0]));
Common::SetBit<0>(data[1], Common::ExtractBit<1>(data[1]));

// This is an overwritten unused button bit on the Classic Controller.
// Note it's a significant bit on the DJ Hero Turntable. (passthrough not feasible)
Common::SetBit<0>(data[4], 1);
}
}

} // namespace WiimoteEmu
@@ -7,17 +7,116 @@
#include <array>

#include "Common/CommonTypes.h"
#include "Common/Swap.h"
#include "Core/HW/WiimoteEmu/Dynamics.h"
#include "Core/HW/WiimoteEmu/ExtensionPort.h"
#include "Core/HW/WiimoteEmu/I2CBus.h"

namespace WiimoteEmu
{
struct AngularVelocity;

struct MotionPlus : public Extension
{
public:
enum class PassthroughMode : u8
{
// Note: `Disabled` is an M+ enabled with no passthrough. Maybe there is a better name.
Disabled = 0x04,
Nunchuk = 0x05,
Classic = 0x07,
};

#pragma pack(push, 1)
struct CalibrationBlock
{
Common::BigEndianValue<u16> yaw_zero;
Common::BigEndianValue<u16> roll_zero;
Common::BigEndianValue<u16> pitch_zero;
Common::BigEndianValue<u16> yaw_scale;
Common::BigEndianValue<u16> roll_scale;
Common::BigEndianValue<u16> pitch_scale;
u8 degrees_div_6;
};

struct CalibrationBlocks
{
using GyroType = Common::TVec3<u16>;
using SlowType = Common::TVec3<bool>;

struct RelevantCalibration
{
ControllerEmu::TwoPointCalibration<GyroType, 16> value;
Common::TVec3<u16> degrees;
};

// Each axis may be using either slow or fast calibration.
// This function builds calibration that is relevant for current data.
RelevantCalibration GetRelevantCalibration(SlowType is_slow) const;

CalibrationBlock fast;
CalibrationBlock slow;
};

struct CalibrationData
{
void UpdateChecksum();

CalibrationBlock fast;
u8 uid_1;
Common::BigEndianValue<u16> crc32_msb;
CalibrationBlock slow;
u8 uid_2;
Common::BigEndianValue<u16> crc32_lsb;
};
static_assert(sizeof(CalibrationData) == 0x20, "Wrong size");

struct DataFormat
{
using GyroType = CalibrationBlocks::GyroType;
using SlowType = CalibrationBlocks::SlowType;
using GyroRawValue = ControllerEmu::RawValue<GyroType, 14>;

struct Data
{
// Return radian/s following "right-hand rule" with given calibration blocks.
Common::Vec3 GetAngularVelocity(const CalibrationBlocks&) const;

GyroRawValue gyro;
SlowType is_slow;
};

auto GetData() const
{
return Data{
GyroRawValue{GyroType(pitch1 | pitch2 << 8, roll1 | roll2 << 8, yaw1 | yaw2 << 8)},
SlowType(pitch_slow, roll_slow, yaw_slow)};
}

// yaw1, roll1, pitch1: Bits 0-7
// yaw2, roll2, pitch2: Bits 8-13

u8 yaw1;
u8 roll1;
u8 pitch1;

u8 pitch_slow : 1;
u8 yaw_slow : 1;
u8 yaw2 : 6;

u8 extension_connected : 1;
u8 roll_slow : 1;
u8 roll2 : 6;

u8 zero : 1;
u8 is_mp_data : 1;
u8 pitch2 : 6;
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");
#pragma pack(pop)

static constexpr u8 INACTIVE_DEVICE_ADDR = 0x53;
static constexpr u8 ACTIVE_DEVICE_ADDR = 0x52;
static constexpr u8 PASSTHROUGH_MODE_OFFSET = 0xfe;

MotionPlus();

void Update() override;
@@ -29,6 +128,10 @@ struct MotionPlus : public Extension
// Vec3 is interpreted as radians/s about the x,y,z axes following the "right-hand rule".
void PrepareInput(const Common::Vec3& angular_velocity);

// Pointer to 6 bytes is expected.
static void ApplyPassthroughModifications(PassthroughMode, u8* data);
static void ReversePassthroughModifications(PassthroughMode, u8* data);

private:
enum class ChallengeState : u8
{
@@ -42,13 +145,6 @@ struct MotionPlus : public Extension
ParameterYReady = 0x1a,
};

enum class PassthroughMode : u8
{
Disabled = 0x04,
Nunchuk = 0x05,
Classic = 0x07,
};

enum class ActivationStatus
{
Inactive,
@@ -58,28 +154,6 @@ struct MotionPlus : public Extension
};

#pragma pack(push, 1)
struct DataFormat
{
// yaw1, roll1, pitch1: Bits 0-7
// yaw2, roll2, pitch2: Bits 8-13

u8 yaw1;
u8 roll1;
u8 pitch1;

u8 pitch_slow : 1;
u8 yaw_slow : 1;
u8 yaw2 : 6;

u8 extension_connected : 1;
u8 roll_slow : 1;
u8 roll2 : 6;

u8 zero : 1;
u8 is_mp_data : 1;
u8 pitch2 : 6;
};

struct Register
{
std::array<u8, 21> controller_data;
@@ -135,14 +209,8 @@ struct MotionPlus : public Extension
std::array<u8, 6> ext_identifier;
};
#pragma pack(pop)
static_assert(sizeof(DataFormat) == 6, "Wrong size");
static_assert(0x100 == sizeof(Register), "Wrong size");

static constexpr u8 INACTIVE_DEVICE_ADDR = 0x53;
static constexpr u8 ACTIVE_DEVICE_ADDR = 0x52;

static constexpr u8 PASSTHROUGH_MODE_OFFSET = 0xfe;

static constexpr int CALIBRATION_BITS = 16;

static constexpr u16 CALIBRATION_ZERO = 1 << (CALIBRATION_BITS - 1);
@@ -500,7 +500,7 @@ void Wiimote::SendDataReport()
if (rpt_builder.HasAccel())
{
// Calibration values are 8-bit but we want 10-bit precision, so << 2.
DataReportBuilder::AccelData accel =
AccelData accel =
ConvertAccelData(GetTotalAcceleration(), ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
rpt_builder.SetAccelData(accel);
}
@@ -257,12 +257,13 @@ @interface ConnectBT : NSObject
@implementation SearchBT
- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender
error:(IOReturn)error
aborted:(BOOL)aborted {
aborted:(BOOL)aborted
{
done = true;
}

- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender
device:(IOBluetoothDevice*)device {
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender device:(IOBluetoothDevice*)device
{
NOTICE_LOG(WIIMOTE, "Discovered Bluetooth device at %s: %s", [[device addressString] UTF8String],
[[device name] UTF8String]);

@@ -274,11 +275,12 @@ - (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender
@implementation ConnectBT
- (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel
data:(unsigned char*)data
length:(NSUInteger)length {
length:(NSUInteger)length
{
IOBluetoothDevice* device = [l2capChannel device];
WiimoteReal::WiimoteDarwin* wm = nullptr;

std::lock_guard<std::mutex> lk(WiimoteReal::g_wiimotes_mutex);
std::lock_guard lk(WiimoteReal::g_wiimotes_mutex);

for (int i = 0; i < MAX_WIIMOTES; i++)
{
@@ -314,11 +316,12 @@ - (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel
CFRunLoopStop(CFRunLoopGetCurrent());
}

- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel {
- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel
{
IOBluetoothDevice* device = [l2capChannel device];
WiimoteReal::WiimoteDarwin* wm = nullptr;

std::lock_guard<std::mutex> lk(WiimoteReal::g_wiimotes_mutex);
std::lock_guard lk(WiimoteReal::g_wiimotes_mutex);

for (int i = 0; i < MAX_WIIMOTES; i++)
{

Large diffs are not rendered by default.

@@ -65,6 +65,7 @@ class Wiimote
void Update();
bool CheckForButtonPress();

bool GetNextReport(Report* report);
Report& ProcessReadQueue();

void Read();
@@ -101,8 +102,16 @@ class Wiimote

void QueueReport(WiimoteCommon::OutputReportID rpt_id, const void* data, unsigned int size);

template <typename T>
void QueueReport(const T& report)
{
QueueReport(report.REPORT_ID, &report, sizeof(report));
}

int GetIndex() const;

void SetChannel(u16 channel);

protected:
Wiimote();

@@ -173,6 +182,7 @@ class WiimoteScanner

private:
void ThreadFunc();
void PoolThreadFunc();

std::vector<std::unique_ptr<WiimoteScannerBackend>> m_backends;
mutable std::mutex m_backends_mutex;
@@ -183,10 +193,13 @@ class WiimoteScanner
std::atomic<WiimoteScanMode> m_scan_mode{WiimoteScanMode::DO_NOT_SCAN};
};

extern std::mutex g_wiimotes_mutex;
// Mutex is recursive as ControllerInterface may call AddWiimoteToPool within ProcessWiimotePool.
extern std::recursive_mutex g_wiimotes_mutex;
extern WiimoteScanner g_wiimote_scanner;
extern std::unique_ptr<Wiimote> g_wiimotes[MAX_BBMOTES];

void AddWiimoteToPool(std::unique_ptr<Wiimote>);

void InterruptChannel(int wiimote_number, u16 channel_id, const void* data, u32 size);
void ControlChannel(int wiimote_number, u16 channel_id, const void* data, u32 size);
void Update(int wiimote_number);
@@ -202,4 +215,7 @@ void HandleWiimoteSourceChange(unsigned int wiimote_number);
void InitAdapterClass();
#endif

void HandleWiimotesInControllerInterfaceSettingChange();
void ProcessWiimotePool();

} // namespace WiimoteReal
@@ -678,12 +678,13 @@ static void SetWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt,

if (rpt.HasAccel())
{
DataReportBuilder::AccelData accel_data;
AccelData accel_data;
rpt.GetAccelData(&accel_data);

// FYI: This will only print partial data for interleaved reports.

display_str += fmt::format(" ACC:{},{},{}", accel_data.x, accel_data.y, accel_data.z);
display_str +=
fmt::format(" ACC:{},{},{}", accel_data.value.x, accel_data.value.y, accel_data.value.z);
}

if (rpt.HasIR())
@@ -707,9 +708,8 @@ static void SetWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt,
key.Decrypt((u8*)&nunchuk, 0, sizeof(nunchuk));
nunchuk.bt.hex = nunchuk.bt.hex ^ 0x3;

const std::string accel = fmt::format(
" N-ACC:{},{},{}", (nunchuk.ax << 2) | nunchuk.bt.acc_x_lsb,
(nunchuk.ay << 2) | nunchuk.bt.acc_y_lsb, (nunchuk.az << 2) | nunchuk.bt.acc_z_lsb);
const std::string accel = fmt::format(" N-ACC:{},{},{}", nunchuk.GetAccelX(),
nunchuk.GetAccelY(), nunchuk.GetAccelZ());

if (nunchuk.bt.c)
display_str += " C";
@@ -756,10 +756,14 @@ static void SetWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt,
if (cc.bt.home)
display_str += " HOME";

display_str += Analog1DToString(cc.lt1 | (cc.lt2 << 3), " L", 31);
display_str += Analog1DToString(cc.rt, " R", 31);
display_str += Analog2DToString(cc.lx, cc.ly, " ANA", 63);
display_str += Analog2DToString(cc.rx1 | (cc.rx2 << 1) | (cc.rx3 << 3), cc.ry, " R-ANA", 31);
display_str += Analog1DToString(cc.GetLeftTrigger().value, " L", 31);
display_str += Analog1DToString(cc.GetRightTrigger().value, " R", 31);

const auto left_stick = cc.GetLeftStick().value;
display_str += Analog2DToString(left_stick.x, left_stick.y, " ANA", 63);

const auto right_stick = cc.GetRightStick().value;
display_str += Analog2DToString(right_stick.x, right_stick.y, " R-ANA", 31);
}

std::lock_guard<std::mutex> guard(s_input_display_lock);
@@ -72,8 +72,6 @@ ControllersWindow::ControllersWindow(QWidget* parent) : QDialog(parent)
CreateMainLayout();
LoadSettings();
ConnectWidgets();

OnEmulationStateChanged(Core::GetState() != Core::State::Uninitialized);
}

void ControllersWindow::CreateGamecubeLayout()
@@ -157,6 +155,7 @@ void ControllersWindow::CreateWiimoteLayout()
m_wiimote_continuous_scanning = new QCheckBox(tr("Continuous Scanning"));
m_wiimote_real_balance_board = new QCheckBox(tr("Real Balance Board"));
m_wiimote_speaker_data = new QCheckBox(tr("Enable Speaker Data"));
m_wiimote_ciface = new QCheckBox(tr("Connect Wii Remotes for Emulated Controllers"));

m_wiimote_layout->setVerticalSpacing(7);
m_wiimote_layout->setColumnMinimumWidth(0, GetRadioButtonIndicatorWidth() -
@@ -192,12 +191,14 @@ void ControllersWindow::CreateWiimoteLayout()
m_wiimote_layout->addWidget(wm_button, wm_row, 3);
}

int continuous_scanning_row = m_wiimote_layout->rowCount();
m_wiimote_layout->addWidget(m_wiimote_continuous_scanning, continuous_scanning_row, 1, 1, 2);
m_wiimote_layout->addWidget(m_wiimote_refresh, continuous_scanning_row, 3);

m_wiimote_layout->addWidget(m_wiimote_real_balance_board, m_wiimote_layout->rowCount(), 1, 1, -1);
m_wiimote_layout->addWidget(m_wiimote_speaker_data, m_wiimote_layout->rowCount(), 1, 1, -1);

m_wiimote_layout->addWidget(m_wiimote_ciface, m_wiimote_layout->rowCount(), 0, 1, -1);

int continuous_scanning_row = m_wiimote_layout->rowCount();
m_wiimote_layout->addWidget(m_wiimote_continuous_scanning, continuous_scanning_row, 0, 1, 3);
m_wiimote_layout->addWidget(m_wiimote_refresh, continuous_scanning_row, 3);
}

void ControllersWindow::CreateCommonLayout()
@@ -232,10 +233,15 @@ void ControllersWindow::ConnectWidgets()
{
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
[=](Core::State state) { OnEmulationStateChanged(state != Core::State::Uninitialized); });
&ControllersWindow::UpdateDisabledWiimoteControls);

connect(m_wiimote_passthrough, &QRadioButton::toggled, this,
&ControllersWindow::OnWiimoteModeChanged);
connect(m_wiimote_ciface, &QCheckBox::toggled, this, &ControllersWindow::OnWiimoteModeChanged);
connect(m_wiimote_ciface, &QCheckBox::toggled, this,
&WiimoteReal::HandleWiimotesInControllerInterfaceSettingChange);
connect(m_wiimote_continuous_scanning, &QCheckBox::toggled, this,
&ControllersWindow::OnWiimoteModeChanged);

connect(m_common_bg_input, &QCheckBox::toggled, this, &ControllersWindow::SaveSettings);
connect(m_common_configure_controller_interface, &QPushButton::clicked, this,
@@ -259,7 +265,7 @@ void ControllersWindow::ConnectWidgets()
&ControllersWindow::SaveSettings);
connect(m_wiimote_boxes[i],
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
&ControllersWindow::OnWiimoteTypeChanged);
&ControllersWindow::OnWiimoteModeChanged);
connect(m_wiimote_buttons[i], &QPushButton::clicked, this,
&ControllersWindow::OnWiimoteConfigure);

@@ -273,45 +279,50 @@ void ControllersWindow::ConnectWidgets()
}
}

void ControllersWindow::OnWiimoteModeChanged(bool passthrough)
void ControllersWindow::OnWiimoteModeChanged()
{
SaveSettings();

m_wiimote_sync->setEnabled(passthrough);
m_wiimote_reset->setEnabled(passthrough);
// Make sure continuous scanning setting is applied.
WiimoteReal::Initialize(::Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES);

for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
const int index = m_wiimote_boxes[i]->currentIndex();
UpdateDisabledWiimoteControls();
}

if (i < 2)
m_wiimote_pt_labels[i]->setEnabled(passthrough);
void ControllersWindow::UpdateDisabledWiimoteControls()
{
const bool running = Core::GetState() != Core::State::Uninitialized;

m_wiimote_labels[i]->setEnabled(!passthrough);
m_wiimote_boxes[i]->setEnabled(!passthrough);
m_wiimote_buttons[i]->setEnabled(!passthrough && index != 0 && index != 2);
}
m_wiimote_emu->setEnabled(!running);
m_wiimote_passthrough->setEnabled(!running);

m_wiimote_refresh->setEnabled(!passthrough);
m_wiimote_real_balance_board->setEnabled(!passthrough);
m_wiimote_speaker_data->setEnabled(!passthrough);
m_wiimote_continuous_scanning->setEnabled(!passthrough);
}
const bool running_gc = running && !SConfig::GetInstance().bWii;
const bool enable_passthrough = m_wiimote_passthrough->isChecked() && !running_gc;
const bool enable_emu_bt = !m_wiimote_passthrough->isChecked() && !running_gc;

m_wiimote_sync->setEnabled(enable_passthrough);
m_wiimote_reset->setEnabled(enable_passthrough);

for (auto* pt_label : m_wiimote_pt_labels)
pt_label->setEnabled(enable_passthrough);

void ControllersWindow::OnWiimoteTypeChanged(int type)
{
const auto* box = static_cast<QComboBox*>(QObject::sender());
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
if (m_wiimote_boxes[i] == box)
{
const int index = box->currentIndex();
m_wiimote_buttons[i]->setEnabled(index != 0 && index != 2);
return;
}
m_wiimote_labels[i]->setEnabled(enable_emu_bt);
m_wiimote_boxes[i]->setEnabled(enable_emu_bt);

const bool is_emu_wiimote = m_wiimote_boxes[i]->currentIndex() == 1;
m_wiimote_buttons[i]->setEnabled(enable_emu_bt && is_emu_wiimote);
}

SaveSettings();
m_wiimote_real_balance_board->setEnabled(enable_emu_bt);
m_wiimote_speaker_data->setEnabled(enable_emu_bt);

const bool ciface_wiimotes = m_wiimote_ciface->isChecked();

m_wiimote_refresh->setEnabled((enable_emu_bt || ciface_wiimotes) &&
!m_wiimote_continuous_scanning->isChecked());
m_wiimote_continuous_scanning->setEnabled(enable_emu_bt || ciface_wiimotes);
}

void ControllersWindow::OnGCTypeChanged(int type)
@@ -375,30 +386,6 @@ void ControllersWindow::OnWiimoteRefreshPressed()
WiimoteReal::Refresh();
}

void ControllersWindow::OnEmulationStateChanged(bool running)
{
const bool passthrough = SConfig::GetInstance().m_bt_passthrough_enabled;

if (!SConfig::GetInstance().bWii)
{
m_wiimote_sync->setEnabled(!running && passthrough);
m_wiimote_reset->setEnabled(!running && passthrough);

for (size_t i = 0; i < m_wiimote_groups.size(); i++)
m_wiimote_boxes[i]->setEnabled(!running && !passthrough);
}

m_wiimote_emu->setEnabled(!running);
m_wiimote_passthrough->setEnabled(!running);

if (!SConfig::GetInstance().bWii)
{
m_wiimote_real_balance_board->setEnabled(!running && !passthrough);
m_wiimote_continuous_scanning->setEnabled(!running && !passthrough);
m_wiimote_speaker_data->setEnabled(!running && !passthrough);
}
}

void ControllersWindow::OnGCPadConfigure()
{
size_t index;
@@ -489,14 +476,12 @@ void ControllersWindow::LoadSettings()
m_gc_controller_boxes[i]->setCurrentIndex(*gc_index);
m_gc_buttons[i]->setEnabled(*gc_index != 0 && *gc_index != 6);
}

const WiimoteSource source = WiimoteCommon::GetSource(int(i));
m_wiimote_boxes[i]->setCurrentIndex(int(source));
m_wiimote_buttons[i]->setEnabled(source == WiimoteSource::Emulated);
m_wiimote_boxes[i]->setCurrentIndex(int(WiimoteCommon::GetSource(u32(i))));
}
m_wiimote_real_balance_board->setChecked(WiimoteCommon::GetSource(WIIMOTE_BALANCE_BOARD) ==
WiimoteSource::Real);
m_wiimote_speaker_data->setChecked(SConfig::GetInstance().m_WiimoteEnableSpeaker);
m_wiimote_ciface->setChecked(SConfig::GetInstance().connect_wiimotes_for_ciface);
m_wiimote_continuous_scanning->setChecked(SConfig::GetInstance().m_WiimoteContinuousScanning);

m_common_bg_input->setChecked(SConfig::GetInstance().m_BackgroundInput);
@@ -506,12 +491,13 @@ void ControllersWindow::LoadSettings()
else
m_wiimote_emu->setChecked(true);

OnWiimoteModeChanged(SConfig::GetInstance().m_bt_passthrough_enabled);
OnWiimoteModeChanged();
}

void ControllersWindow::SaveSettings()
{
SConfig::GetInstance().m_WiimoteEnableSpeaker = m_wiimote_speaker_data->isChecked();
SConfig::GetInstance().connect_wiimotes_for_ciface = m_wiimote_ciface->isChecked();
SConfig::GetInstance().m_WiimoteContinuousScanning = m_wiimote_continuous_scanning->isChecked();
SConfig::GetInstance().m_bt_passthrough_enabled = m_wiimote_passthrough->isChecked();
SConfig::GetInstance().m_BackgroundInput = m_common_bg_input->isChecked();
@@ -522,9 +508,8 @@ void ControllersWindow::SaveSettings()

for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
const auto source = WiimoteSource(m_wiimote_boxes[i]->currentIndex());
m_wiimote_buttons[i]->setEnabled(source == WiimoteSource::Emulated);
WiimoteCommon::SetSource(static_cast<u32>(i), source);
const int index = m_wiimote_boxes[i]->currentIndex();
WiimoteCommon::SetSource(u32(i), WiimoteSource(index));
}

UICommon::SaveWiimoteSources();
@@ -27,9 +27,8 @@ class ControllersWindow final : public QDialog
explicit ControllersWindow(QWidget* parent);

private:
void OnEmulationStateChanged(bool running);
void OnWiimoteModeChanged(bool passthrough);
void OnWiimoteTypeChanged(int state);
void OnWiimoteModeChanged();
void UpdateDisabledWiimoteControls();
void OnGCTypeChanged(int state);
void SaveSettings();
void OnBluetoothPassthroughSyncPressed();
@@ -72,6 +71,7 @@ class ControllersWindow final : public QDialog
QCheckBox* m_wiimote_continuous_scanning;
QCheckBox* m_wiimote_real_balance_board;
QCheckBox* m_wiimote_speaker_data;
QCheckBox* m_wiimote_ciface;
QPushButton* m_wiimote_refresh;

// Common
@@ -159,10 +159,12 @@ bool IsCalibrationDataSensible(const ControllerEmu::ReshapableInput::Calibration
// Even the GC controller's small range would pass this test.
constexpr double REASONABLE_AVERAGE_RADIUS = 0.6;

const double sum = std::accumulate(data.begin(), data.end(), 0.0);
const double mean = sum / data.size();
MathUtil::RunningVariance<ControlState> stats;

if (mean < REASONABLE_AVERAGE_RADIUS)
for (auto& x : data)
stats.Push(x);

if (stats.Mean() < REASONABLE_AVERAGE_RADIUS)
{
return false;
}
@@ -173,11 +175,7 @@ bool IsCalibrationDataSensible(const ControllerEmu::ReshapableInput::Calibration
// Approx. deviation of a square input gate, anything much more than that would be unusual.
constexpr double REASONABLE_DEVIATION = 0.14;

// Population standard deviation.
const double square_sum = std::inner_product(data.begin(), data.end(), data.begin(), 0.0);
const double standard_deviation = std::sqrt(square_sum / data.size() - mean * mean);

return standard_deviation < REASONABLE_DEVIATION;
return stats.StandardDeviation() < REASONABLE_DEVIATION;
}

// Used to test for a miscalibrated stick so the user can be informed.
@@ -754,33 +752,34 @@ void AccelerometerMappingIndicator::paintEvent(QPaintEvent*)
p.setBrush(Qt::NoBrush);
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);

// Red dot upright target.
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{0, SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
p.setPen(Qt::NoPen);

// Red dot.
const auto point = rotation * Common::Vec3{0, 0, SPHERE_INDICATOR_DIST};
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
{
p.setPen(Qt::NoPen);
p.setBrush(GetAdjustedInputColor());
p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}

// Blue dot target.
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
p.setBrush(Qt::NoBrush);
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);

// Blue dot.
const auto point2 = -point;
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
{
p.setPen(Qt::NoPen);
p.setBrush(Qt::blue);
p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}

p.setBrush(Qt::NoBrush);

// Red dot upright target.
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{0, SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);

// Blue dot target.
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);

// Only draw g-force text if acceleration data is present.
if (!accel_state.has_value())
return;
@@ -802,16 +801,18 @@ GyroMappingIndicator::GyroMappingIndicator(ControllerEmu::IMUGyroscope* group)
void GyroMappingIndicator::paintEvent(QPaintEvent*)
{
const auto gyro_state = m_gyro_group.GetState();
const auto raw_gyro_state = m_gyro_group.GetRawState();
const auto angular_velocity = gyro_state.value_or(Common::Vec3{});
const auto jitter = raw_gyro_state - m_previous_velocity;
m_previous_velocity = raw_gyro_state;

m_state *= Common::Matrix33::FromQuaternion(angular_velocity.x / -INDICATOR_UPDATE_FREQ / 2,
angular_velocity.y / INDICATOR_UPDATE_FREQ / 2,
angular_velocity.z / -INDICATOR_UPDATE_FREQ / 2, 1);
m_state *= WiimoteEmu::GetMatrixFromGyroscope(angular_velocity * Common::Vec3(-1, +1, -1) /
INDICATOR_UPDATE_FREQ);

// Reset orientation when stable for a bit:
constexpr u32 STABLE_RESET_STEPS = INDICATOR_UPDATE_FREQ;
// This works well with my DS4 but a potentially noisy device might not behave.
const bool is_stable = angular_velocity.Length() < MathUtil::TAU / 30;
// Consider device stable when data (with deadzone applied) is zero.
const bool is_stable = !angular_velocity.LengthSquared();

if (!is_stable)
m_stable_steps = 0;
@@ -839,60 +840,79 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);

// Deadzone.
if (const auto deadzone_value = m_gyro_group.GetDeadzone(); deadzone_value)
{
static constexpr auto DEADZONE_DRAW_SIZE = 1 - SPHERE_SIZE;
static constexpr auto DEADZONE_DRAW_BOTTOM = 1;

p.setPen(GetDeadZonePen());
p.setBrush(GetDeadZoneBrush());
p.scale(-1.0, 1.0);
p.drawRect(-scale, DEADZONE_DRAW_BOTTOM * scale, scale * 2, -scale * DEADZONE_DRAW_SIZE);
p.scale(-1.0, 1.0);

if (gyro_state.has_value())
{
const auto max_jitter =
std::max({std::abs(jitter.x), std::abs(jitter.y), std::abs(jitter.z)});
const auto jitter_line_y =
std::min(max_jitter / deadzone_value * DEADZONE_DRAW_SIZE - DEADZONE_DRAW_BOTTOM, 1.0);
p.setPen(QPen(GetRawInputColor(), INPUT_DOT_RADIUS));
p.drawLine(-scale, jitter_line_y * -scale, scale, jitter_line_y * -scale);

// Sphere background.
p.setPen(Qt::NoPen);
p.setBrush(GetBBoxBrush());
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);
}
}

// Sphere dots.
p.setPen(Qt::NoPen);
p.setBrush(GetRawInputColor());

GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&, this](const Common::Vec3& point) {
GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&](const Common::Vec3& point) {
const auto pt = rotation * point;

if (pt.y > 0)
p.drawEllipse(QPointF(pt.x, pt.z) * scale * SPHERE_SIZE, 0.5f, 0.5f);
});

// Sphere outline.
p.setPen(GetRawInputColor());
const auto outline_color = is_stable ?
(m_gyro_group.IsCalibrating() ? Qt::blue : GetRawInputColor()) :
GetAdjustedInputColor();
p.setPen(outline_color);
p.setBrush(Qt::NoBrush);
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);

// Red dot upright target.
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
p.setPen(Qt::NoPen);

// Red dot.
const auto point = rotation * Common::Vec3{0, 0, -SPHERE_INDICATOR_DIST};
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
{
p.setPen(Qt::NoPen);
p.setBrush(GetAdjustedInputColor());
p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}

// Blue dot target.
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
p.setBrush(Qt::NoBrush);
p.drawEllipse(QPointF{}, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);

// Blue dot.
const auto point2 = rotation * Common::Vec3{0, SPHERE_INDICATOR_DIST, 0};
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
{
p.setPen(Qt::NoPen);
p.setBrush(Qt::blue);
p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}

// Only draw text if data is present.
if (!gyro_state.has_value())
return;
p.setBrush(Qt::NoBrush);

// Angle of red dot from starting position.
const auto angle = std::acos(point.Normalized().Dot({0, 0, -1}));
// Red dot upright target.
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);

// Angle text:
p.setPen(GetTextColor());
p.drawText(QRectF(-2, 0, scale, scale), Qt::AlignBottom | Qt::AlignRight,
// i18n: "°" is the symbol for degrees (angular measurement).
QString::fromStdString(fmt::format("{:.2f} °", angle / MathUtil::TAU * 360)));
// Blue dot target.
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{}, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}

void MappingIndicator::DrawCalibration(QPainter& p, Common::DVec2 point)
@@ -101,6 +101,7 @@ class GyroMappingIndicator : public MappingIndicator
private:
ControllerEmu::IMUGyroscope& m_gyro_group;
Common::Matrix33 m_state;
Common::Vec3 m_previous_velocity = {};
u32 m_stable_steps = 0;
};

@@ -334,18 +334,20 @@ void WiiTASInputWindow::GetValues(DataReportBuilder& rpt, int ext,
DataReportBuilder::CoreData core;
rpt.GetCoreData(&core);

using EmuWiimote = WiimoteEmu::Wiimote;

u16& buttons = core.hex;
GetButton<u16>(m_a_button, buttons, WiimoteEmu::Wiimote::BUTTON_A);
GetButton<u16>(m_b_button, buttons, WiimoteEmu::Wiimote::BUTTON_B);
GetButton<u16>(m_1_button, buttons, WiimoteEmu::Wiimote::BUTTON_ONE);
GetButton<u16>(m_2_button, buttons, WiimoteEmu::Wiimote::BUTTON_TWO);
GetButton<u16>(m_plus_button, buttons, WiimoteEmu::Wiimote::BUTTON_PLUS);
GetButton<u16>(m_minus_button, buttons, WiimoteEmu::Wiimote::BUTTON_MINUS);
GetButton<u16>(m_home_button, buttons, WiimoteEmu::Wiimote::BUTTON_HOME);
GetButton<u16>(m_left_button, buttons, WiimoteEmu::Wiimote::PAD_LEFT);
GetButton<u16>(m_up_button, buttons, WiimoteEmu::Wiimote::PAD_UP);
GetButton<u16>(m_down_button, buttons, WiimoteEmu::Wiimote::PAD_DOWN);
GetButton<u16>(m_right_button, buttons, WiimoteEmu::Wiimote::PAD_RIGHT);
GetButton<u16>(m_a_button, buttons, EmuWiimote::BUTTON_A);
GetButton<u16>(m_b_button, buttons, EmuWiimote::BUTTON_B);
GetButton<u16>(m_1_button, buttons, EmuWiimote::BUTTON_ONE);
GetButton<u16>(m_2_button, buttons, EmuWiimote::BUTTON_TWO);
GetButton<u16>(m_plus_button, buttons, EmuWiimote::BUTTON_PLUS);
GetButton<u16>(m_minus_button, buttons, EmuWiimote::BUTTON_MINUS);
GetButton<u16>(m_home_button, buttons, EmuWiimote::BUTTON_HOME);
GetButton<u16>(m_left_button, buttons, EmuWiimote::PAD_LEFT);
GetButton<u16>(m_up_button, buttons, EmuWiimote::PAD_UP);
GetButton<u16>(m_down_button, buttons, EmuWiimote::PAD_DOWN);
GetButton<u16>(m_right_button, buttons, EmuWiimote::PAD_RIGHT);

rpt.SetCoreData(core);
}
@@ -354,12 +356,12 @@ void WiiTASInputWindow::GetValues(DataReportBuilder& rpt, int ext,
{
// FYI: Interleaved reports may behave funky as not all data is always available.

DataReportBuilder::AccelData accel;
AccelData accel;
rpt.GetAccelData(&accel);

GetSpinBoxU16(m_remote_orientation_x_value, accel.x);
GetSpinBoxU16(m_remote_orientation_y_value, accel.y);
GetSpinBoxU16(m_remote_orientation_z_value, accel.z);
GetSpinBoxU16(m_remote_orientation_x_value, accel.value.x);
GetSpinBoxU16(m_remote_orientation_y_value, accel.value.y);
GetSpinBoxU16(m_remote_orientation_z_value, accel.value.z);

rpt.SetAccelData(accel);
}
@@ -439,26 +441,16 @@ void WiiTASInputWindow::GetValues(DataReportBuilder& rpt, int ext,
GetSpinBoxU8(m_nunchuk_stick_x_value, nunchuk.jx);
GetSpinBoxU8(m_nunchuk_stick_y_value, nunchuk.jy);

u16 accel_x = nunchuk.ax << 2 & (nunchuk.bt.acc_x_lsb & 0b11);
u16 accel_y = nunchuk.ay << 2 & (nunchuk.bt.acc_y_lsb & 0b11);
u16 accel_z = nunchuk.az << 2 & (nunchuk.bt.acc_z_lsb & 0b11);

GetSpinBoxU16(m_nunchuk_orientation_x_value, accel_x);
GetSpinBoxU16(m_nunchuk_orientation_y_value, accel_y);
GetSpinBoxU16(m_nunchuk_orientation_z_value, accel_z);

nunchuk.ax = accel_x >> 2;
nunchuk.ay = accel_y >> 2;
nunchuk.az = accel_z >> 2;

nunchuk.bt.acc_x_lsb = accel_x & 0b11;
nunchuk.bt.acc_y_lsb = accel_y & 0b11;
nunchuk.bt.acc_z_lsb = accel_z & 0b11;
auto accel = nunchuk.GetAccel().value;
GetSpinBoxU16(m_nunchuk_orientation_x_value, accel.x);
GetSpinBoxU16(m_nunchuk_orientation_y_value, accel.y);
GetSpinBoxU16(m_nunchuk_orientation_z_value, accel.z);
nunchuk.SetAccel(accel);

nunchuk.bt.hex ^= 0b11;
GetButton<u8>(m_c_button, nunchuk.bt.hex, WiimoteEmu::Nunchuk::BUTTON_C);
GetButton<u8>(m_z_button, nunchuk.bt.hex, WiimoteEmu::Nunchuk::BUTTON_Z);
nunchuk.bt.hex ^= 0b11;
u8 bt = nunchuk.GetButtons();
GetButton<u8>(m_c_button, bt, WiimoteEmu::Nunchuk::BUTTON_C);
GetButton<u8>(m_z_button, bt, WiimoteEmu::Nunchuk::BUTTON_Z);
nunchuk.SetButtons(bt);

key.Encrypt(reinterpret_cast<u8*>(&nunchuk), 0, sizeof(nunchuk));
}
@@ -470,50 +462,41 @@ void WiiTASInputWindow::GetValues(DataReportBuilder& rpt, int ext,
auto& cc = *reinterpret_cast<WiimoteEmu::Classic::DataFormat*>(ext_data);
key.Decrypt(reinterpret_cast<u8*>(&cc), 0, sizeof(cc));

cc.bt.hex ^= 0xFFFF;
GetButton<u16>(m_classic_a_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_A);
GetButton<u16>(m_classic_b_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_B);
GetButton<u16>(m_classic_x_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_X);
GetButton<u16>(m_classic_y_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_Y);
GetButton<u16>(m_classic_plus_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_PLUS);
GetButton<u16>(m_classic_minus_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_MINUS);
GetButton<u16>(m_classic_l_button, cc.bt.hex, WiimoteEmu::Classic::TRIGGER_L);
GetButton<u16>(m_classic_r_button, cc.bt.hex, WiimoteEmu::Classic::TRIGGER_R);
GetButton<u16>(m_classic_zl_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_ZL);
GetButton<u16>(m_classic_zr_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_ZR);
GetButton<u16>(m_classic_home_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_HOME);
GetButton<u16>(m_classic_left_button, cc.bt.hex, WiimoteEmu::Classic::PAD_LEFT);
GetButton<u16>(m_classic_up_button, cc.bt.hex, WiimoteEmu::Classic::PAD_UP);
GetButton<u16>(m_classic_down_button, cc.bt.hex, WiimoteEmu::Classic::PAD_DOWN);
GetButton<u16>(m_classic_right_button, cc.bt.hex, WiimoteEmu::Classic::PAD_RIGHT);
cc.bt.hex ^= 0xFFFF;

u8 rx = (cc.rx1 & 0b1) & ((cc.rx2 & 0b11) << 1) & ((cc.rx3 & 0b11) << 3);
GetSpinBoxU8(m_classic_right_stick_x_value, rx);
cc.rx1 = rx & 0b1;
cc.rx2 = (rx >> 1) & 0b11;
cc.rx3 = (rx >> 3) & 0b11;

u8 ry = cc.ry;
GetSpinBoxU8(m_classic_right_stick_y_value, ry);
cc.ry = ry;

u8 lx = cc.lx;
GetSpinBoxU8(m_classic_left_stick_x_value, lx);
cc.lx = lx;

u8 ly = cc.ly;
GetSpinBoxU8(m_classic_left_stick_y_value, ly);
cc.ly = ly;

u8 rt = cc.rt;
u16 bt = cc.GetButtons();
GetButton<u16>(m_classic_a_button, bt, WiimoteEmu::Classic::BUTTON_A);
GetButton<u16>(m_classic_b_button, bt, WiimoteEmu::Classic::BUTTON_B);
GetButton<u16>(m_classic_x_button, bt, WiimoteEmu::Classic::BUTTON_X);
GetButton<u16>(m_classic_y_button, bt, WiimoteEmu::Classic::BUTTON_Y);
GetButton<u16>(m_classic_plus_button, bt, WiimoteEmu::Classic::BUTTON_PLUS);
GetButton<u16>(m_classic_minus_button, bt, WiimoteEmu::Classic::BUTTON_MINUS);
GetButton<u16>(m_classic_l_button, bt, WiimoteEmu::Classic::TRIGGER_L);
GetButton<u16>(m_classic_r_button, bt, WiimoteEmu::Classic::TRIGGER_R);
GetButton<u16>(m_classic_zl_button, bt, WiimoteEmu::Classic::BUTTON_ZL);
GetButton<u16>(m_classic_zr_button, bt, WiimoteEmu::Classic::BUTTON_ZR);
GetButton<u16>(m_classic_home_button, bt, WiimoteEmu::Classic::BUTTON_HOME);
GetButton<u16>(m_classic_left_button, bt, WiimoteEmu::Classic::PAD_LEFT);
GetButton<u16>(m_classic_up_button, bt, WiimoteEmu::Classic::PAD_UP);
GetButton<u16>(m_classic_down_button, bt, WiimoteEmu::Classic::PAD_DOWN);
GetButton<u16>(m_classic_right_button, bt, WiimoteEmu::Classic::PAD_RIGHT);
cc.SetButtons(bt);

auto right_stick = cc.GetRightStick().value;
GetSpinBoxU8(m_classic_right_stick_x_value, right_stick.x);
GetSpinBoxU8(m_classic_right_stick_y_value, right_stick.y);
cc.SetRightStick(right_stick);

auto left_stick = cc.GetLeftStick().value;
GetSpinBoxU8(m_classic_left_stick_x_value, left_stick.x);
GetSpinBoxU8(m_classic_left_stick_y_value, left_stick.y);
cc.SetLeftStick(left_stick);

u8 rt = cc.GetRightTrigger().value;
GetSpinBoxU8(m_right_trigger_value, rt);
cc.rt = rt;
cc.SetRightTrigger(rt);

u8 lt = (cc.lt1 & 0b111) & (cc.lt2 >> 3);
u8 lt = cc.GetLeftTrigger().value;
GetSpinBoxU8(m_left_trigger_value, lt);
cc.lt1 = lt & 0b111;
cc.lt2 = (lt >> 3) & 0b11;
cc.SetLeftTrigger(lt);

key.Encrypt(reinterpret_cast<u8*>(&cc), 0, sizeof(cc));
}
@@ -50,6 +50,8 @@ add_library(inputcommon
ControllerInterface/ControllerInterface.h
ControllerInterface/Device.cpp
ControllerInterface/Device.h
ControllerInterface/Wiimote/Wiimote.cpp
ControllerInterface/Wiimote/Wiimote.h
ControlReference/ControlReference.cpp
ControlReference/ControlReference.h
ControlReference/ExpressionParser.cpp
@@ -50,7 +50,7 @@ Cursor::Cursor(std::string name, std::string ui_name)
_trans("°"),
// i18n: Refers to emulated wii remote movements.
_trans("Total rotation about the yaw axis.")},
15, 0, 180);
15, 0, 360);

AddSetting(&m_pitch_setting,
// i18n: Refers to an amount of rotational movement about the "pitch" axis.
@@ -59,7 +59,7 @@ Cursor::Cursor(std::string name, std::string ui_name)
_trans("°"),
// i18n: Refers to emulated wii remote movements.
_trans("Total rotation about the pitch axis.")},
15, 0, 180);
15, 0, 360);

AddSetting(&m_relative_setting, {_trans("Relative Input")}, false);
AddSetting(&m_autohide_setting, {_trans("Auto-Hide")}, false);
@@ -7,13 +7,23 @@
#include <memory>

#include "Common/Common.h"
#include "Common/MathUtil.h"

#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/Control/Input.h"

namespace ControllerEmu
{
// Maximum period for calculating an average stable value.
// Just to prevent failures due to timer overflow.
static constexpr auto MAXIMUM_CALIBRATION_DURATION = std::chrono::hours(1);

// If calibration updates do not happen at this rate, restart calibration period.
// This prevents calibration across periods of no regular updates. (e.g. between game sessions)
// This is made slightly lower than the UI update frequency of 30.
static constexpr auto WORST_ACCEPTABLE_CALIBRATION_UPDATE_FREQUENCY = 25;

IMUGyroscope::IMUGyroscope(std::string name, std::string ui_name)
: ControlGroup(std::move(name), std::move(ui_name), GroupType::IMUGyroscope)
{
@@ -23,18 +33,130 @@ IMUGyroscope::IMUGyroscope(std::string name, std::string ui_name)
AddInput(Translate, _trans("Roll Right"));
AddInput(Translate, _trans("Yaw Left"));
AddInput(Translate, _trans("Yaw Right"));

AddSetting(&m_deadzone_setting,
{_trans("Dead Zone"),
// i18n: "°/s" is the symbol for degrees (angular measurement) divided by seconds.
_trans("°/s"),
// i18n: Refers to the dead-zone setting of gyroscope input.
_trans("Angular velocity to ignore.")},
2, 0, 180);

AddSetting(&m_calibration_period_setting,
{_trans("Calibration Period"),
// i18n: "s" is the symbol for seconds.
_trans("s"),
// i18n: Refers to the "Calibration" setting of gyroscope input.
_trans("Time period of stable input to trigger calibration. (zero to disable)")},
3, 0, 30);
}

void IMUGyroscope::RestartCalibration() const
{
m_calibration_period_start = Clock::now();
m_running_calibration.Clear();
}

void IMUGyroscope::UpdateCalibration(const StateData& state) const
{
const auto now = Clock::now();
const auto calibration_period = m_calibration_period_setting.GetValue();

// If calibration time is zero. User is choosing to not calibrate.
if (!calibration_period)
{
// Set calibration to zero.
m_calibration = {};
RestartCalibration();
return;
}

// If there is no running calibration a new gyro was just mapped or calibration was just enabled,
// apply the current state as calibration, it's often better than zeros.
if (!m_running_calibration.Count())
{
m_calibration = state;
}
else
{
const auto calibration_freq =
m_running_calibration.Count() /
std::chrono::duration_cast<std::chrono::duration<double>>(now - m_calibration_period_start)
.count();

const auto potential_calibration = m_running_calibration.Mean();
const auto current_difference = state - potential_calibration;
const auto deadzone = GetDeadzone();

// Check for required calibration update frequency
// and if current data is within deadzone distance of mean stable value.
if (calibration_freq < WORST_ACCEPTABLE_CALIBRATION_UPDATE_FREQUENCY ||
std::any_of(current_difference.data.begin(), current_difference.data.end(),
[&](auto c) { return std::abs(c) > deadzone; }))
{
RestartCalibration();
}
}

// Update running mean stable value.
m_running_calibration.Push(state);

// Apply calibration after configured time.
const auto calibration_duration = now - m_calibration_period_start;
if (calibration_duration >= std::chrono::duration<double>(calibration_period))
{
m_calibration = m_running_calibration.Mean();

if (calibration_duration >= MAXIMUM_CALIBRATION_DURATION)
{
RestartCalibration();
m_running_calibration.Push(m_calibration);
}
}
}

auto IMUGyroscope::GetRawState() const -> StateData
{
return StateData(controls[1]->GetState() - controls[0]->GetState(),
controls[2]->GetState() - controls[3]->GetState(),
controls[4]->GetState() - controls[5]->GetState());
}

std::optional<IMUGyroscope::StateData> IMUGyroscope::GetState() const
{
if (controls[0]->control_ref->BoundCount() == 0)
{
// Set calibration to zero.
m_calibration = {};
RestartCalibration();
return std::nullopt;
}

auto state = GetRawState();

// If the input gate is disabled, miscalibration to zero values would occur.
if (ControlReference::GetInputGate())
UpdateCalibration(state);

state -= m_calibration;

// Apply "deadzone".
for (auto& c : state.data)
c *= std::abs(c) > GetDeadzone();

StateData state;
state.x = (controls[1]->GetState() - controls[0]->GetState());
state.y = (controls[2]->GetState() - controls[3]->GetState());
state.z = (controls[4]->GetState() - controls[5]->GetState());
return state;
}

ControlState IMUGyroscope::GetDeadzone() const
{
return m_deadzone_setting.GetValue() / 360 * MathUtil::TAU;
}

bool IMUGyroscope::IsCalibrating() const
{
const auto calibration_period = m_calibration_period_setting.GetValue();
return calibration_period && (Clock::now() - m_calibration_period_start) >=
std::chrono::duration<double>(calibration_period);
}

} // namespace ControllerEmu
@@ -4,11 +4,14 @@

#pragma once

#include <chrono>
#include <optional>
#include <string>

#include "Common/MathUtil.h"
#include "Common/Matrix.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"

namespace ControllerEmu
{
@@ -19,6 +22,25 @@ class IMUGyroscope : public ControlGroup

IMUGyroscope(std::string name, std::string ui_name);

StateData GetRawState() const;
std::optional<StateData> GetState() const;

// Value is in rad/s.
ControlState GetDeadzone() const;

bool IsCalibrating() const;

private:
using Clock = std::chrono::steady_clock;

void RestartCalibration() const;
void UpdateCalibration(const StateData&) const;

SettingValue<double> m_deadzone_setting;
SettingValue<double> m_calibration_period_setting;

mutable StateData m_calibration = {};
mutable MathUtil::RunningMean<StateData> m_running_calibration;
mutable Clock::time_point m_calibration_period_start = Clock::now();
};
} // namespace ControllerEmu
@@ -11,6 +11,7 @@
#include <type_traits>
#include <vector>

#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/IniFile.h"
#include "InputCommon/ControlReference/ExpressionParser.h"
@@ -27,6 +28,106 @@ namespace ControllerEmu
{
class ControlGroup;

// Represents calibration data found on Wii Remotes + extensions with a zero and a max value.
// (e.g. accelerometer data)
// Bits of precision specified to handle common situation of differing precision in the actual data.
template <typename T, size_t Bits>
struct TwoPointCalibration
{
TwoPointCalibration() = default;
TwoPointCalibration(const T& zero_, const T& max_) : zero{zero_}, max{max_} {}

static constexpr size_t BITS_OF_PRECISION = Bits;

T zero;
T max;
};

// Represents calibration data with a min, zero, and max value. (e.g. joystick data)
template <typename T, size_t Bits>
struct ThreePointCalibration
{
ThreePointCalibration() = default;
ThreePointCalibration(const T& min_, const T& zero_, const T& max_)
: min{min_}, zero{zero_}, max{max_}
{
}

static constexpr size_t BITS_OF_PRECISION = Bits;

T min;
T zero;
T max;
};

// Represents a raw/uncalibrated N-dimensional value of input data. (e.g. Joystick X and Y)
// A normalized value can be calculated with a provided {Two,Three}PointCalibration.
// Values are adjusted with mismatched bits of precision.
// Underlying type may be an unsigned type or a a Common::TVecN<> of an unsigned type.
template <typename T, size_t Bits>
struct RawValue
{
RawValue() = default;
explicit RawValue(const T& value_) : value{value_} {}

static constexpr size_t BITS_OF_PRECISION = Bits;

T value;

template <typename OtherT, size_t OtherBits>
auto GetNormalizedValue(const TwoPointCalibration<OtherT, OtherBits>& calibration) const
{
const auto value_expansion =
std::max(0, int(calibration.BITS_OF_PRECISION) - int(BITS_OF_PRECISION));

const auto calibration_expansion =
std::max(0, int(BITS_OF_PRECISION) - int(calibration.BITS_OF_PRECISION));

const auto calibration_zero = ExpandValue(calibration.zero, calibration_expansion) * 1.f;
const auto calibration_max = ExpandValue(calibration.max, calibration_expansion) * 1.f;

// Multiplication by 1.f to floatify either a scalar or a Vec.
return (ExpandValue(value, value_expansion) * 1.f - calibration_zero) /
(calibration_max - calibration_zero);
}

template <typename OtherT, size_t OtherBits>
auto GetNormalizedValue(const ThreePointCalibration<OtherT, OtherBits>& calibration) const
{
const auto value_expansion =
std::max(0, int(calibration.BITS_OF_PRECISION) - int(BITS_OF_PRECISION));

const auto calibration_expansion =
std::max(0, int(BITS_OF_PRECISION) - int(calibration.BITS_OF_PRECISION));

const auto calibration_min = ExpandValue(calibration.min, calibration_expansion) * 1.f;
const auto calibration_zero = ExpandValue(calibration.zero, calibration_expansion) * 1.f;
const auto calibration_max = ExpandValue(calibration.max, calibration_expansion) * 1.f;

const auto use_max = calibration.zero < value;

// Multiplication by 1.f to floatify either a scalar or a Vec.
return (ExpandValue(value, value_expansion) * 1.f - calibration_zero) /
(use_max * 1.f * (calibration_max - calibration_zero) +
!use_max * 1.f * (calibration_zero - calibration_min));
}

template <typename OtherT>
static OtherT ExpandValue(OtherT value, size_t bits)
{
if constexpr (std::is_arithmetic_v<OtherT>)
{
return Common::ExpandValue(value, bits);
}
else
{
for (size_t i = 0; i != std::size(value.data); ++i)
value.data[i] = Common::ExpandValue(value.data[i], bits);
return value;
}
}
};

class EmulatedController
{
public:
@@ -7,6 +7,7 @@
#include <algorithm>

#include "Common/Logging/Log.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"

#ifdef CIFACE_USE_WIN32
#include "InputCommon/ControllerInterface/Win32/Win32.h"
@@ -93,7 +94,7 @@ void ControllerInterface::RefreshDevices()
return;

{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
m_devices.clear();
}

@@ -132,6 +133,8 @@ void ControllerInterface::RefreshDevices()
ciface::DualShockUDPClient::PopulateDevices();
#endif

WiimoteReal::ProcessWiimotePool();

m_is_populating_devices = false;
InvokeDevicesChangedCallbacks();
}
@@ -146,7 +149,7 @@ void ControllerInterface::Shutdown()
m_is_init = false;

{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);

for (const auto& d : m_devices)
{
@@ -193,7 +196,7 @@ void ControllerInterface::AddDevice(std::shared_ptr<ciface::Core::Device> device
return;

{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);

const auto is_id_in_use = [&device, this](int id) {
return std::any_of(m_devices.begin(), m_devices.end(), [&device, &id](const auto& d) {
@@ -229,7 +232,7 @@ void ControllerInterface::AddDevice(std::shared_ptr<ciface::Core::Device> device
void ControllerInterface::RemoveDevice(std::function<bool(const ciface::Core::Device*)> callback)
{
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
auto it = std::remove_if(m_devices.begin(), m_devices.end(), [&callback](const auto& dev) {
if (callback(dev.get()))
{
@@ -251,7 +254,7 @@ void ControllerInterface::UpdateInput()
// Don't block the UI or CPU thread (to avoid a short but noticeable frame drop)
if (m_devices_mutex.try_lock())
{
std::lock_guard<std::mutex> lk(m_devices_mutex, std::adopt_lock);
std::lock_guard lk(m_devices_mutex, std::adopt_lock);
for (const auto& d : m_devices)
d->UpdateInput();
}
@@ -180,7 +180,7 @@ bool DeviceQualifier::operator!=(const DeviceQualifier& devq) const

std::shared_ptr<Device> DeviceContainer::FindDevice(const DeviceQualifier& devq) const
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
for (const auto& d : m_devices)
{
if (devq == d.get())
@@ -192,7 +192,7 @@ std::shared_ptr<Device> DeviceContainer::FindDevice(const DeviceQualifier& devq)

std::vector<std::string> DeviceContainer::GetAllDeviceStrings() const
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);

std::vector<std::string> device_strings;
DeviceQualifier device_qualifier;
@@ -208,7 +208,7 @@ std::vector<std::string> DeviceContainer::GetAllDeviceStrings() const

std::string DeviceContainer::GetDefaultDeviceString() const
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
if (m_devices.empty())
return "";

@@ -226,7 +226,7 @@ Device::Input* DeviceContainer::FindInput(std::string_view name, const Device* d
return inp;
}

std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
for (const auto& d : m_devices)
{
Device::Input* const i = d->FindInput(name);
@@ -198,7 +198,7 @@ class DeviceContainer
DetectInput(u32 wait_ms, const std::vector<std::string>& device_strings) const;

protected:
mutable std::mutex m_devices_mutex;
mutable std::recursive_mutex m_devices_mutex;
std::vector<std::shared_ptr<Device>> m_devices;
};
} // namespace Core