input_common: Add support for joycon input reports
This commit is contained in:
		| @@ -64,6 +64,10 @@ if (ENABLE_SDL2) | ||||
|         helpers/joycon_protocol/generic_functions.cpp | ||||
|         helpers/joycon_protocol/generic_functions.h | ||||
|         helpers/joycon_protocol/joycon_types.h | ||||
|         helpers/joycon_protocol/poller.cpp | ||||
|         helpers/joycon_protocol/poller.h | ||||
|         helpers/joycon_protocol/rumble.cpp | ||||
|         helpers/joycon_protocol/rumble.h | ||||
|     ) | ||||
|     target_link_libraries(input_common PRIVATE SDL2::SDL2) | ||||
|     target_compile_definitions(input_common PRIVATE HAVE_SDL2) | ||||
|   | ||||
| @@ -167,30 +167,31 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) { | ||||
|     if (result == Joycon::DriverResult::Success) { | ||||
|         LOG_WARNING(Input, "Initialize device"); | ||||
|  | ||||
|         std::function<void(Joycon::Battery)> on_battery_data; | ||||
|         std::function<void(Joycon::Color)> on_button_data; | ||||
|         std::function<void(int, f32)> on_stick_data; | ||||
|         std::function<void(int, std::array<u8, 6>)> on_motion_data; | ||||
|         std::function<void(s16)> on_ring_data; | ||||
|         std::function<void(const std::vector<u8>&)> on_amiibo_data; | ||||
|  | ||||
|         const std::size_t port = handle->GetDevicePort(); | ||||
|         handle->on_battery_data = { | ||||
|             [this, port, type](Joycon::Battery value) { OnBatteryUpdate(port, type, value); }}; | ||||
|         handle->on_color_data = { | ||||
|             [this, port, type](Joycon::Color value) { OnColorUpdate(port, type, value); }}; | ||||
|         handle->on_button_data = { | ||||
|             [this, port, type](int id, bool value) { OnButtonUpdate(port, type, id, value); }}; | ||||
|         handle->on_stick_data = { | ||||
|             [this, port, type](int id, f32 value) { OnStickUpdate(port, type, id, value); }}; | ||||
|         handle->on_motion_data = {[this, port, type](int id, Joycon::MotionData value) { | ||||
|             OnMotionUpdate(port, type, id, value); | ||||
|         }}; | ||||
|         handle->on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}; | ||||
|         handle->on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) { | ||||
|             OnAmiiboUpdate(port, amiibo_data); | ||||
|         }}; | ||||
|         const Joycon::JoyconCallbacks callbacks{ | ||||
|             .on_battery_data = {[this, port, type](Joycon::Battery value) { | ||||
|                 OnBatteryUpdate(port, type, value); | ||||
|             }}, | ||||
|             .on_color_data = {[this, port, type](Joycon::Color value) { | ||||
|                 OnColorUpdate(port, type, value); | ||||
|             }}, | ||||
|             .on_button_data = {[this, port, type](int id, bool value) { | ||||
|                 OnButtonUpdate(port, type, id, value); | ||||
|             }}, | ||||
|             .on_stick_data = {[this, port, type](int id, f32 value) { | ||||
|                 OnStickUpdate(port, type, id, value); | ||||
|             }}, | ||||
|             .on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) { | ||||
|                 OnMotionUpdate(port, type, id, value); | ||||
|             }}, | ||||
|             .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}, | ||||
|             .on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) { | ||||
|                 OnAmiiboUpdate(port, amiibo_data); | ||||
|             }}, | ||||
|         }; | ||||
|  | ||||
|         handle->InitializeDevice(); | ||||
|         handle->SetCallbacks(callbacks); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -235,7 +236,7 @@ Common::Input::VibrationError Joycons::SetVibration( | ||||
|         .low_amplitude = vibration.low_amplitude, | ||||
|         .low_frequency = vibration.low_frequency, | ||||
|         .high_amplitude = vibration.high_amplitude, | ||||
|         .high_frequency = vibration.high_amplitude, | ||||
|         .high_frequency = vibration.high_frequency, | ||||
|     }; | ||||
|     auto handle = GetHandle(identifier); | ||||
|     if (handle == nullptr) { | ||||
|   | ||||
| @@ -66,6 +66,7 @@ DriverResult JoyconDriver::InitializeDevice() { | ||||
|     // Initialize HW Protocols | ||||
|     calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle); | ||||
|     generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle); | ||||
|     rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle); | ||||
|  | ||||
|     // Get fixed joycon info | ||||
|     generic_protocol->GetVersionNumber(version); | ||||
| @@ -90,6 +91,10 @@ DriverResult JoyconDriver::InitializeDevice() { | ||||
|     // Apply HW configuration | ||||
|     SetPollingMode(); | ||||
|  | ||||
|     // Initialize joycon poller | ||||
|     joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration, | ||||
|                                                    right_stick_calibration, motion_calibration); | ||||
|  | ||||
|     // Start pooling for data | ||||
|     is_connected = true; | ||||
|     if (!input_thread_running) { | ||||
| @@ -142,15 +147,40 @@ void JoyconDriver::InputThread(std::stop_token stop_token) { | ||||
| void JoyconDriver::OnNewData(std::span<u8> buffer) { | ||||
|     const auto report_mode = static_cast<InputReport>(buffer[0]); | ||||
|  | ||||
|     // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion | ||||
|     // experience | ||||
|     switch (report_mode) { | ||||
|     case InputReport::STANDARD_FULL_60HZ: | ||||
|         ReadActiveMode(buffer); | ||||
|     case InputReport::NFC_IR_MODE_60HZ: | ||||
|     case InputReport::SIMPLE_HID_MODE: { | ||||
|         const auto now = std::chrono::steady_clock::now(); | ||||
|         const auto new_delta_time = static_cast<u64>( | ||||
|             std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); | ||||
|         delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10; | ||||
|         last_update = now; | ||||
|         joycon_poller->UpdateColor(color); | ||||
|         break; | ||||
|     } | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     const MotionStatus motion_status{ | ||||
|         .is_enabled = motion_enabled, | ||||
|         .delta_time = delta_time, | ||||
|         .gyro_sensitivity = gyro_sensitivity, | ||||
|         .accelerometer_sensitivity = accelerometer_sensitivity, | ||||
|     }; | ||||
|  | ||||
|     switch (report_mode) { | ||||
|     case InputReport::STANDARD_FULL_60HZ: | ||||
|         joycon_poller->ReadActiveMode(buffer, motion_status); | ||||
|         break; | ||||
|     case InputReport::NFC_IR_MODE_60HZ: | ||||
|         ReadNfcIRMode(buffer); | ||||
|         joycon_poller->ReadNfcIRMode(buffer, motion_status); | ||||
|         break; | ||||
|     case InputReport::SIMPLE_HID_MODE: | ||||
|         ReadPassiveMode(buffer); | ||||
|         joycon_poller->ReadPassiveMode(buffer); | ||||
|         break; | ||||
|     case InputReport::SUBCMD_REPLY: | ||||
|         LOG_DEBUG(Input, "Unhandled command reply"); | ||||
| @@ -164,6 +194,8 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) { | ||||
| void JoyconDriver::SetPollingMode() { | ||||
|     disable_input_thread = true; | ||||
|  | ||||
|     rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration); | ||||
|  | ||||
|     if (motion_enabled && supported_features.motion) { | ||||
|         generic_protocol->EnableImu(true); | ||||
|         generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance, | ||||
| @@ -209,62 +241,6 @@ JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() { | ||||
|     return features; | ||||
| } | ||||
|  | ||||
| void JoyconDriver::ReadActiveMode(std::span<u8> buffer) { | ||||
|     InputReportActive data{}; | ||||
|     memcpy(&data, buffer.data(), sizeof(InputReportActive)); | ||||
|  | ||||
|     // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion | ||||
|     // experience | ||||
|     const auto now = std::chrono::steady_clock::now(); | ||||
|     const auto new_delta_time = | ||||
|         std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count(); | ||||
|     delta_time = static_cast<u64>((delta_time * 0.8f) + (new_delta_time * 0.2)); | ||||
|     last_update = now; | ||||
|  | ||||
|     switch (device_type) { | ||||
|     case Joycon::ControllerType::Left: | ||||
|         break; | ||||
|     case Joycon::ControllerType::Right: | ||||
|         break; | ||||
|     case Joycon::ControllerType::Pro: | ||||
|         break; | ||||
|     case Joycon::ControllerType::Grip: | ||||
|     case Joycon::ControllerType::Dual: | ||||
|     case Joycon::ControllerType::None: | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     on_battery_data(data.battery_status); | ||||
|     on_color_data(color); | ||||
| } | ||||
|  | ||||
| void JoyconDriver::ReadPassiveMode(std::span<u8> buffer) { | ||||
|     InputReportPassive data{}; | ||||
|     memcpy(&data, buffer.data(), sizeof(InputReportPassive)); | ||||
|  | ||||
|     switch (device_type) { | ||||
|     case Joycon::ControllerType::Left: | ||||
|         break; | ||||
|     case Joycon::ControllerType::Right: | ||||
|         break; | ||||
|     case Joycon::ControllerType::Pro: | ||||
|         break; | ||||
|     case Joycon::ControllerType::Grip: | ||||
|     case Joycon::ControllerType::Dual: | ||||
|     case Joycon::ControllerType::None: | ||||
|         break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void JoyconDriver::ReadNfcIRMode(std::span<u8> buffer) { | ||||
|     // This mode is compatible with the active mode | ||||
|     ReadActiveMode(buffer); | ||||
|  | ||||
|     if (!nfc_enabled) { | ||||
|         return; | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool JoyconDriver::IsInputThreadValid() const { | ||||
|     if (!is_connected) { | ||||
|         return false; | ||||
| @@ -302,7 +278,7 @@ DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) { | ||||
|     if (disable_input_thread) { | ||||
|         return DriverResult::HandleInUse; | ||||
|     } | ||||
|     return DriverResult::NotSupported; | ||||
|     return rumble_protocol->SendVibration(vibration); | ||||
| } | ||||
|  | ||||
| DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { | ||||
| @@ -398,6 +374,10 @@ SerialNumber JoyconDriver::GetHandleSerialNumber() const { | ||||
|     return handle_serial_number; | ||||
| } | ||||
|  | ||||
| void JoyconDriver::SetCallbacks(const Joycon::JoyconCallbacks& callbacks) { | ||||
|     joycon_poller->SetCallbacks(callbacks); | ||||
| } | ||||
|  | ||||
| Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info, | ||||
|                                                  ControllerType& controller_type) { | ||||
|     std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{ | ||||
|   | ||||
| @@ -11,6 +11,8 @@ | ||||
| #include "input_common/helpers/joycon_protocol/calibration.h" | ||||
| #include "input_common/helpers/joycon_protocol/generic_functions.h" | ||||
| #include "input_common/helpers/joycon_protocol/joycon_types.h" | ||||
| #include "input_common/helpers/joycon_protocol/poller.h" | ||||
| #include "input_common/helpers/joycon_protocol/rumble.h" | ||||
|  | ||||
| namespace InputCommon::Joycon { | ||||
|  | ||||
| @@ -42,6 +44,8 @@ public: | ||||
|     DriverResult SetNfcMode(); | ||||
|     DriverResult SetRingConMode(); | ||||
|  | ||||
|     void SetCallbacks(const Joycon::JoyconCallbacks& callbacks); | ||||
|  | ||||
|     // Returns device type from hidapi handle | ||||
|     static Joycon::DriverResult GetDeviceType(SDL_hid_device_info* device_info, | ||||
|                                               Joycon::ControllerType& controller_type); | ||||
| @@ -50,14 +54,6 @@ public: | ||||
|     static Joycon::DriverResult GetSerialNumber(SDL_hid_device_info* device_info, | ||||
|                                                 Joycon::SerialNumber& serial_number); | ||||
|  | ||||
|     std::function<void(Battery)> on_battery_data; | ||||
|     std::function<void(Color)> on_color_data; | ||||
|     std::function<void(int, bool)> on_button_data; | ||||
|     std::function<void(int, f32)> on_stick_data; | ||||
|     std::function<void(int, MotionData)> on_motion_data; | ||||
|     std::function<void(f32)> on_ring_data; | ||||
|     std::function<void(const std::vector<u8>&)> on_amiibo_data; | ||||
|  | ||||
| private: | ||||
|     struct SupportedFeatures { | ||||
|         bool passive{}; | ||||
| @@ -86,18 +82,11 @@ private: | ||||
|     /// Returns a list of supported features that can be enabled on this device | ||||
|     SupportedFeatures GetSupportedFeatures(); | ||||
|  | ||||
|     /// Handles data from passive packages | ||||
|     void ReadPassiveMode(std::span<u8> buffer); | ||||
|  | ||||
|     /// Handles data from active packages | ||||
|     void ReadActiveMode(std::span<u8> buffer); | ||||
|  | ||||
|     /// Handles data from nfc or ir packages | ||||
|     void ReadNfcIRMode(std::span<u8> buffer); | ||||
|  | ||||
|     // Protocol Features | ||||
|     std::unique_ptr<CalibrationProtocol> calibration_protocol = nullptr; | ||||
|     std::unique_ptr<GenericProtocol> generic_protocol = nullptr; | ||||
|     std::unique_ptr<JoyconPoller> joycon_poller = nullptr; | ||||
|     std::unique_ptr<RumbleProtocol> rumble_protocol = nullptr; | ||||
|  | ||||
|     // Connection status | ||||
|     bool is_connected{}; | ||||
|   | ||||
							
								
								
									
										315
									
								
								src/input_common/helpers/joycon_protocol/poller.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								src/input_common/helpers/joycon_protocol/poller.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										77
									
								
								src/input_common/helpers/joycon_protocol/poller.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/input_common/helpers/joycon_protocol/poller.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										299
									
								
								src/input_common/helpers/joycon_protocol/rumble.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								src/input_common/helpers/joycon_protocol/rumble.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										33
									
								
								src/input_common/helpers/joycon_protocol/rumble.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/input_common/helpers/joycon_protocol/rumble.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
		Reference in New Issue
	
	Block a user