8 changed files with 798 additions and 100 deletions
-
4src/input_common/CMakeLists.txt
-
47src/input_common/drivers/joycon.cpp
-
100src/input_common/helpers/joycon_driver.cpp
-
23src/input_common/helpers/joycon_driver.h
-
315src/input_common/helpers/joycon_protocol/poller.cpp
-
77src/input_common/helpers/joycon_protocol/poller.h
-
299src/input_common/helpers/joycon_protocol/rumble.cpp
-
33src/input_common/helpers/joycon_protocol/rumble.h
@ -0,0 +1,315 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
|||
#include "common/logging/log.h"
|
|||
#include "input_common/helpers/joycon_protocol/poller.h"
|
|||
|
|||
namespace InputCommon::Joycon { |
|||
|
|||
JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_, |
|||
JoyStickCalibration right_stick_calibration_, |
|||
MotionCalibration motion_calibration_) |
|||
: device_type{device_type_}, left_stick_calibration{left_stick_calibration_}, |
|||
right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {} |
|||
|
|||
void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) { |
|||
callbacks = std::move(callbacks_); |
|||
} |
|||
|
|||
void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status) { |
|||
InputReportActive data{}; |
|||
memcpy(&data, buffer.data(), sizeof(InputReportActive)); |
|||
|
|||
switch (device_type) { |
|||
case Joycon::ControllerType::Left: |
|||
UpdateActiveLeftPadInput(data, motion_status); |
|||
break; |
|||
case Joycon::ControllerType::Right: |
|||
UpdateActiveRightPadInput(data, motion_status); |
|||
break; |
|||
case Joycon::ControllerType::Pro: |
|||
UpdateActiveProPadInput(data, motion_status); |
|||
break; |
|||
case Joycon::ControllerType::Grip: |
|||
case Joycon::ControllerType::Dual: |
|||
case Joycon::ControllerType::None: |
|||
break; |
|||
} |
|||
|
|||
callbacks.on_battery_data(data.battery_status); |
|||
} |
|||
|
|||
void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) { |
|||
InputReportPassive data{}; |
|||
memcpy(&data, buffer.data(), sizeof(InputReportPassive)); |
|||
|
|||
switch (device_type) { |
|||
case Joycon::ControllerType::Left: |
|||
UpdatePasiveLeftPadInput(data); |
|||
break; |
|||
case Joycon::ControllerType::Right: |
|||
UpdatePasiveRightPadInput(data); |
|||
break; |
|||
case Joycon::ControllerType::Pro: |
|||
UpdatePasiveProPadInput(data); |
|||
break; |
|||
case Joycon::ControllerType::Grip: |
|||
case Joycon::ControllerType::Dual: |
|||
case Joycon::ControllerType::None: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) { |
|||
// This mode is compatible with the active mode
|
|||
ReadActiveMode(buffer, motion_status); |
|||
} |
|||
|
|||
void JoyconPoller::UpdateColor(const Color& color) { |
|||
callbacks.on_color_data(color); |
|||
} |
|||
|
|||
void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input, |
|||
const MotionStatus& motion_status) { |
|||
static constexpr std::array<Joycon::PadButton, 11> left_buttons{ |
|||
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right, |
|||
Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR, |
|||
Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus, |
|||
Joycon::PadButton::Capture, Joycon::PadButton::StickL, |
|||
}; |
|||
|
|||
const u32 raw_button = |
|||
static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16)); |
|||
for (std::size_t i = 0; i < left_buttons.size(); ++i) { |
|||
const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0; |
|||
const int button = static_cast<int>(left_buttons[i]); |
|||
callbacks.on_button_data(button, button_status); |
|||
} |
|||
|
|||
const u16 raw_left_axis_x = |
|||
static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8)); |
|||
const u16 raw_left_axis_y = |
|||
static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4)); |
|||
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x); |
|||
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y); |
|||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x); |
|||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y); |
|||
|
|||
if (motion_status.is_enabled) { |
|||
auto left_motion = GetMotionInput(input, motion_status); |
|||
// Rotate motion axis to the correct direction
|
|||
left_motion.accel_y = -left_motion.accel_y; |
|||
left_motion.accel_z = -left_motion.accel_z; |
|||
left_motion.gyro_x = -left_motion.gyro_x; |
|||
callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion); |
|||
} |
|||
} |
|||
|
|||
void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input, |
|||
const MotionStatus& motion_status) { |
|||
static constexpr std::array<Joycon::PadButton, 11> right_buttons{ |
|||
Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B, |
|||
Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR, |
|||
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus, |
|||
Joycon::PadButton::Home, Joycon::PadButton::StickR, |
|||
}; |
|||
|
|||
const u32 raw_button = |
|||
static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16)); |
|||
for (std::size_t i = 0; i < right_buttons.size(); ++i) { |
|||
const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0; |
|||
const int button = static_cast<int>(right_buttons[i]); |
|||
callbacks.on_button_data(button, button_status); |
|||
} |
|||
|
|||
const u16 raw_right_axis_x = |
|||
static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8)); |
|||
const u16 raw_right_axis_y = |
|||
static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4)); |
|||
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x); |
|||
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y); |
|||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x); |
|||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y); |
|||
|
|||
if (motion_status.is_enabled) { |
|||
auto right_motion = GetMotionInput(input, motion_status); |
|||
// Rotate motion axis to the correct direction
|
|||
right_motion.accel_x = -right_motion.accel_x; |
|||
right_motion.accel_y = -right_motion.accel_y; |
|||
right_motion.gyro_z = -right_motion.gyro_z; |
|||
callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion); |
|||
} |
|||
} |
|||
|
|||
void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input, |
|||
const MotionStatus& motion_status) { |
|||
static constexpr std::array<Joycon::PadButton, 18> pro_buttons{ |
|||
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right, |
|||
Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL, |
|||
Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y, |
|||
Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A, |
|||
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus, |
|||
Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR, |
|||
}; |
|||
|
|||
const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) | |
|||
(input.button_input[1] << 16)); |
|||
for (std::size_t i = 0; i < pro_buttons.size(); ++i) { |
|||
const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0; |
|||
const int button = static_cast<int>(pro_buttons[i]); |
|||
callbacks.on_button_data(button, button_status); |
|||
} |
|||
|
|||
const u16 raw_left_axis_x = |
|||
static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8)); |
|||
const u16 raw_left_axis_y = |
|||
static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4)); |
|||
const u16 raw_right_axis_x = |
|||
static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8)); |
|||
const u16 raw_right_axis_y = |
|||
static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4)); |
|||
|
|||
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x); |
|||
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y); |
|||
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x); |
|||
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y); |
|||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x); |
|||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y); |
|||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x); |
|||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y); |
|||
|
|||
if (motion_status.is_enabled) { |
|||
auto pro_motion = GetMotionInput(input, motion_status); |
|||
pro_motion.gyro_x = -pro_motion.gyro_x; |
|||
pro_motion.accel_y = -pro_motion.accel_y; |
|||
pro_motion.accel_z = -pro_motion.accel_z; |
|||
callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion); |
|||
callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion); |
|||
} |
|||
} |
|||
|
|||
void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) { |
|||
static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{ |
|||
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X, |
|||
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y, |
|||
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR, |
|||
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR, |
|||
Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture, |
|||
Joycon::PasivePadButton::StickL, |
|||
}; |
|||
|
|||
for (std::size_t i = 0; i < left_buttons.size(); ++i) { |
|||
const bool button_status = (input.button_input & static_cast<u32>(left_buttons[i])) != 0; |
|||
const int button = static_cast<int>(left_buttons[i]); |
|||
callbacks.on_button_data(button, button_status); |
|||
} |
|||
} |
|||
|
|||
void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) { |
|||
static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{ |
|||
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X, |
|||
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y, |
|||
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR, |
|||
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR, |
|||
Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home, |
|||
Joycon::PasivePadButton::StickR, |
|||
}; |
|||
|
|||
for (std::size_t i = 0; i < right_buttons.size(); ++i) { |
|||
const bool button_status = (input.button_input & static_cast<u32>(right_buttons[i])) != 0; |
|||
const int button = static_cast<int>(right_buttons[i]); |
|||
callbacks.on_button_data(button, button_status); |
|||
} |
|||
} |
|||
|
|||
void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) { |
|||
static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{ |
|||
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X, |
|||
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y, |
|||
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR, |
|||
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR, |
|||
Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus, |
|||
Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home, |
|||
Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR, |
|||
}; |
|||
|
|||
for (std::size_t i = 0; i < pro_buttons.size(); ++i) { |
|||
const bool button_status = (input.button_input & static_cast<u32>(pro_buttons[i])) != 0; |
|||
const int button = static_cast<int>(pro_buttons[i]); |
|||
callbacks.on_button_data(button, button_status); |
|||
} |
|||
} |
|||
|
|||
f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const { |
|||
const f32 value = static_cast<f32>(raw_value - calibration.center); |
|||
if (value > 0.0f) { |
|||
return value / calibration.max; |
|||
} |
|||
return value / calibration.min; |
|||
} |
|||
|
|||
f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal, |
|||
AccelerometerSensitivity sensitivity) const { |
|||
const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4; |
|||
switch (sensitivity) { |
|||
case Joycon::AccelerometerSensitivity::G2: |
|||
return value / 4.0f; |
|||
case Joycon::AccelerometerSensitivity::G4: |
|||
return value / 2.0f; |
|||
case Joycon::AccelerometerSensitivity::G8: |
|||
return value; |
|||
case Joycon::AccelerometerSensitivity::G16: |
|||
return value * 2.0f; |
|||
} |
|||
return value; |
|||
} |
|||
|
|||
f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal, |
|||
GyroSensitivity sensitivity) const { |
|||
const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f; |
|||
switch (sensitivity) { |
|||
case Joycon::GyroSensitivity::DPS250: |
|||
return value / 8.0f; |
|||
case Joycon::GyroSensitivity::DPS500: |
|||
return value / 4.0f; |
|||
case Joycon::GyroSensitivity::DPS1000: |
|||
return value / 2.0f; |
|||
case Joycon::GyroSensitivity::DPS2000: |
|||
return value; |
|||
} |
|||
return value; |
|||
} |
|||
|
|||
s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis, |
|||
const InputReportActive& input) const { |
|||
return input.motion_input[(sensor * 3) + axis]; |
|||
} |
|||
|
|||
MotionData JoyconPoller::GetMotionInput(const InputReportActive& input, |
|||
const MotionStatus& motion_status) const { |
|||
MotionData motion{}; |
|||
const auto& accel_cal = motion_calibration.accelerometer; |
|||
const auto& gyro_cal = motion_calibration.gyro; |
|||
const s16 raw_accel_x = input.motion_input[1]; |
|||
const s16 raw_accel_y = input.motion_input[0]; |
|||
const s16 raw_accel_z = input.motion_input[2]; |
|||
const s16 raw_gyro_x = input.motion_input[4]; |
|||
const s16 raw_gyro_y = input.motion_input[3]; |
|||
const s16 raw_gyro_z = input.motion_input[5]; |
|||
|
|||
motion.delta_timestamp = motion_status.delta_time; |
|||
motion.accel_x = |
|||
GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity); |
|||
motion.accel_y = |
|||
GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity); |
|||
motion.accel_z = |
|||
GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity); |
|||
motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity); |
|||
motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity); |
|||
motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity); |
|||
|
|||
// TODO(German77): Return all three samples data
|
|||
return motion; |
|||
} |
|||
|
|||
} // namespace InputCommon::Joycon
|
|||
@ -0,0 +1,77 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse |
|||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c |
|||
// https://github.com/CTCaer/jc_toolkit |
|||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering |
|||
|
|||
#pragma once |
|||
|
|||
#include <functional> |
|||
#include <span> |
|||
|
|||
#include "input_common/helpers/joycon_protocol/joycon_types.h" |
|||
|
|||
namespace InputCommon::Joycon { |
|||
|
|||
// Handles input packages and triggers the corresponding input events |
|||
class JoyconPoller { |
|||
public: |
|||
JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_, |
|||
JoyStickCalibration right_stick_calibration_, |
|||
MotionCalibration motion_calibration_); |
|||
|
|||
void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_); |
|||
|
|||
/// Handles data from passive packages |
|||
void ReadPassiveMode(std::span<u8> buffer); |
|||
|
|||
/// Handles data from active packages |
|||
void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status); |
|||
|
|||
/// Handles data from nfc or ir packages |
|||
void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status); |
|||
|
|||
void UpdateColor(const Color& color); |
|||
|
|||
private: |
|||
void UpdateActiveLeftPadInput(const InputReportActive& input, |
|||
const MotionStatus& motion_status); |
|||
void UpdateActiveRightPadInput(const InputReportActive& input, |
|||
const MotionStatus& motion_status); |
|||
void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status); |
|||
|
|||
void UpdatePasiveLeftPadInput(const InputReportPassive& buffer); |
|||
void UpdatePasiveRightPadInput(const InputReportPassive& buffer); |
|||
void UpdatePasiveProPadInput(const InputReportPassive& buffer); |
|||
|
|||
/// Returns a calibrated joystick axis from raw axis data |
|||
f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const; |
|||
|
|||
/// Returns a calibrated accelerometer axis from raw motion data |
|||
f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal, |
|||
AccelerometerSensitivity sensitivity) const; |
|||
|
|||
/// Returns a calibrated gyro axis from raw motion data |
|||
f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal, |
|||
GyroSensitivity sensitivity) const; |
|||
|
|||
/// Returns a raw motion value from a buffer |
|||
s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const; |
|||
|
|||
/// Returns motion data from a buffer |
|||
MotionData GetMotionInput(const InputReportActive& input, |
|||
const MotionStatus& motion_status) const; |
|||
|
|||
ControllerType device_type{}; |
|||
|
|||
// Device calibration |
|||
JoyStickCalibration left_stick_calibration{}; |
|||
JoyStickCalibration right_stick_calibration{}; |
|||
MotionCalibration motion_calibration{}; |
|||
|
|||
Joycon::JoyconCallbacks callbacks{}; |
|||
}; |
|||
|
|||
} // namespace InputCommon::Joycon |
|||
@ -0,0 +1,299 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
|||
#include "common/logging/log.h"
|
|||
#include "input_common/helpers/joycon_protocol/rumble.h"
|
|||
|
|||
namespace InputCommon::Joycon { |
|||
|
|||
RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle) |
|||
: JoyconCommonProtocol(handle) {} |
|||
|
|||
DriverResult RumbleProtocol::EnableRumble(bool is_enabled) { |
|||
LOG_DEBUG(Input, "Enable Rumble"); |
|||
const std::vector<u8> buffer{static_cast<u8>(is_enabled ? 1 : 0)}; |
|||
std::vector<u8> output; |
|||
SetBlocking(); |
|||
const auto result = SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer, output); |
|||
SetNonBlocking(); |
|||
return result; |
|||
} |
|||
|
|||
DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) { |
|||
std::vector<u8> buffer(sizeof(DefaultVibrationBuffer)); |
|||
|
|||
if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) { |
|||
return SendVibrationReport(DefaultVibrationBuffer); |
|||
} |
|||
|
|||
// Protect joycons from damage from strong vibrations
|
|||
const f32 clamp_amplitude = |
|||
1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude); |
|||
|
|||
const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency); |
|||
const u8 encoded_high_amplitude = |
|||
EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude); |
|||
const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency); |
|||
const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude); |
|||
|
|||
buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF); |
|||
buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01)); |
|||
buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80)); |
|||
buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF); |
|||
|
|||
// Duplicate rumble for now
|
|||
buffer[4] = buffer[0]; |
|||
buffer[5] = buffer[1]; |
|||
buffer[6] = buffer[2]; |
|||
buffer[7] = buffer[3]; |
|||
|
|||
return SendVibrationReport(buffer); |
|||
} |
|||
|
|||
u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const { |
|||
const u8 new_frequency = |
|||
static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f)); |
|||
return static_cast<u16>((new_frequency - 0x60) * 4); |
|||
} |
|||
|
|||
u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const { |
|||
const u8 new_frequency = |
|||
static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f)); |
|||
return static_cast<u8>(new_frequency - 0x40); |
|||
} |
|||
|
|||
u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const { |
|||
/* More information about these values can be found here:
|
|||
* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
|
|||
*/ |
|||
constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{ |
|||
std::pair<f32, int>{0.0f, 0x0}, |
|||
{0.01f, 0x2}, |
|||
{0.012f, 0x4}, |
|||
{0.014f, 0x6}, |
|||
{0.017f, 0x8}, |
|||
{0.02f, 0x0a}, |
|||
{0.024f, 0x0c}, |
|||
{0.028f, 0x0e}, |
|||
{0.033f, 0x10}, |
|||
{0.04f, 0x12}, |
|||
{0.047f, 0x14}, |
|||
{0.056f, 0x16}, |
|||
{0.067f, 0x18}, |
|||
{0.08f, 0x1a}, |
|||
{0.095f, 0x1c}, |
|||
{0.112f, 0x1e}, |
|||
{0.117f, 0x20}, |
|||
{0.123f, 0x22}, |
|||
{0.128f, 0x24}, |
|||
{0.134f, 0x26}, |
|||
{0.14f, 0x28}, |
|||
{0.146f, 0x2a}, |
|||
{0.152f, 0x2c}, |
|||
{0.159f, 0x2e}, |
|||
{0.166f, 0x30}, |
|||
{0.173f, 0x32}, |
|||
{0.181f, 0x34}, |
|||
{0.189f, 0x36}, |
|||
{0.198f, 0x38}, |
|||
{0.206f, 0x3a}, |
|||
{0.215f, 0x3c}, |
|||
{0.225f, 0x3e}, |
|||
{0.23f, 0x40}, |
|||
{0.235f, 0x42}, |
|||
{0.24f, 0x44}, |
|||
{0.245f, 0x46}, |
|||
{0.251f, 0x48}, |
|||
{0.256f, 0x4a}, |
|||
{0.262f, 0x4c}, |
|||
{0.268f, 0x4e}, |
|||
{0.273f, 0x50}, |
|||
{0.279f, 0x52}, |
|||
{0.286f, 0x54}, |
|||
{0.292f, 0x56}, |
|||
{0.298f, 0x58}, |
|||
{0.305f, 0x5a}, |
|||
{0.311f, 0x5c}, |
|||
{0.318f, 0x5e}, |
|||
{0.325f, 0x60}, |
|||
{0.332f, 0x62}, |
|||
{0.34f, 0x64}, |
|||
{0.347f, 0x66}, |
|||
{0.355f, 0x68}, |
|||
{0.362f, 0x6a}, |
|||
{0.37f, 0x6c}, |
|||
{0.378f, 0x6e}, |
|||
{0.387f, 0x70}, |
|||
{0.395f, 0x72}, |
|||
{0.404f, 0x74}, |
|||
{0.413f, 0x76}, |
|||
{0.422f, 0x78}, |
|||
{0.431f, 0x7a}, |
|||
{0.44f, 0x7c}, |
|||
{0.45f, 0x7e}, |
|||
{0.46f, 0x80}, |
|||
{0.47f, 0x82}, |
|||
{0.48f, 0x84}, |
|||
{0.491f, 0x86}, |
|||
{0.501f, 0x88}, |
|||
{0.512f, 0x8a}, |
|||
{0.524f, 0x8c}, |
|||
{0.535f, 0x8e}, |
|||
{0.547f, 0x90}, |
|||
{0.559f, 0x92}, |
|||
{0.571f, 0x94}, |
|||
{0.584f, 0x96}, |
|||
{0.596f, 0x98}, |
|||
{0.609f, 0x9a}, |
|||
{0.623f, 0x9c}, |
|||
{0.636f, 0x9e}, |
|||
{0.65f, 0xa0}, |
|||
{0.665f, 0xa2}, |
|||
{0.679f, 0xa4}, |
|||
{0.694f, 0xa6}, |
|||
{0.709f, 0xa8}, |
|||
{0.725f, 0xaa}, |
|||
{0.741f, 0xac}, |
|||
{0.757f, 0xae}, |
|||
{0.773f, 0xb0}, |
|||
{0.79f, 0xb2}, |
|||
{0.808f, 0xb4}, |
|||
{0.825f, 0xb6}, |
|||
{0.843f, 0xb8}, |
|||
{0.862f, 0xba}, |
|||
{0.881f, 0xbc}, |
|||
{0.9f, 0xbe}, |
|||
{0.92f, 0xc0}, |
|||
{0.94f, 0xc2}, |
|||
{0.96f, 0xc4}, |
|||
{0.981f, 0xc6}, |
|||
{1.003f, 0xc8}, |
|||
}; |
|||
|
|||
for (const auto& [amplitude_value, code] : high_fequency_amplitude) { |
|||
if (amplitude <= amplitude_value) { |
|||
return static_cast<u8>(code); |
|||
} |
|||
} |
|||
|
|||
return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second); |
|||
} |
|||
|
|||
u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const { |
|||
/* More information about these values can be found here:
|
|||
* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
|
|||
*/ |
|||
constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{ |
|||
std::pair<f32, int>{0.0f, 0x0040}, |
|||
{0.01f, 0x8040}, |
|||
{0.012f, 0x0041}, |
|||
{0.014f, 0x8041}, |
|||
{0.017f, 0x0042}, |
|||
{0.02f, 0x8042}, |
|||
{0.024f, 0x0043}, |
|||
{0.028f, 0x8043}, |
|||
{0.033f, 0x0044}, |
|||
{0.04f, 0x8044}, |
|||
{0.047f, 0x0045}, |
|||
{0.056f, 0x8045}, |
|||
{0.067f, 0x0046}, |
|||
{0.08f, 0x8046}, |
|||
{0.095f, 0x0047}, |
|||
{0.112f, 0x8047}, |
|||
{0.117f, 0x0048}, |
|||
{0.123f, 0x8048}, |
|||
{0.128f, 0x0049}, |
|||
{0.134f, 0x8049}, |
|||
{0.14f, 0x004a}, |
|||
{0.146f, 0x804a}, |
|||
{0.152f, 0x004b}, |
|||
{0.159f, 0x804b}, |
|||
{0.166f, 0x004c}, |
|||
{0.173f, 0x804c}, |
|||
{0.181f, 0x004d}, |
|||
{0.189f, 0x804d}, |
|||
{0.198f, 0x004e}, |
|||
{0.206f, 0x804e}, |
|||
{0.215f, 0x004f}, |
|||
{0.225f, 0x804f}, |
|||
{0.23f, 0x0050}, |
|||
{0.235f, 0x8050}, |
|||
{0.24f, 0x0051}, |
|||
{0.245f, 0x8051}, |
|||
{0.251f, 0x0052}, |
|||
{0.256f, 0x8052}, |
|||
{0.262f, 0x0053}, |
|||
{0.268f, 0x8053}, |
|||
{0.273f, 0x0054}, |
|||
{0.279f, 0x8054}, |
|||
{0.286f, 0x0055}, |
|||
{0.292f, 0x8055}, |
|||
{0.298f, 0x0056}, |
|||
{0.305f, 0x8056}, |
|||
{0.311f, 0x0057}, |
|||
{0.318f, 0x8057}, |
|||
{0.325f, 0x0058}, |
|||
{0.332f, 0x8058}, |
|||
{0.34f, 0x0059}, |
|||
{0.347f, 0x8059}, |
|||
{0.355f, 0x005a}, |
|||
{0.362f, 0x805a}, |
|||
{0.37f, 0x005b}, |
|||
{0.378f, 0x805b}, |
|||
{0.387f, 0x005c}, |
|||
{0.395f, 0x805c}, |
|||
{0.404f, 0x005d}, |
|||
{0.413f, 0x805d}, |
|||
{0.422f, 0x005e}, |
|||
{0.431f, 0x805e}, |
|||
{0.44f, 0x005f}, |
|||
{0.45f, 0x805f}, |
|||
{0.46f, 0x0060}, |
|||
{0.47f, 0x8060}, |
|||
{0.48f, 0x0061}, |
|||
{0.491f, 0x8061}, |
|||
{0.501f, 0x0062}, |
|||
{0.512f, 0x8062}, |
|||
{0.524f, 0x0063}, |
|||
{0.535f, 0x8063}, |
|||
{0.547f, 0x0064}, |
|||
{0.559f, 0x8064}, |
|||
{0.571f, 0x0065}, |
|||
{0.584f, 0x8065}, |
|||
{0.596f, 0x0066}, |
|||
{0.609f, 0x8066}, |
|||
{0.623f, 0x0067}, |
|||
{0.636f, 0x8067}, |
|||
{0.65f, 0x0068}, |
|||
{0.665f, 0x8068}, |
|||
{0.679f, 0x0069}, |
|||
{0.694f, 0x8069}, |
|||
{0.709f, 0x006a}, |
|||
{0.725f, 0x806a}, |
|||
{0.741f, 0x006b}, |
|||
{0.757f, 0x806b}, |
|||
{0.773f, 0x006c}, |
|||
{0.79f, 0x806c}, |
|||
{0.808f, 0x006d}, |
|||
{0.825f, 0x806d}, |
|||
{0.843f, 0x006e}, |
|||
{0.862f, 0x806e}, |
|||
{0.881f, 0x006f}, |
|||
{0.9f, 0x806f}, |
|||
{0.92f, 0x0070}, |
|||
{0.94f, 0x8070}, |
|||
{0.96f, 0x0071}, |
|||
{0.981f, 0x8071}, |
|||
{1.003f, 0x0072}, |
|||
}; |
|||
|
|||
for (const auto& [amplitude_value, code] : high_fequency_amplitude) { |
|||
if (amplitude <= amplitude_value) { |
|||
return static_cast<u16>(code); |
|||
} |
|||
} |
|||
|
|||
return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second); |
|||
} |
|||
|
|||
} // namespace InputCommon::Joycon
|
|||
@ -0,0 +1,33 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse |
|||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c |
|||
// https://github.com/CTCaer/jc_toolkit |
|||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
|
|||
#include "input_common/helpers/joycon_protocol/common_protocol.h" |
|||
#include "input_common/helpers/joycon_protocol/joycon_types.h" |
|||
|
|||
namespace InputCommon::Joycon { |
|||
|
|||
class RumbleProtocol final : private JoyconCommonProtocol { |
|||
public: |
|||
RumbleProtocol(std::shared_ptr<JoyconHandle> handle); |
|||
|
|||
DriverResult EnableRumble(bool is_enabled); |
|||
|
|||
DriverResult SendVibration(const VibrationValue& vibration); |
|||
|
|||
private: |
|||
u16 EncodeHighFrequency(f32 frequency) const; |
|||
u8 EncodeLowFrequency(f32 frequency) const; |
|||
u8 EncodeHighAmplitude(f32 amplitude) const; |
|||
u16 EncodeLowAmplitude(f32 amplitude) const; |
|||
}; |
|||
|
|||
} // namespace InputCommon::Joycon |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue