summaryrefslogtreecommitdiff
path: root/src/input_common/helpers/joycon_protocol
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common/helpers/joycon_protocol')
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.cpp315
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.h77
-rw-r--r--src/input_common/helpers/joycon_protocol/rumble.cpp299
-rw-r--r--src/input_common/helpers/joycon_protocol/rumble.h33
4 files changed, 724 insertions, 0 deletions
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
new file mode 100644
index 000000000..341479c0c
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -0,0 +1,315 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/poller.h"
6
7namespace InputCommon::Joycon {
8
9JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
10 JoyStickCalibration right_stick_calibration_,
11 MotionCalibration motion_calibration_)
12 : device_type{device_type_}, left_stick_calibration{left_stick_calibration_},
13 right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {}
14
15void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
16 callbacks = std::move(callbacks_);
17}
18
19void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status) {
20 InputReportActive data{};
21 memcpy(&data, buffer.data(), sizeof(InputReportActive));
22
23 switch (device_type) {
24 case Joycon::ControllerType::Left:
25 UpdateActiveLeftPadInput(data, motion_status);
26 break;
27 case Joycon::ControllerType::Right:
28 UpdateActiveRightPadInput(data, motion_status);
29 break;
30 case Joycon::ControllerType::Pro:
31 UpdateActiveProPadInput(data, motion_status);
32 break;
33 case Joycon::ControllerType::Grip:
34 case Joycon::ControllerType::Dual:
35 case Joycon::ControllerType::None:
36 break;
37 }
38
39 callbacks.on_battery_data(data.battery_status);
40}
41
42void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
43 InputReportPassive data{};
44 memcpy(&data, buffer.data(), sizeof(InputReportPassive));
45
46 switch (device_type) {
47 case Joycon::ControllerType::Left:
48 UpdatePasiveLeftPadInput(data);
49 break;
50 case Joycon::ControllerType::Right:
51 UpdatePasiveRightPadInput(data);
52 break;
53 case Joycon::ControllerType::Pro:
54 UpdatePasiveProPadInput(data);
55 break;
56 case Joycon::ControllerType::Grip:
57 case Joycon::ControllerType::Dual:
58 case Joycon::ControllerType::None:
59 break;
60 }
61}
62
63void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
64 // This mode is compatible with the active mode
65 ReadActiveMode(buffer, motion_status);
66}
67
68void JoyconPoller::UpdateColor(const Color& color) {
69 callbacks.on_color_data(color);
70}
71
72void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
73 const MotionStatus& motion_status) {
74 static constexpr std::array<Joycon::PadButton, 11> left_buttons{
75 Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
76 Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
77 Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus,
78 Joycon::PadButton::Capture, Joycon::PadButton::StickL,
79 };
80
81 const u32 raw_button =
82 static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16));
83 for (std::size_t i = 0; i < left_buttons.size(); ++i) {
84 const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
85 const int button = static_cast<int>(left_buttons[i]);
86 callbacks.on_button_data(button, button_status);
87 }
88
89 const u16 raw_left_axis_x =
90 static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
91 const u16 raw_left_axis_y =
92 static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
93 const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
94 const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
95 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
96 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
97
98 if (motion_status.is_enabled) {
99 auto left_motion = GetMotionInput(input, motion_status);
100 // Rotate motion axis to the correct direction
101 left_motion.accel_y = -left_motion.accel_y;
102 left_motion.accel_z = -left_motion.accel_z;
103 left_motion.gyro_x = -left_motion.gyro_x;
104 callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion);
105 }
106}
107
108void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input,
109 const MotionStatus& motion_status) {
110 static constexpr std::array<Joycon::PadButton, 11> right_buttons{
111 Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B,
112 Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
113 Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
114 Joycon::PadButton::Home, Joycon::PadButton::StickR,
115 };
116
117 const u32 raw_button =
118 static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16));
119 for (std::size_t i = 0; i < right_buttons.size(); ++i) {
120 const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
121 const int button = static_cast<int>(right_buttons[i]);
122 callbacks.on_button_data(button, button_status);
123 }
124
125 const u16 raw_right_axis_x =
126 static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
127 const u16 raw_right_axis_y =
128 static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
129 const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
130 const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
131 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
132 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
133
134 if (motion_status.is_enabled) {
135 auto right_motion = GetMotionInput(input, motion_status);
136 // Rotate motion axis to the correct direction
137 right_motion.accel_x = -right_motion.accel_x;
138 right_motion.accel_y = -right_motion.accel_y;
139 right_motion.gyro_z = -right_motion.gyro_z;
140 callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion);
141 }
142}
143
144void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input,
145 const MotionStatus& motion_status) {
146 static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
147 Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
148 Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL,
149 Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
150 Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A,
151 Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
152 Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR,
153 };
154
155 const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) |
156 (input.button_input[1] << 16));
157 for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
158 const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
159 const int button = static_cast<int>(pro_buttons[i]);
160 callbacks.on_button_data(button, button_status);
161 }
162
163 const u16 raw_left_axis_x =
164 static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
165 const u16 raw_left_axis_y =
166 static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
167 const u16 raw_right_axis_x =
168 static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
169 const u16 raw_right_axis_y =
170 static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
171
172 const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
173 const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
174 const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
175 const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
176 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
177 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
178 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
179 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
180
181 if (motion_status.is_enabled) {
182 auto pro_motion = GetMotionInput(input, motion_status);
183 pro_motion.gyro_x = -pro_motion.gyro_x;
184 pro_motion.accel_y = -pro_motion.accel_y;
185 pro_motion.accel_z = -pro_motion.accel_z;
186 callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion);
187 callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion);
188 }
189}
190
191void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
192 static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{
193 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
194 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
195 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
196 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
197 Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture,
198 Joycon::PasivePadButton::StickL,
199 };
200
201 for (std::size_t i = 0; i < left_buttons.size(); ++i) {
202 const bool button_status = (input.button_input & static_cast<u32>(left_buttons[i])) != 0;
203 const int button = static_cast<int>(left_buttons[i]);
204 callbacks.on_button_data(button, button_status);
205 }
206}
207
208void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
209 static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{
210 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
211 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
212 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
213 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
214 Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home,
215 Joycon::PasivePadButton::StickR,
216 };
217
218 for (std::size_t i = 0; i < right_buttons.size(); ++i) {
219 const bool button_status = (input.button_input & static_cast<u32>(right_buttons[i])) != 0;
220 const int button = static_cast<int>(right_buttons[i]);
221 callbacks.on_button_data(button, button_status);
222 }
223}
224
225void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
226 static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{
227 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
228 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
229 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
230 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
231 Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus,
232 Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home,
233 Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR,
234 };
235
236 for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
237 const bool button_status = (input.button_input & static_cast<u32>(pro_buttons[i])) != 0;
238 const int button = static_cast<int>(pro_buttons[i]);
239 callbacks.on_button_data(button, button_status);
240 }
241}
242
243f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
244 const f32 value = static_cast<f32>(raw_value - calibration.center);
245 if (value > 0.0f) {
246 return value / calibration.max;
247 }
248 return value / calibration.min;
249}
250
251f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
252 AccelerometerSensitivity sensitivity) const {
253 const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
254 switch (sensitivity) {
255 case Joycon::AccelerometerSensitivity::G2:
256 return value / 4.0f;
257 case Joycon::AccelerometerSensitivity::G4:
258 return value / 2.0f;
259 case Joycon::AccelerometerSensitivity::G8:
260 return value;
261 case Joycon::AccelerometerSensitivity::G16:
262 return value * 2.0f;
263 }
264 return value;
265}
266
267f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
268 GyroSensitivity sensitivity) const {
269 const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
270 switch (sensitivity) {
271 case Joycon::GyroSensitivity::DPS250:
272 return value / 8.0f;
273 case Joycon::GyroSensitivity::DPS500:
274 return value / 4.0f;
275 case Joycon::GyroSensitivity::DPS1000:
276 return value / 2.0f;
277 case Joycon::GyroSensitivity::DPS2000:
278 return value;
279 }
280 return value;
281}
282
283s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
284 const InputReportActive& input) const {
285 return input.motion_input[(sensor * 3) + axis];
286}
287
288MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
289 const MotionStatus& motion_status) const {
290 MotionData motion{};
291 const auto& accel_cal = motion_calibration.accelerometer;
292 const auto& gyro_cal = motion_calibration.gyro;
293 const s16 raw_accel_x = input.motion_input[1];
294 const s16 raw_accel_y = input.motion_input[0];
295 const s16 raw_accel_z = input.motion_input[2];
296 const s16 raw_gyro_x = input.motion_input[4];
297 const s16 raw_gyro_y = input.motion_input[3];
298 const s16 raw_gyro_z = input.motion_input[5];
299
300 motion.delta_timestamp = motion_status.delta_time;
301 motion.accel_x =
302 GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
303 motion.accel_y =
304 GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
305 motion.accel_z =
306 GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
307 motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
308 motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
309 motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
310
311 // TODO(German77): Return all three samples data
312 return motion;
313}
314
315} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
new file mode 100644
index 000000000..fff681d0a
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -0,0 +1,77 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <functional>
12#include <span>
13
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18// Handles input packages and triggers the corresponding input events
19class JoyconPoller {
20public:
21 JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
22 JoyStickCalibration right_stick_calibration_,
23 MotionCalibration motion_calibration_);
24
25 void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_);
26
27 /// Handles data from passive packages
28 void ReadPassiveMode(std::span<u8> buffer);
29
30 /// Handles data from active packages
31 void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status);
32
33 /// Handles data from nfc or ir packages
34 void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
35
36 void UpdateColor(const Color& color);
37
38private:
39 void UpdateActiveLeftPadInput(const InputReportActive& input,
40 const MotionStatus& motion_status);
41 void UpdateActiveRightPadInput(const InputReportActive& input,
42 const MotionStatus& motion_status);
43 void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status);
44
45 void UpdatePasiveLeftPadInput(const InputReportPassive& buffer);
46 void UpdatePasiveRightPadInput(const InputReportPassive& buffer);
47 void UpdatePasiveProPadInput(const InputReportPassive& buffer);
48
49 /// Returns a calibrated joystick axis from raw axis data
50 f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
51
52 /// Returns a calibrated accelerometer axis from raw motion data
53 f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
54 AccelerometerSensitivity sensitivity) const;
55
56 /// Returns a calibrated gyro axis from raw motion data
57 f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal,
58 GyroSensitivity sensitivity) const;
59
60 /// Returns a raw motion value from a buffer
61 s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const;
62
63 /// Returns motion data from a buffer
64 MotionData GetMotionInput(const InputReportActive& input,
65 const MotionStatus& motion_status) const;
66
67 ControllerType device_type{};
68
69 // Device calibration
70 JoyStickCalibration left_stick_calibration{};
71 JoyStickCalibration right_stick_calibration{};
72 MotionCalibration motion_calibration{};
73
74 Joycon::JoyconCallbacks callbacks{};
75};
76
77} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp
new file mode 100644
index 000000000..17ee38863
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.cpp
@@ -0,0 +1,299 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/rumble.h"
6
7namespace InputCommon::Joycon {
8
9RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
10 : JoyconCommonProtocol(handle) {}
11
12DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
13 LOG_DEBUG(Input, "Enable Rumble");
14 const std::vector<u8> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
15 std::vector<u8> output;
16 SetBlocking();
17 const auto result = SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer, output);
18 SetNonBlocking();
19 return result;
20}
21
22DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
23 std::vector<u8> buffer(sizeof(DefaultVibrationBuffer));
24
25 if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) {
26 return SendVibrationReport(DefaultVibrationBuffer);
27 }
28
29 // Protect joycons from damage from strong vibrations
30 const f32 clamp_amplitude =
31 1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude);
32
33 const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency);
34 const u8 encoded_high_amplitude =
35 EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude);
36 const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency);
37 const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude);
38
39 buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF);
40 buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01));
41 buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80));
42 buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF);
43
44 // Duplicate rumble for now
45 buffer[4] = buffer[0];
46 buffer[5] = buffer[1];
47 buffer[6] = buffer[2];
48 buffer[7] = buffer[3];
49
50 return SendVibrationReport(buffer);
51}
52
53u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const {
54 const u8 new_frequency =
55 static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
56 return static_cast<u16>((new_frequency - 0x60) * 4);
57}
58
59u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
60 const u8 new_frequency =
61 static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
62 return static_cast<u8>(new_frequency - 0x40);
63}
64
65u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
66 /* More information about these values can be found here:
67 * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
68 */
69 constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
70 std::pair<f32, int>{0.0f, 0x0},
71 {0.01f, 0x2},
72 {0.012f, 0x4},
73 {0.014f, 0x6},
74 {0.017f, 0x8},
75 {0.02f, 0x0a},
76 {0.024f, 0x0c},
77 {0.028f, 0x0e},
78 {0.033f, 0x10},
79 {0.04f, 0x12},
80 {0.047f, 0x14},
81 {0.056f, 0x16},
82 {0.067f, 0x18},
83 {0.08f, 0x1a},
84 {0.095f, 0x1c},
85 {0.112f, 0x1e},
86 {0.117f, 0x20},
87 {0.123f, 0x22},
88 {0.128f, 0x24},
89 {0.134f, 0x26},
90 {0.14f, 0x28},
91 {0.146f, 0x2a},
92 {0.152f, 0x2c},
93 {0.159f, 0x2e},
94 {0.166f, 0x30},
95 {0.173f, 0x32},
96 {0.181f, 0x34},
97 {0.189f, 0x36},
98 {0.198f, 0x38},
99 {0.206f, 0x3a},
100 {0.215f, 0x3c},
101 {0.225f, 0x3e},
102 {0.23f, 0x40},
103 {0.235f, 0x42},
104 {0.24f, 0x44},
105 {0.245f, 0x46},
106 {0.251f, 0x48},
107 {0.256f, 0x4a},
108 {0.262f, 0x4c},
109 {0.268f, 0x4e},
110 {0.273f, 0x50},
111 {0.279f, 0x52},
112 {0.286f, 0x54},
113 {0.292f, 0x56},
114 {0.298f, 0x58},
115 {0.305f, 0x5a},
116 {0.311f, 0x5c},
117 {0.318f, 0x5e},
118 {0.325f, 0x60},
119 {0.332f, 0x62},
120 {0.34f, 0x64},
121 {0.347f, 0x66},
122 {0.355f, 0x68},
123 {0.362f, 0x6a},
124 {0.37f, 0x6c},
125 {0.378f, 0x6e},
126 {0.387f, 0x70},
127 {0.395f, 0x72},
128 {0.404f, 0x74},
129 {0.413f, 0x76},
130 {0.422f, 0x78},
131 {0.431f, 0x7a},
132 {0.44f, 0x7c},
133 {0.45f, 0x7e},
134 {0.46f, 0x80},
135 {0.47f, 0x82},
136 {0.48f, 0x84},
137 {0.491f, 0x86},
138 {0.501f, 0x88},
139 {0.512f, 0x8a},
140 {0.524f, 0x8c},
141 {0.535f, 0x8e},
142 {0.547f, 0x90},
143 {0.559f, 0x92},
144 {0.571f, 0x94},
145 {0.584f, 0x96},
146 {0.596f, 0x98},
147 {0.609f, 0x9a},
148 {0.623f, 0x9c},
149 {0.636f, 0x9e},
150 {0.65f, 0xa0},
151 {0.665f, 0xa2},
152 {0.679f, 0xa4},
153 {0.694f, 0xa6},
154 {0.709f, 0xa8},
155 {0.725f, 0xaa},
156 {0.741f, 0xac},
157 {0.757f, 0xae},
158 {0.773f, 0xb0},
159 {0.79f, 0xb2},
160 {0.808f, 0xb4},
161 {0.825f, 0xb6},
162 {0.843f, 0xb8},
163 {0.862f, 0xba},
164 {0.881f, 0xbc},
165 {0.9f, 0xbe},
166 {0.92f, 0xc0},
167 {0.94f, 0xc2},
168 {0.96f, 0xc4},
169 {0.981f, 0xc6},
170 {1.003f, 0xc8},
171 };
172
173 for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
174 if (amplitude <= amplitude_value) {
175 return static_cast<u8>(code);
176 }
177 }
178
179 return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
180}
181
182u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
183 /* More information about these values can be found here:
184 * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
185 */
186 constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
187 std::pair<f32, int>{0.0f, 0x0040},
188 {0.01f, 0x8040},
189 {0.012f, 0x0041},
190 {0.014f, 0x8041},
191 {0.017f, 0x0042},
192 {0.02f, 0x8042},
193 {0.024f, 0x0043},
194 {0.028f, 0x8043},
195 {0.033f, 0x0044},
196 {0.04f, 0x8044},
197 {0.047f, 0x0045},
198 {0.056f, 0x8045},
199 {0.067f, 0x0046},
200 {0.08f, 0x8046},
201 {0.095f, 0x0047},
202 {0.112f, 0x8047},
203 {0.117f, 0x0048},
204 {0.123f, 0x8048},
205 {0.128f, 0x0049},
206 {0.134f, 0x8049},
207 {0.14f, 0x004a},
208 {0.146f, 0x804a},
209 {0.152f, 0x004b},
210 {0.159f, 0x804b},
211 {0.166f, 0x004c},
212 {0.173f, 0x804c},
213 {0.181f, 0x004d},
214 {0.189f, 0x804d},
215 {0.198f, 0x004e},
216 {0.206f, 0x804e},
217 {0.215f, 0x004f},
218 {0.225f, 0x804f},
219 {0.23f, 0x0050},
220 {0.235f, 0x8050},
221 {0.24f, 0x0051},
222 {0.245f, 0x8051},
223 {0.251f, 0x0052},
224 {0.256f, 0x8052},
225 {0.262f, 0x0053},
226 {0.268f, 0x8053},
227 {0.273f, 0x0054},
228 {0.279f, 0x8054},
229 {0.286f, 0x0055},
230 {0.292f, 0x8055},
231 {0.298f, 0x0056},
232 {0.305f, 0x8056},
233 {0.311f, 0x0057},
234 {0.318f, 0x8057},
235 {0.325f, 0x0058},
236 {0.332f, 0x8058},
237 {0.34f, 0x0059},
238 {0.347f, 0x8059},
239 {0.355f, 0x005a},
240 {0.362f, 0x805a},
241 {0.37f, 0x005b},
242 {0.378f, 0x805b},
243 {0.387f, 0x005c},
244 {0.395f, 0x805c},
245 {0.404f, 0x005d},
246 {0.413f, 0x805d},
247 {0.422f, 0x005e},
248 {0.431f, 0x805e},
249 {0.44f, 0x005f},
250 {0.45f, 0x805f},
251 {0.46f, 0x0060},
252 {0.47f, 0x8060},
253 {0.48f, 0x0061},
254 {0.491f, 0x8061},
255 {0.501f, 0x0062},
256 {0.512f, 0x8062},
257 {0.524f, 0x0063},
258 {0.535f, 0x8063},
259 {0.547f, 0x0064},
260 {0.559f, 0x8064},
261 {0.571f, 0x0065},
262 {0.584f, 0x8065},
263 {0.596f, 0x0066},
264 {0.609f, 0x8066},
265 {0.623f, 0x0067},
266 {0.636f, 0x8067},
267 {0.65f, 0x0068},
268 {0.665f, 0x8068},
269 {0.679f, 0x0069},
270 {0.694f, 0x8069},
271 {0.709f, 0x006a},
272 {0.725f, 0x806a},
273 {0.741f, 0x006b},
274 {0.757f, 0x806b},
275 {0.773f, 0x006c},
276 {0.79f, 0x806c},
277 {0.808f, 0x006d},
278 {0.825f, 0x806d},
279 {0.843f, 0x006e},
280 {0.862f, 0x806e},
281 {0.881f, 0x006f},
282 {0.9f, 0x806f},
283 {0.92f, 0x0070},
284 {0.94f, 0x8070},
285 {0.96f, 0x0071},
286 {0.981f, 0x8071},
287 {1.003f, 0x0072},
288 };
289
290 for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
291 if (amplitude <= amplitude_value) {
292 return static_cast<u16>(code);
293 }
294 }
295
296 return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
297}
298
299} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.h b/src/input_common/helpers/joycon_protocol/rumble.h
new file mode 100644
index 000000000..7d0329f03
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class RumbleProtocol final : private JoyconCommonProtocol {
19public:
20 RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableRumble(bool is_enabled);
23
24 DriverResult SendVibration(const VibrationValue& vibration);
25
26private:
27 u16 EncodeHighFrequency(f32 frequency) const;
28 u8 EncodeLowFrequency(f32 frequency) const;
29 u8 EncodeHighAmplitude(f32 amplitude) const;
30 u16 EncodeLowAmplitude(f32 amplitude) const;
31};
32
33} // namespace InputCommon::Joycon