mc.mitm: implement proper decoding of nintendo's hd rumble encoding scheme

This commit is contained in:
ndeadly 2024-10-10 11:36:22 +02:00
parent 8cddf8fbeb
commit 43f405e3e6
17 changed files with 554 additions and 111 deletions

View file

@ -87,9 +87,9 @@ namespace ams::controller {
R_SUCCEED();
}
Result DualsenseController::SetVibration(const SwitchRumbleData *rumble_data) {
m_rumble_state.amp_motor_left = static_cast<u8>(255 * std::max(rumble_data[0].low_band_amp, rumble_data[1].low_band_amp));
m_rumble_state.amp_motor_right = static_cast<u8>(255 * std::max(rumble_data[0].high_band_amp, rumble_data[1].high_band_amp));
Result DualsenseController::SetVibration(const SwitchMotorData *motor_data) {
m_rumble_state.amp_motor_left = static_cast<u8>(255 * std::max(motor_data->left_motor.low_band_amp, motor_data->right_motor.low_band_amp));
m_rumble_state.amp_motor_right = static_cast<u8>(255 * std::max(motor_data->left_motor.high_band_amp, motor_data->right_motor.high_band_amp));
return this->PushRumbleLedState();
}

View file

@ -173,7 +173,7 @@ namespace ams::controller {
, m_rumble_state({0, 0}) { }
Result Initialize();
Result SetVibration(const SwitchRumbleData *rumble_data);
Result SetVibration(const SwitchMotorData *motor_data);
Result CancelVibration();
Result SetPlayerLed(u8 led_mask);
Result SetLightbarColour(RGBColour colour);

View file

@ -191,9 +191,9 @@ namespace ams::controller {
R_SUCCEED();
}
Result Dualshock3Controller::SetVibration(const SwitchRumbleData *rumble_data) {
m_rumble_state.amp_motor_left = static_cast<u8>(255 * std::max(rumble_data[0].low_band_amp, rumble_data[1].low_band_amp));
m_rumble_state.amp_motor_right = static_cast<u8>(255 * std::max(rumble_data[0].high_band_amp, rumble_data[1].high_band_amp));
Result Dualshock3Controller::SetVibration(const SwitchMotorData *motor_data) {
m_rumble_state.amp_motor_left = static_cast<u8>(255 * std::max(motor_data->left_motor.low_band_amp, motor_data->right_motor.low_band_amp));
m_rumble_state.amp_motor_right = static_cast<u8>(255 * std::max(motor_data->left_motor.high_band_amp, motor_data->right_motor.high_band_amp));
R_RETURN(this->PushRumbleLedState());
}

View file

@ -106,7 +106,7 @@ namespace ams::controller {
: EmulatedSwitchController(address, id) { }
Result Initialize(void);
Result SetVibration(const SwitchRumbleData *rumble_data);
Result SetVibration(const SwitchMotorData *motor_data);
Result CancelVibration();
Result SetPlayerLed(u8 led_mask);

View file

@ -65,9 +65,9 @@ namespace ams::controller {
R_SUCCEED();
}
Result Dualshock4Controller::SetVibration(const SwitchRumbleData *rumble_data) {
m_rumble_state.amp_motor_left = static_cast<u8>(255 * std::max(rumble_data[0].low_band_amp, rumble_data[1].low_band_amp));
m_rumble_state.amp_motor_right = static_cast<u8>(255 * std::max(rumble_data[0].high_band_amp, rumble_data[1].high_band_amp));
Result Dualshock4Controller::SetVibration(const SwitchMotorData *motor_data) {
m_rumble_state.amp_motor_left = static_cast<u8>(255 * std::max(motor_data->left_motor.low_band_amp, motor_data->right_motor.low_band_amp));
m_rumble_state.amp_motor_right = static_cast<u8>(255 * std::max(motor_data->left_motor.high_band_amp, motor_data->right_motor.high_band_amp));
R_RETURN(this->PushRumbleLedState());
}

View file

@ -211,7 +211,7 @@ namespace ams::controller {
, m_rumble_state({0, 0}) { }
Result Initialize();
Result SetVibration(const SwitchRumbleData *rumble_data);
Result SetVibration(const SwitchMotorData *motor_data);
Result CancelVibration();
Result SetPlayerLed(u8 led_mask);
Result SetLightbarColour(RGBColour colour);

View file

@ -21,72 +21,6 @@ namespace ams::controller {
namespace {
// Frequency in Hz rounded to nearest int
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md#frequency-table
constinit const u16 RumbleFreqLookup[] = {
0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, 0x0030, 0x0031,
0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0039, 0x003a, 0x003b,
0x003c, 0x003e, 0x003f, 0x0040, 0x0042, 0x0043, 0x0045, 0x0046, 0x0048,
0x0049, 0x004b, 0x004d, 0x004e, 0x0050, 0x0052, 0x0054, 0x0055, 0x0057,
0x0059, 0x005b, 0x005d, 0x005f, 0x0061, 0x0063, 0x0066, 0x0068, 0x006a,
0x006c, 0x006f, 0x0071, 0x0074, 0x0076, 0x0079, 0x007b, 0x007e, 0x0081,
0x0084, 0x0087, 0x0089, 0x008d, 0x0090, 0x0093, 0x0096, 0x0099, 0x009d,
0x00a0, 0x00a4, 0x00a7, 0x00ab, 0x00ae, 0x00b2, 0x00b6, 0x00ba, 0x00be,
0x00c2, 0x00c7, 0x00cb, 0x00cf, 0x00d4, 0x00d9, 0x00dd, 0x00e2, 0x00e7,
0x00ec, 0x00f1, 0x00f7, 0x00fc, 0x0102, 0x0107, 0x010d, 0x0113, 0x0119,
0x011f, 0x0125, 0x012c, 0x0132, 0x0139, 0x0140, 0x0147, 0x014e, 0x0155,
0x015d, 0x0165, 0x016c, 0x0174, 0x017d, 0x0185, 0x018d, 0x0196, 0x019f,
0x01a8, 0x01b1, 0x01bb, 0x01c5, 0x01ce, 0x01d9, 0x01e3, 0x01ee, 0x01f8,
0x0203, 0x020f, 0x021a, 0x0226, 0x0232, 0x023e, 0x024b, 0x0258, 0x0265,
0x0272, 0x0280, 0x028e, 0x029c, 0x02ab, 0x02ba, 0x02c9, 0x02d9, 0x02e9,
0x02f9, 0x030a, 0x031b, 0x032c, 0x033e, 0x0350, 0x0363, 0x0376, 0x0389,
0x039d, 0x03b1, 0x03c6, 0x03db, 0x03f1, 0x0407, 0x041d, 0x0434, 0x044c,
0x0464, 0x047d, 0x0496, 0x04af, 0x04ca, 0x04e5
};
constexpr size_t RumbleFreqLookupSize = sizeof(RumbleFreqLookup) / sizeof(u16);
// Floats from dekunukem repo normalised and scaled by function used by yuzu
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md#amplitude-table
// https://github.com/yuzu-emu/yuzu/blob/d3a4a192fe26e251f521f0311b2d712f5db9918e/src/input_common/sdl/sdl_impl.cpp#L429
constinit const float RumbleAmpLookup[] = {
0.000000, 0.120576, 0.137846, 0.146006, 0.154745, 0.164139, 0.174246,
0.185147, 0.196927, 0.209703, 0.223587, 0.238723, 0.255268, 0.273420,
0.293398, 0.315462, 0.321338, 0.327367, 0.333557, 0.339913, 0.346441,
0.353145, 0.360034, 0.367112, 0.374389, 0.381870, 0.389564, 0.397476,
0.405618, 0.413996, 0.422620, 0.431501, 0.436038, 0.440644, 0.445318,
0.450062, 0.454875, 0.459764, 0.464726, 0.469763, 0.474876, 0.480068,
0.485342, 0.490694, 0.496130, 0.501649, 0.507256, 0.512950, 0.518734,
0.524609, 0.530577, 0.536639, 0.542797, 0.549055, 0.555413, 0.561872,
0.568436, 0.575106, 0.581886, 0.588775, 0.595776, 0.602892, 0.610127,
0.617482, 0.624957, 0.632556, 0.640283, 0.648139, 0.656126, 0.664248,
0.672507, 0.680906, 0.689447, 0.698135, 0.706971, 0.715957, 0.725098,
0.734398, 0.743857, 0.753481, 0.763273, 0.773235, 0.783370, 0.793684,
0.804178, 0.814858, 0.825726, 0.836787, 0.848044, 0.859502, 0.871165,
0.883035, 0.895119, 0.907420, 0.919943, 0.932693, 0.945673, 0.958889,
0.972345, 0.986048, 1.000000
};
constexpr size_t RumbleAmpLookupSize = sizeof(RumbleAmpLookup) / sizeof(float);
void DecodeRumbleValues(const u8 enc[], SwitchRumbleData *dec) {
u8 hi_freq_ind = 0x20 + (enc[0] >> 2) + ((enc[1] & 0x01) * 0x40) - 1;
u8 hi_amp_ind = (enc[1] & 0xfe) >> 1;
u8 lo_freq_ind = (enc[2] & 0x7f) - 1;
u8 lo_amp_ind = ((enc[3] - 0x40) << 1) + ((enc[2] & 0x80) >> 7);
if (!((hi_freq_ind < RumbleFreqLookupSize) &&
(hi_amp_ind < RumbleAmpLookupSize) &&
(lo_freq_ind < RumbleFreqLookupSize) &&
(lo_amp_ind < RumbleAmpLookupSize))) {
std::memset(dec, 0, sizeof(SwitchRumbleData));
return;
}
dec->high_band_freq = float(RumbleFreqLookup[hi_freq_ind]);
dec->high_band_amp = RumbleAmpLookup[hi_amp_ind];
dec->low_band_freq = float(RumbleFreqLookup[lo_freq_ind]);
dec->low_band_amp = RumbleAmpLookup[lo_amp_ind];
}
// CRC-8 with polynomial 0x7 for NFC/IR packets
constexpr u8 ComputeCrc8(const void *data, size_t size) {
return utils::Crc8<7>::Calculate(data, size);
@ -151,14 +85,14 @@ namespace ams::controller {
switch (output_report->id) {
case 0x01:
R_TRY(this->HandleRumbleData(&output_report->rumble_data));
R_TRY(this->HandleRumbleData(&output_report->enc_motor_data));
R_TRY(this->HandleHidCommand(&output_report->type0x01.hid_command));
break;
case 0x10:
R_TRY(this->HandleRumbleData(&output_report->rumble_data));
R_TRY(this->HandleRumbleData(&output_report->enc_motor_data));
break;
case 0x11:
R_TRY(this->HandleRumbleData(&output_report->rumble_data));
R_TRY(this->HandleRumbleData(&output_report->enc_motor_data));
//R_TRY(this->HandleNfcIrData(output_report->type0x11.nfc_ir_data));
break;
default:
@ -168,12 +102,12 @@ namespace ams::controller {
R_SUCCEED();
}
Result EmulatedSwitchController::HandleRumbleData(const SwitchRumbleDataEncoded *encoded) {
Result EmulatedSwitchController::HandleRumbleData(const SwitchEncodedMotorData *encoded_motor_data) {
if (m_enable_rumble) {
SwitchRumbleData rumble_data[2];
DecodeRumbleValues(encoded->left_motor, &rumble_data[0]);
DecodeRumbleValues(encoded->right_motor, &rumble_data[1]);
R_TRY(this->SetVibration(rumble_data));
SwitchMotorData motor_data;
if (m_rumble_handler.GetDecodedValues(encoded_motor_data, &motor_data)) {
R_TRY(this->SetVibration(&motor_data));
}
}
R_SUCCEED();

View file

@ -32,14 +32,14 @@ namespace ams::controller {
protected:
void ClearControllerState();
virtual Result SetVibration(const SwitchRumbleData *rumble_data) { AMS_UNUSED(rumble_data); R_SUCCEED(); }
virtual Result SetVibration(const SwitchMotorData *motor_data) { AMS_UNUSED(motor_data); R_SUCCEED(); }
virtual Result CancelVibration() { R_SUCCEED(); }
virtual Result SetPlayerLed(u8 led_mask) { AMS_UNUSED(led_mask); R_SUCCEED(); }
void UpdateControllerState(const bluetooth::HidReport *report) override;
virtual void ProcessInputData(const bluetooth::HidReport *report) { AMS_UNUSED(report); }
Result HandleRumbleData(const SwitchRumbleDataEncoded *encoded);
Result HandleRumbleData(const SwitchEncodedMotorData *enc_motor_data);
Result HandleHidCommand(const SwitchHidCommand *command);
Result HandleNfcIrData(const u8 *nfc_ir);
@ -80,6 +80,8 @@ namespace ams::controller {
u8 m_input_report_mode;
SwitchRumbleHandler m_rumble_handler;
bool m_enable_rumble;
bool m_enable_motion;

View file

@ -18,6 +18,7 @@
#include "../bluetooth_mitm/bluetooth/bluetooth_types.hpp"
#include "../bluetooth_mitm/bluetooth/bluetooth_hid_report.hpp"
#include "../async/future_response.hpp"
#include "switch_rumble_handler.hpp"
#include <queue>
namespace ams::controller {
@ -123,18 +124,6 @@ namespace ams::controller {
s16 z;
} PACKED;
struct SwitchRumbleDataEncoded {
u8 left_motor[4];
u8 right_motor[4];
} PACKED;
struct SwitchRumbleData {
float high_band_freq;
float high_band_amp;
float low_band_freq;
float low_band_amp;
} PACKED;
enum HidCommandType : u8 {
HidCommand_PairingOut = 0x01,
HidCommand_GetDeviceInfo = 0x02,
@ -307,7 +296,7 @@ namespace ams::controller {
struct SwitchOutputReport {
u8 id;
u8 counter;
SwitchRumbleDataEncoded rumble_data;
SwitchEncodedMotorData enc_motor_data;
union {
struct{

View file

@ -0,0 +1,313 @@
/*
* Copyright (c) 2020-2024 ndeadly
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "switch_rumble_decoder.hpp"
namespace ams::controller {
namespace {
constexpr float MinAmplitude = -8.0f;
constexpr float MaxAmplitude = 0.0f;
constexpr float DefaultAmplitude = MinAmplitude;
constexpr float MinFrequency = -2.0f;
constexpr float MaxFrequency = 2.0f;
constexpr float DefaultFrequency = 0.0f;
constexpr float CenterFreqHigh = 320.0f;
constexpr float CenterFreqLow = 160.0f;
constexpr float ExpBase2LookupResolution = 1.0f / 32;
constexpr float ExpBase2RangeStart = std::min(MinAmplitude, MinFrequency);
constexpr float ExpBase2RangeEnd = std::max(MaxAmplitude, MaxFrequency);
constexpr size_t ExpBase2LookupLength = (std::fabs(ExpBase2RangeEnd - ExpBase2RangeStart) + ExpBase2LookupResolution) / ExpBase2LookupResolution;
constexpr std::array<float, ExpBase2LookupLength> ExpBase2Lookup = []() {
std::array<float, ExpBase2LookupLength> table = {};
constexpr float AmplitudeThreshold = -7.9375f;
for (size_t i = 0; i < table.size(); ++i) {
float f = ExpBase2RangeStart + i * ExpBase2LookupResolution;
if (f >= AmplitudeThreshold) {
table[i] = std::exp2f(f);
}
}
return table;
}();
constexpr u32 GetLookupIndex(float input) {
return (input - ExpBase2RangeStart) / ExpBase2LookupResolution;
}
constexpr std::array<float, 128> Am7BitLookup = []() {
std::array<float, 128> table = {};
for (size_t i = 0; i < table.size(); ++i) {
if (i == 0) {
table[i] = -8.0f;
} else if (i < 16) {
table[i] = 0.25f * i - 7.75f;
} else if (i < 32) {
table[i] = 0.0625f * i - 4.9375f;
} else {
table[i] = 0.03125f * i - 3.96875f;
}
}
return table;
}();
constexpr std::array<float, 128> Fm7BitLookup = []() {
std::array<float, 128> table = {};
for (size_t i = 0; i < table.size(); ++i) {
table[i] = 0.03125f * i - 2.0f;
}
return table;
}();
enum Switch5BitAction : u8 {
Switch5BitAction_Ignore = 0x0,
Switch5BitAction_Default = 0x1,
Switch5BitAction_Substitute = 0x2,
Switch5BitAction_Sum = 0x3,
};
struct Switch5BitCommand {
Switch5BitAction am_action;
Switch5BitAction fm_action;
float am_offset;
float fm_offset;
};
constexpr Switch5BitCommand CommandTable[] = {
{ .am_action = Switch5BitAction_Default, .fm_action = Switch5BitAction_Default, .am_offset = 0.0f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Substitute, .fm_action = Switch5BitAction_Ignore, .am_offset = 0.0f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Substitute, .fm_action = Switch5BitAction_Ignore, .am_offset = -0.5f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Substitute, .fm_action = Switch5BitAction_Ignore, .am_offset = -1.0f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Substitute, .fm_action = Switch5BitAction_Ignore, .am_offset = -1.5f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Substitute, .fm_action = Switch5BitAction_Ignore, .am_offset = -2.0f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Substitute, .fm_action = Switch5BitAction_Ignore, .am_offset = -2.5f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Substitute, .fm_action = Switch5BitAction_Ignore, .am_offset = -3.0f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Substitute, .fm_action = Switch5BitAction_Ignore, .am_offset = -3.5f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Substitute, .fm_action = Switch5BitAction_Ignore, .am_offset = -4.0f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Substitute, .fm_action = Switch5BitAction_Ignore, .am_offset = -4.5f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Substitute, .fm_action = Switch5BitAction_Ignore, .am_offset = -5.0f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Ignore, .fm_action = Switch5BitAction_Substitute, .am_offset = 0.0f, .fm_offset = -0.375f },
{ .am_action = Switch5BitAction_Ignore, .fm_action = Switch5BitAction_Substitute, .am_offset = 0.0f, .fm_offset = -0.1875f },
{ .am_action = Switch5BitAction_Ignore, .fm_action = Switch5BitAction_Substitute, .am_offset = 0.0f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Ignore, .fm_action = Switch5BitAction_Substitute, .am_offset = 0.0f, .fm_offset = 0.1875f },
{ .am_action = Switch5BitAction_Ignore, .fm_action = Switch5BitAction_Substitute, .am_offset = 0.0f, .fm_offset = 0.375f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Sum, .am_offset = 0.125f, .fm_offset = 0.03125f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Ignore, .am_offset = 0.125f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Sum, .am_offset = 0.125f, .fm_offset = -0.03125f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Sum, .am_offset = 0.03125f, .fm_offset = 0.03125f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Ignore, .am_offset = 0.03125f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Sum, .am_offset = 0.03125f, .fm_offset = -0.03125f },
{ .am_action = Switch5BitAction_Ignore, .fm_action = Switch5BitAction_Sum, .am_offset = 0.0f, .fm_offset = 0.03125f },
{ .am_action = Switch5BitAction_Ignore, .fm_action = Switch5BitAction_Ignore, .am_offset = 0.0f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Ignore, .fm_action = Switch5BitAction_Sum, .am_offset = 0.0f, .fm_offset = -0.03125f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Sum, .am_offset = -0.03125f, .fm_offset = 0.03125f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Ignore, .am_offset = -0.03125f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Sum, .am_offset = -0.03125f, .fm_offset = -0.03125f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Sum, .am_offset = -0.125f, .fm_offset = 0.03125f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Ignore, .am_offset = -0.125f, .fm_offset = 0.0f },
{ .am_action = Switch5BitAction_Sum, .fm_action = Switch5BitAction_Sum, .am_offset = -0.125f, .fm_offset = -0.03125f }
};
float ApplyCommand(Switch5BitAction action, float offset, float current_val, float default_val, float min, float max) {
switch (action) {
case Switch5BitAction_Ignore: return current_val;
case Switch5BitAction_Substitute: return offset;
case Switch5BitAction_Sum: return std::clamp(current_val + offset, min, max);
default: return default_val;
}
}
ALWAYS_INLINE float ApplyAmCommand(u8 amfm_code, float current_val) {
return ApplyCommand(CommandTable[amfm_code].am_action, CommandTable[amfm_code].am_offset, current_val, DefaultAmplitude, MinAmplitude, MaxAmplitude);
}
ALWAYS_INLINE float ApplyFmCommand(u8 amfm_code, float current_val) {
return ApplyCommand(CommandTable[amfm_code].fm_action, CommandTable[amfm_code].fm_offset, current_val, DefaultFrequency, MinFrequency, MaxFrequency);
}
}
SwitchRumbleDecoder::SwitchRumbleDecoder() {
m_state = {
.lo_amp_linear = DefaultAmplitude,
.lo_freq_linear = DefaultFrequency,
.hi_amp_linear = DefaultAmplitude,
.hi_freq_linear = DefaultFrequency
};
}
void SwitchRumbleDecoder::DecodeSamples(const SwitchEncodedVibrationSamples* encoded, SwitchVibrationSamples* decoded) {
switch (encoded->packet_type) {
case 0:
decoded->count = 0;
break;
case 1:
if (encoded->one5bit.reserved == 0) {
this->DecodeOne5Bit(encoded, decoded);
} else if (encoded->one7bit.reserved == 0) {
this->DecodeOne7Bit(encoded, decoded);
} else {
this->DecodeThree7Bit(encoded, decoded);
}
break;
case 2:
if (encoded->two5bit.reserved == 0) {
this->DecodeTwo5Bit(encoded, decoded);
} else {
this->DecodeTwo7Bit(encoded, decoded);
}
break;
case 3:
this->DecodeThree5Bit(encoded, decoded);
break;
AMS_UNREACHABLE_DEFAULT_CASE();
};
}
void SwitchRumbleDecoder::DecodeOne5Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded) {
m_state.lo_amp_linear = ApplyAmCommand(encoded->one5bit.amfm_5bit_lo, m_state.lo_amp_linear);
m_state.lo_freq_linear = ApplyFmCommand(encoded->one5bit.amfm_5bit_lo, m_state.lo_freq_linear);
m_state.hi_amp_linear = ApplyAmCommand(encoded->one5bit.amfm_5bit_hi, m_state.hi_amp_linear);
m_state.hi_freq_linear = ApplyFmCommand(encoded->one5bit.amfm_5bit_hi, m_state.hi_freq_linear);
this->GetCurrentOutputValue(&decoded->samples[0]);
decoded->count = 1;
}
void SwitchRumbleDecoder::DecodeOne7Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded) {
m_state.lo_amp_linear = Am7BitLookup[encoded->one7bit.am_7bit_lo];
m_state.lo_freq_linear = Fm7BitLookup[encoded->one7bit.fm_7bit_lo];
m_state.hi_amp_linear = Am7BitLookup[encoded->one7bit.am_7bit_hi];
m_state.hi_freq_linear = Fm7BitLookup[encoded->one7bit.fm_7bit_hi];
this->GetCurrentOutputValue(&decoded->samples[0]);
decoded->count = 1;
}
void SwitchRumbleDecoder::DecodeTwo5Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded) {
m_state.lo_amp_linear = ApplyAmCommand(encoded->two5bit.amfm_5bit_lo_0, m_state.lo_amp_linear);
m_state.lo_freq_linear = ApplyFmCommand(encoded->two5bit.amfm_5bit_lo_0, m_state.lo_freq_linear);
m_state.hi_amp_linear = ApplyAmCommand(encoded->two5bit.amfm_5bit_hi_0, m_state.hi_amp_linear);
m_state.hi_freq_linear = ApplyFmCommand(encoded->two5bit.amfm_5bit_hi_0, m_state.hi_freq_linear);
this->GetCurrentOutputValue(&decoded->samples[0]);
m_state.lo_amp_linear = ApplyAmCommand(encoded->two5bit.amfm_5bit_lo_1, m_state.lo_amp_linear);
m_state.lo_freq_linear = ApplyFmCommand(encoded->two5bit.amfm_5bit_lo_1, m_state.lo_freq_linear);
m_state.hi_amp_linear = ApplyAmCommand(encoded->two5bit.amfm_5bit_hi_1, m_state.hi_amp_linear);
m_state.hi_freq_linear = ApplyFmCommand(encoded->two5bit.amfm_5bit_hi_1, m_state.hi_freq_linear);
this->GetCurrentOutputValue(&decoded->samples[1]);
decoded->count = 2;
}
void SwitchRumbleDecoder::DecodeTwo7Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded) {
if (encoded->two7bit.high_select) {
m_state.hi_amp_linear = Am7BitLookup[encoded->two7bit.am_7bit_xx];
m_state.hi_freq_linear = Fm7BitLookup[encoded->two7bit.fm_7bit_xx];
m_state.lo_amp_linear = ApplyAmCommand(encoded->two7bit.amfm_5bit_xx_0, m_state.lo_amp_linear);
m_state.lo_freq_linear = ApplyFmCommand(encoded->two7bit.amfm_5bit_xx_0, m_state.lo_freq_linear);
} else {
m_state.lo_amp_linear = Am7BitLookup[encoded->two7bit.am_7bit_xx];
m_state.lo_freq_linear = Fm7BitLookup[encoded->two7bit.fm_7bit_xx];
m_state.hi_amp_linear = ApplyAmCommand(encoded->two7bit.amfm_5bit_xx_0, m_state.hi_amp_linear);
m_state.hi_freq_linear = ApplyFmCommand(encoded->two7bit.amfm_5bit_xx_0, m_state.hi_freq_linear);
}
this->GetCurrentOutputValue(&decoded->samples[0]);
m_state.lo_amp_linear = ApplyAmCommand(encoded->two7bit.amfm_5bit_lo_1, m_state.lo_amp_linear);
m_state.lo_freq_linear = ApplyFmCommand(encoded->two7bit.amfm_5bit_lo_1, m_state.lo_freq_linear);
m_state.hi_amp_linear = ApplyAmCommand(encoded->two7bit.amfm_5bit_hi_1, m_state.hi_amp_linear);
m_state.hi_freq_linear = ApplyFmCommand(encoded->two7bit.amfm_5bit_hi_1, m_state.hi_freq_linear);
this->GetCurrentOutputValue(&decoded->samples[1]);
decoded->count = 2;
}
void SwitchRumbleDecoder::DecodeThree5Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded) {
m_state.lo_amp_linear = ApplyAmCommand(encoded->three5bit.amfm_5bit_lo_0, m_state.lo_amp_linear);
m_state.lo_freq_linear = ApplyFmCommand(encoded->three5bit.amfm_5bit_lo_0, m_state.lo_freq_linear);
m_state.hi_amp_linear = ApplyAmCommand(encoded->three5bit.amfm_5bit_hi_0, m_state.hi_amp_linear);
m_state.hi_freq_linear = ApplyFmCommand(encoded->three5bit.amfm_5bit_hi_0, m_state.hi_freq_linear);
this->GetCurrentOutputValue(&decoded->samples[0]);
m_state.lo_amp_linear = ApplyAmCommand(encoded->three5bit.amfm_5bit_lo_1, m_state.lo_amp_linear);
m_state.lo_freq_linear = ApplyFmCommand(encoded->three5bit.amfm_5bit_lo_1, m_state.lo_freq_linear);
m_state.hi_amp_linear = ApplyAmCommand(encoded->three5bit.amfm_5bit_hi_1, m_state.hi_amp_linear);
m_state.hi_freq_linear = ApplyFmCommand(encoded->three5bit.amfm_5bit_hi_1, m_state.hi_freq_linear);
this->GetCurrentOutputValue(&decoded->samples[1]);
m_state.lo_amp_linear = ApplyAmCommand(encoded->three5bit.amfm_5bit_lo_2, m_state.lo_amp_linear);
m_state.lo_freq_linear = ApplyFmCommand(encoded->three5bit.amfm_5bit_lo_2, m_state.lo_freq_linear);
m_state.hi_amp_linear = ApplyAmCommand(encoded->three5bit.amfm_5bit_hi_2, m_state.hi_amp_linear);
m_state.hi_freq_linear = ApplyFmCommand(encoded->three5bit.amfm_5bit_hi_2, m_state.hi_freq_linear);
this->GetCurrentOutputValue(&decoded->samples[2]);
decoded->count = 3;
}
void SwitchRumbleDecoder::DecodeThree7Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded) {
if (encoded->three7bit.high_select) {
if (encoded->three7bit.freq_select) {
m_state.hi_freq_linear = Fm7BitLookup[encoded->three7bit.xx_7bit_xx];
} else {
m_state.hi_amp_linear = Am7BitLookup[encoded->three7bit.xx_7bit_xx];
}
} else {
if (encoded->three7bit.freq_select) {
m_state.lo_freq_linear = Fm7BitLookup[encoded->three7bit.xx_7bit_xx];
} else {
m_state.lo_amp_linear = Am7BitLookup[encoded->three7bit.xx_7bit_xx];
}
}
this->GetCurrentOutputValue(&decoded->samples[0]);
m_state.lo_amp_linear = ApplyAmCommand(encoded->three7bit.amfm_5bit_lo_1, m_state.lo_amp_linear);
m_state.lo_freq_linear = ApplyFmCommand(encoded->three7bit.amfm_5bit_lo_1, m_state.lo_freq_linear);
m_state.hi_amp_linear = ApplyAmCommand(encoded->three7bit.amfm_5bit_hi_1, m_state.hi_amp_linear);
m_state.hi_freq_linear = ApplyFmCommand(encoded->three7bit.amfm_5bit_hi_1, m_state.hi_freq_linear);
this->GetCurrentOutputValue(&decoded->samples[1]);
m_state.lo_amp_linear = ApplyAmCommand(encoded->three7bit.amfm_5bit_lo_2, m_state.lo_amp_linear);
m_state.lo_freq_linear = ApplyFmCommand(encoded->three7bit.amfm_5bit_lo_2, m_state.lo_freq_linear);
m_state.hi_amp_linear = ApplyAmCommand(encoded->three7bit.amfm_5bit_hi_2, m_state.hi_amp_linear);
m_state.hi_freq_linear = ApplyFmCommand(encoded->three7bit.amfm_5bit_hi_2, m_state.hi_freq_linear);
this->GetCurrentOutputValue(&decoded->samples[2]);
decoded->count = 3;
}
void SwitchRumbleDecoder::GetCurrentOutputValue(SwitchVibrationValues* output) {
output->low_band_amp = ExpBase2Lookup[GetLookupIndex(m_state.lo_amp_linear)];
output->low_band_freq = ExpBase2Lookup[GetLookupIndex(m_state.lo_freq_linear)] * CenterFreqLow;
output->high_band_amp = ExpBase2Lookup[GetLookupIndex(m_state.hi_amp_linear)];
output->high_band_freq = ExpBase2Lookup[GetLookupIndex(m_state.hi_freq_linear)] * CenterFreqHigh;
}
}

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2020-2024 ndeadly
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::controller {
struct SwitchVibrationValues {
float low_band_amp;
float low_band_freq;
float high_band_amp;
float high_band_freq;
};
struct SwitchVibrationSamples {
u8 count;
SwitchVibrationValues samples[3];
};
struct SwitchEncodedVibrationSamples {
union {
struct {
u32 data : 30;
u32 packet_type : 2;
};
struct {
u32 reserved : 20; // Zero padding
u32 amfm_5bit_hi : 5; // 5-bit amfm hi [0]
u32 amfm_5bit_lo : 5; // 5-bit amfm lo [0]
u32 packet_type : 2; // 1
} one5bit;
struct {
u32 reserved : 2; // Zero padding
u32 fm_7bit_hi : 7; // 7-bit fm hi [0]
u32 am_7bit_hi : 7; // 7-bit am hi [0]
u32 fm_7bit_lo : 7; // 7-bit fm lo [0]
u32 am_7bit_lo : 7; // 7-bit am lo [0]
u32 packet_type : 2; // 1
} one7bit;
struct {
u32 reserved : 10; // Zero padding
u32 amfm_5bit_hi_1 : 5; // 5-bit amfm hi [1]
u32 amfm_5bit_lo_1 : 5; // 5-bit amfm lo [1]
u32 amfm_5bit_hi_0 : 5; // 5-bit amfm hi [0]
u32 amfm_5bit_lo_0 : 5; // 5-bit amfm lo [0]
u32 packet_type : 2; // 2
} two5bit;
struct {
u32 high_select : 1; // Whether 7-bit values are high or low
u32 fm_7bit_xx : 7; // 7-bit fm hi/lo [0], hi or lo denoted by high_select bit
u32 amfm_5bit_hi_1 : 5; // 5-bit amfm hi [1]
u32 amfm_5bit_lo_1 : 5; // 5-bit amfm lo [1]
u32 amfm_5bit_xx_0 : 5; // 5-bit amfm lo/hi [0], denoted by ~high_select
u32 am_7bit_xx : 7; // 7-bit am hi/lo [0], hi or lo denoted by high_select bit
u32 packet_type : 2; // 2
} two7bit;
struct {
u32 amfm_5bit_hi_2 : 5; // 5-bit amfm hi [2]
u32 amfm_5bit_lo_2 : 5; // 5-bit amfm lo [2]
u32 amfm_5bit_hi_1 : 5; // 5-bit amfm hi [1]
u32 amfm_5bit_lo_1 : 5; // 5-bit amfm lo [1]
u32 amfm_5bit_hi_0 : 5; // 5-bit amfm hi [0]
u32 amfm_5bit_lo_0 : 5; // 5-bit amfm lo [0]
u32 packet_type : 2; // 3
} three5bit;
struct {
u32 high_select : 1; // Whether 7-bit value is high or low
u32 : 1; // Always 1
u32 freq_select : 1; // Whether 7-bit value is freq or amp
u32 amfm_5bit_hi_2 : 5; // 5-bit amfm hi [2]
u32 amfm_5bit_lo_2 : 5; // 5-bit amfm lo [2]
u32 amfm_5bit_hi_1 : 5; // 5-bit amfm hi [1]
u32 amfm_5bit_lo_1 : 5; // 5-bit amfm lo [1]
u32 xx_7bit_xx : 7; // 7-bit am/fm lo/hi [0], denoted by freq_select and high_select bits
u32 packet_type : 2; // 1
} three7bit;
};
} PACKED;
class SwitchRumbleDecoder {
public:
SwitchRumbleDecoder();
void DecodeSamples(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded);
void GetCurrentOutputValue(SwitchVibrationValues *output);
private:
void DecodeOne5Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded);
void DecodeOne7Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded);
void DecodeTwo5Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded);
void DecodeTwo7Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded);
void DecodeThree5Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded);
void DecodeThree7Bit(const SwitchEncodedVibrationSamples *encoded, SwitchVibrationSamples *decoded);
private:
struct {
float lo_amp_linear;
float lo_freq_linear;
float hi_amp_linear;
float hi_freq_linear;
} m_state;
};
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2020-2024 ndeadly
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "switch_rumble_handler.hpp"
namespace ams::controller {
bool SwitchRumbleHandler::GetDecodedValues(const SwitchEncodedMotorData *encoded, SwitchMotorData *decoded) {
return this->GetNextDecodedValue(&m_decoder_left, &encoded->left_motor, &decoded->left_motor) | this->GetNextDecodedValue(&m_decoder_right, &encoded->right_motor, &decoded->right_motor);
}
bool SwitchRumbleHandler::GetNextDecodedValue(SwitchRumbleDecoder *decoder, const SwitchEncodedVibrationSamples *encoded_samples, SwitchVibrationValues *out_sample) {
SwitchVibrationSamples decoded_samples;
decoder->DecodeSamples(encoded_samples, &decoded_samples);
if (decoded_samples.count > 0) {
// We will just take the first decoded sample and ignore the others
*out_sample = decoded_samples.samples[0];
} else {
// Repeat the current sample if no new samples were decoded
decoder->GetCurrentOutputValue(out_sample);
}
return decoded_samples.count > 0;
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020-2024 ndeadly
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "switch_rumble_decoder.hpp"
namespace ams::controller {
struct SwitchEncodedMotorData {
SwitchEncodedVibrationSamples left_motor;
SwitchEncodedVibrationSamples right_motor;
} PACKED;
struct SwitchMotorData {
SwitchVibrationValues left_motor;
SwitchVibrationValues right_motor;
};
class SwitchRumbleHandler {
public:
bool GetDecodedValues(const SwitchEncodedMotorData *encoded, SwitchMotorData *decoded);
private:
bool GetNextDecodedValue(SwitchRumbleDecoder *decoder, const SwitchEncodedVibrationSamples *encoded_samples, SwitchVibrationValues *out_sample);
private:
SwitchRumbleDecoder m_decoder_left;
SwitchRumbleDecoder m_decoder_right;
};
}

View file

@ -914,11 +914,11 @@ namespace ams::controller {
R_SUCCEED();
}
Result WiiController::SetVibration(const SwitchRumbleData *rumble_data) {
m_rumble_state = rumble_data[0].low_band_amp > 0 ||
rumble_data[0].high_band_amp > 0 ||
rumble_data[1].low_band_amp > 0 ||
rumble_data[1].high_band_amp > 0;
Result WiiController::SetVibration(const SwitchMotorData *motor_data) {
m_rumble_state = motor_data->left_motor.low_band_amp > 0 ||
motor_data->left_motor.high_band_amp > 0 ||
motor_data->right_motor.low_band_amp > 0 ||
motor_data->right_motor.high_band_amp > 0;
std::scoped_lock lk(m_output_mutex);

View file

@ -500,7 +500,7 @@ namespace ams::controller {
, m_mp_state_changing(false) { }
Result Initialize();
Result SetVibration(const SwitchRumbleData *rumble_data);
Result SetVibration(const SwitchMotorData *motor_data);
Result CancelVibration();
Result SetPlayerLed(u8 led_mask);
void ProcessInputData(const bluetooth::HidReport *report) override;

View file

@ -24,13 +24,13 @@ namespace ams::controller {
}
Result XboxOneController::SetVibration(const SwitchRumbleData *rumble_data) {
Result XboxOneController::SetVibration(const SwitchMotorData *motor_data) {
auto report = reinterpret_cast<XboxOneReportData *>(m_output_report.data);
m_output_report.size = sizeof(XboxOneOutputReport0x03) + 1;
report->id = 0x03;
report->output0x03.enable = 0x3;
report->output0x03.magnitude_strong = static_cast<u8>(100 * std::max(rumble_data[0].low_band_amp, rumble_data[1].low_band_amp));
report->output0x03.magnitude_weak = static_cast<u8>(100 * std::max(rumble_data[0].high_band_amp, rumble_data[1].high_band_amp));
report->output0x03.magnitude_strong = static_cast<u8>(100 * std::max(motor_data->left_motor.low_band_amp, motor_data->right_motor.low_band_amp));
report->output0x03.magnitude_weak = static_cast<u8>(100 * std::max(motor_data->left_motor.high_band_amp, motor_data->right_motor.high_band_amp));
report->output0x03.pulse_sustain_10ms = 1;
report->output0x03.pulse_release_10ms = 0;
report->output0x03.loop_count = 0;

View file

@ -140,7 +140,7 @@ namespace ams::controller {
XboxOneController(const bluetooth::Address *address, HardwareID id)
: EmulatedSwitchController(address, id) { }
Result SetVibration(const SwitchRumbleData *rumble_data);
Result SetVibration(const SwitchMotorData *motor_data);
void ProcessInputData(const bluetooth::HidReport *report) override;
private: