diff --git a/src/core-tests/CMakeLists.txt b/src/core-tests/CMakeLists.txt index 99f02f03c..1cd4406a6 100644 --- a/src/core-tests/CMakeLists.txt +++ b/src/core-tests/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable(core-tests cheats_tests.cpp cpu_disasm_tests.cpp lightgun_controller_tests.cpp + negcon_base_tests.cpp spu_tests.cpp stub_host.cpp ) diff --git a/src/core-tests/core-tests.vcxproj b/src/core-tests/core-tests.vcxproj index 3dce54202..0828eeccf 100644 --- a/src/core-tests/core-tests.vcxproj +++ b/src/core-tests/core-tests.vcxproj @@ -6,6 +6,7 @@ + @@ -43,4 +44,4 @@ - + \ No newline at end of file diff --git a/src/core-tests/core-tests.vcxproj.filters b/src/core-tests/core-tests.vcxproj.filters index fb8c94920..f3c8cf4cb 100644 --- a/src/core-tests/core-tests.vcxproj.filters +++ b/src/core-tests/core-tests.vcxproj.filters @@ -6,6 +6,8 @@ + + diff --git a/src/core-tests/negcon_base_tests.cpp b/src/core-tests/negcon_base_tests.cpp new file mode 100644 index 000000000..f42b43732 --- /dev/null +++ b/src/core-tests/negcon_base_tests.cpp @@ -0,0 +1,233 @@ +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin and contributors. +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "core/negcon.h" +#include "core/negcon_base.h" +#include "core/negcon_rumble.h" +#include "core/save_state_version.h" + +#include "util/ini_settings_interface.h" +#include "util/state_wrapper.h" + +#include +#include +#include +#include +#include +#include + +static_assert(std::is_base_of_v); +static_assert(std::is_base_of_v); +static_assert(static_cast(NeGcon::Button::Count) == 8); +static_assert(static_cast(NeGconRumble::Button::Analog) == 8); +static_assert(static_cast(NeGconRumble::Button::Count) == 9); + +static const Controller::ControllerBindingInfo* FindBinding(const Controller::ControllerInfo& info, + std::string_view name); +static bool HasSetting(const Controller::ControllerInfo& info, std::string_view name); + +TEST(NegConBase, BindingIndices) +{ + const Controller::ControllerBindingInfo* negcon_steering_left = FindBinding(NeGcon::INFO, "SteeringLeft"); + const Controller::ControllerBindingInfo* rumble_analog = FindBinding(NeGconRumble::INFO, "Analog"); + const Controller::ControllerBindingInfo* rumble_steering_left = FindBinding(NeGconRumble::INFO, "SteeringLeft"); + const Controller::ControllerBindingInfo* rumble_large_motor = FindBinding(NeGconRumble::INFO, "LargeMotor"); + const Controller::ControllerBindingInfo* rumble_mode_led = FindBinding(NeGconRumble::INFO, "ModeLED"); + + ASSERT_NE(negcon_steering_left, nullptr); + ASSERT_NE(rumble_analog, nullptr); + ASSERT_NE(rumble_steering_left, nullptr); + ASSERT_NE(rumble_large_motor, nullptr); + ASSERT_NE(rumble_mode_led, nullptr); + EXPECT_EQ(negcon_steering_left->bind_index, 8u); + EXPECT_EQ(rumble_analog->bind_index, 8u); + EXPECT_EQ(rumble_steering_left->bind_index, 9u); + EXPECT_EQ(rumble_large_motor->bind_index, 14u); + EXPECT_EQ(rumble_mode_led->bind_index, 16u); +} + +TEST(NegConBase, CommonButtonMappings) +{ + NeGcon negcon(0); + NeGconRumble rumble(0); + + negcon.SetBindState(static_cast(NeGcon::Button::Up), 1.0f); + negcon.SetBindState(static_cast(NeGcon::Button::A), 1.0f); + rumble.SetBindState(static_cast(NeGconRumble::Button::Up), 1.0f); + rumble.SetBindState(static_cast(NeGconRumble::Button::A), 1.0f); + + EXPECT_EQ(negcon.GetButtonStateBits(), UINT16_C(0x2010)); + EXPECT_EQ(rumble.GetButtonStateBits(), UINT16_C(0x2010)); + EXPECT_EQ(negcon.GetBindState(static_cast(NeGcon::Button::Up)), 1.0f); + EXPECT_EQ(rumble.GetBindState(static_cast(NeGconRumble::Button::Up)), 1.0f); + EXPECT_EQ(rumble.GetBindState(static_cast(NeGconRumble::Button::Analog)), 0.0f); +} + +TEST(NegConBase, DefaultAxisModifiers) +{ + NeGcon negcon(0); + NeGconRumble rumble(0); + constexpr u32 negcon_axis_start = static_cast(NeGcon::Button::Count); + constexpr u32 rumble_axis_start = static_cast(NeGconRumble::Button::Count); + + negcon.SetBindState(negcon_axis_start + static_cast(NeGcon::HalfAxis::SteeringRight), 1.0f); + rumble.SetBindState(rumble_axis_start + static_cast(NeGconRumble::HalfAxis::SteeringRight), 1.0f); + negcon.SetBindState(negcon_axis_start + static_cast(NeGcon::HalfAxis::I), 0.5f); + rumble.SetBindState(rumble_axis_start + static_cast(NeGconRumble::HalfAxis::I), 0.5f); + + ASSERT_TRUE(negcon.GetAnalogInputBytes().has_value()); + ASSERT_TRUE(rumble.GetAnalogInputBytes().has_value()); + EXPECT_EQ(negcon.GetAnalogInputBytes(), rumble.GetAnalogInputBytes()); + EXPECT_EQ(negcon.GetAnalogInputBytes().value(), UINT32_C(0x000080FF)); + EXPECT_EQ(negcon.GetBindState(negcon_axis_start + static_cast(NeGcon::HalfAxis::SteeringRight)), 1.0f); + EXPECT_EQ(rumble.GetBindState(rumble_axis_start + static_cast(NeGconRumble::HalfAxis::SteeringRight)), 1.0f); +} + +TEST(NegConBase, ConfiguredAxisModifiers) +{ + INISettingsInterface si; + si.SetFloatValue("Controller", "SteeringDeadzone", 0.2f); + si.SetFloatValue("Controller", "SteeringSaturation", 0.8f); + si.SetFloatValue("Controller", "SteeringScaling", 0.5f); + si.SetFloatValue("Controller", "IDeadzone", 0.1f); + si.SetFloatValue("Controller", "ISaturation", 0.9f); + si.SetFloatValue("Controller", "ILinearity", 0.69314718f); + si.SetFloatValue("Controller", "IScaling", 0.5f); + si.SetFloatValue("Controller", "IIDeadzone", 0.25f); + si.SetFloatValue("Controller", "IISaturation", 0.75f); + si.SetFloatValue("Controller", "LDeadzone", 0.0f); + si.SetFloatValue("Controller", "LSaturation", 0.5f); + si.SetFloatValue("Controller", "LScaling", 0.25f); + + NeGcon negcon(0); + NeGconRumble rumble(0); + negcon.LoadSettings(si, "Controller", true); + rumble.LoadSettings(si, "Controller", true); + + constexpr u32 negcon_axis_start = static_cast(NeGcon::Button::Count); + constexpr u32 rumble_axis_start = static_cast(NeGconRumble::Button::Count); + negcon.SetBindState(negcon_axis_start + static_cast(NeGcon::HalfAxis::SteeringRight), 0.5f); + rumble.SetBindState(rumble_axis_start + static_cast(NeGconRumble::HalfAxis::SteeringRight), 0.5f); + negcon.SetBindState(negcon_axis_start + static_cast(NeGcon::HalfAxis::I), 0.5f); + rumble.SetBindState(rumble_axis_start + static_cast(NeGconRumble::HalfAxis::I), 0.5f); + negcon.SetBindState(negcon_axis_start + static_cast(NeGcon::HalfAxis::II), 0.5f); + rumble.SetBindState(rumble_axis_start + static_cast(NeGconRumble::HalfAxis::II), 0.5f); + negcon.SetBindState(negcon_axis_start + static_cast(NeGcon::HalfAxis::L), 0.5f); + rumble.SetBindState(rumble_axis_start + static_cast(NeGconRumble::HalfAxis::L), 0.5f); + + ASSERT_TRUE(negcon.GetAnalogInputBytes().has_value()); + ASSERT_TRUE(rumble.GetAnalogInputBytes().has_value()); + EXPECT_EQ(negcon.GetAnalogInputBytes(), rumble.GetAnalogInputBytes()); + EXPECT_EQ(negcon.GetAnalogInputBytes().value(), UINT32_C(0x408020A0)); +} + +TEST(NegConBase, RumbleSettings) +{ + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "SteeringDeadzone")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "SteeringSaturation")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "SteeringLinearity")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "SteeringScaling")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "IDeadzone")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "ISaturation")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "ILinearity")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "IScaling")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "IIDeadzone")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "IISaturation")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "IILinearity")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "IIScaling")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "LDeadzone")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "LSaturation")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "LLinearity")); + EXPECT_TRUE(HasSetting(NeGconRumble::INFO, "LScaling")); + EXPECT_FALSE(HasSetting(NeGconRumble::INFO, "SteeringSensitivity")); +} + +TEST(NegConBase, DeviceStateLayouts) +{ + std::array state_data = {}; + + NeGcon negcon(0); + negcon.SetBindState(static_cast(NeGcon::Button::A), 1.0f); + StateWrapper negcon_sw(std::span(state_data), StateWrapper::Mode::Write, SAVE_STATE_VERSION); + ASSERT_TRUE(negcon.DoState(negcon_sw, true)); + ASSERT_FALSE(negcon_sw.HasError()); + EXPECT_EQ(negcon_sw.GetPosition(), 3u); + + u16 negcon_buttons; + std::memcpy(&negcon_buttons, state_data.data(), sizeof(negcon_buttons)); + EXPECT_EQ(negcon_buttons, UINT16_C(0xDFFF)); + + state_data.fill(0); + NeGconRumble rumble(0); + rumble.SetBindState(static_cast(NeGconRumble::Button::A), 1.0f); + StateWrapper rumble_sw(std::span(state_data), StateWrapper::Mode::Write, SAVE_STATE_VERSION); + ASSERT_TRUE(rumble.DoState(rumble_sw, true)); + ASSERT_FALSE(rumble_sw.HasError()); + EXPECT_EQ(rumble_sw.GetPosition(), 24u); + + u16 rumble_buttons; + std::memcpy(&rumble_buttons, &state_data[4], sizeof(rumble_buttons)); + EXPECT_EQ(rumble_buttons, UINT16_C(0xDFFF)); + + std::array legacy_state_data = {}; + NeGconRumble legacy_rumble(0); + legacy_rumble.SetBindState(static_cast(NeGconRumble::Button::A), 1.0f); + StateWrapper legacy_rumble_sw(std::span(legacy_state_data), StateWrapper::Mode::Read, 43); + ASSERT_TRUE(legacy_rumble.DoState(legacy_rumble_sw, true)); + ASSERT_FALSE(legacy_rumble_sw.HasError()); + EXPECT_EQ(legacy_rumble_sw.GetPosition(), legacy_state_data.size()); + EXPECT_EQ(legacy_rumble.GetButtonStateBits(), 0u); +} + +TEST(NegConBase, DeviceProtocols) +{ + NeGcon negcon(0); + negcon.SetBindState(static_cast(NeGcon::Button::A), 1.0f); + + u8 data_out; + EXPECT_TRUE(negcon.Transfer(0x01, &data_out)); + EXPECT_EQ(data_out, 0xFF); + EXPECT_TRUE(negcon.Transfer(0x42, &data_out)); + EXPECT_EQ(data_out, 0x23); + EXPECT_TRUE(negcon.Transfer(0x00, &data_out)); + EXPECT_EQ(data_out, 0x5A); + EXPECT_TRUE(negcon.Transfer(0x00, &data_out)); + EXPECT_EQ(data_out, 0xFF); + EXPECT_TRUE(negcon.Transfer(0x00, &data_out)); + EXPECT_EQ(data_out, 0xDF); + + NeGconRumble rumble(0); + rumble.SetBindState(static_cast(NeGconRumble::Button::A), 1.0f); + EXPECT_TRUE(rumble.Transfer(0x01, &data_out)); + EXPECT_EQ(data_out, 0xFF); + EXPECT_TRUE(rumble.Transfer(0x42, &data_out)); + EXPECT_EQ(data_out, 0x41); + EXPECT_TRUE(rumble.Transfer(0x00, &data_out)); + EXPECT_EQ(data_out, 0x5A); + EXPECT_TRUE(rumble.Transfer(0x00, &data_out)); + EXPECT_EQ(data_out, 0xFF); + EXPECT_FALSE(rumble.Transfer(0x00, &data_out)); + EXPECT_EQ(data_out, 0xDF); +} + +const Controller::ControllerBindingInfo* FindBinding(const Controller::ControllerInfo& info, std::string_view name) +{ + for (const Controller::ControllerBindingInfo& binding : info.bindings) + { + if (name == binding.name) + return &binding; + } + + return nullptr; +} + +bool HasSetting(const Controller::ControllerInfo& info, std::string_view name) +{ + for (const SettingInfo& setting : info.settings) + { + if (name == setting.name) + return true; + } + + return false; +} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b7574318f..063ea73dc 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -116,6 +116,8 @@ add_library(core multitap.h negcon.cpp negcon.h + negcon_base.cpp + negcon_base.h negcon_rumble.cpp negcon_rumble.h pad.cpp diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 763cc036f..6feb13f29 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -67,6 +67,7 @@ + @@ -164,6 +165,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 5c6fa0cba..e63425b9f 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -33,6 +33,7 @@ + @@ -112,6 +113,7 @@ + diff --git a/src/core/negcon.cpp b/src/core/negcon.cpp index d845fd3cf..60cc8e405 100644 --- a/src/core/negcon.cpp +++ b/src/core/negcon.cpp @@ -3,29 +3,17 @@ #include "negcon.h" #include "controller_helpers.h" -#include "host.h" -#include "system.h" #include "util/state_wrapper.h" #include "util/translation.h" #include "common/assert.h" #include "common/bitutils.h" -#include "common/log.h" -#include "common/settings_interface.h" #include "IconsPromptFont.h" -#include -#include - -// Mapping of Button to index of corresponding bit in m_button_state -static constexpr std::array(NeGcon::Button::Count)> s_button_indices = {3, 4, 5, 6, - 7, 11, 12, 13}; -NeGcon::NeGcon(u32 index) : Controller(index) +NeGcon::NeGcon(u32 index) : NegConBase(index, static_cast(Button::Count)) { - m_axis_state.fill(0x00); - m_axis_state[static_cast(Axis::Steering)] = 0x80; } NeGcon::~NeGcon() = default; @@ -51,116 +39,6 @@ bool NeGcon::DoState(StateWrapper& sw, bool apply_input_state) return true; } -float NeGcon::GetBindState(u32 index) const -{ - if (index == (static_cast(Button::Count) + static_cast(HalfAxis::SteeringLeft)) || - index == (static_cast(Button::Count) + static_cast(HalfAxis::SteeringRight))) - { - float value = m_axis_state[static_cast(Axis::Steering)]; - value = value - 128.0f; - value /= value < 0.0f ? 128.0f : 127.0f; - value = std::clamp(value, -1.0f, 1.0f); - if (index == (static_cast(Button::Count) + static_cast(HalfAxis::SteeringLeft))) - { - value *= -1.0f; - } - return std::max(0.0f, value); - } - else if (index >= static_cast(Button::Count)) - { - // less one because of the two steering axes - const u32 sub_index = index - (static_cast(Button::Count) + 1); - if (sub_index >= m_axis_state.size()) - return 0.0f; - - return static_cast(m_axis_state[sub_index]) * (1.0f / 255.0f); - } - else - { - const u32 bit = s_button_indices[index]; - return static_cast(((m_button_state >> bit) & 1u) ^ 1u); - } -} - -static float apply_axis_modifier(float value, const NeGcon::AxisModifier& axis_modifier) -{ - value = (value - axis_modifier.deadzone) / (axis_modifier.saturation - axis_modifier.deadzone); - value = std::clamp(value, 0.0f, 1.0f); - value = std::pow(value, std::exp(axis_modifier.linearity)); - return value; -} - -static u8 get_scaled_value(float value, const NeGcon::AxisModifier& axis_modifier) -{ - value = axis_modifier.scaling * axis_modifier.unit * value + axis_modifier.zero; - return static_cast(std::clamp(std::round(value), 0.0f, 255.0f)); -} - -void NeGcon::SetBindState(u32 index, float value) -{ - // Steering Axis: -1..1 -> 0..255 - if (index == (static_cast(Button::Count) + static_cast(HalfAxis::SteeringLeft)) || - index == (static_cast(Button::Count) + static_cast(HalfAxis::SteeringRight))) - { - value = apply_axis_modifier(value, m_steering_modifier); - - m_half_axis_state[index - static_cast(Button::Count)] = std::clamp(value, 0.0f, 1.0f); - - float merged = m_half_axis_state[1] - m_half_axis_state[0]; - m_axis_state[static_cast(Axis::Steering)] = get_scaled_value(merged, m_steering_modifier); - } - else if (index >= static_cast(Button::Count)) - { - // less one because of the two steering axes - const u32 sub_index = index - (static_cast(Button::Count) + 1); - if (sub_index >= m_axis_state.size()) - return; - - if (index >= (static_cast(Button::Count) + static_cast(HalfAxis::I)) && - index <= (static_cast(Button::Count) + static_cast(HalfAxis::L))) - { - const AxisModifier& axis_modifier = - m_half_axis_modifiers[index - (static_cast(Button::Count) + static_cast(HalfAxis::I))]; - value = apply_axis_modifier(value, axis_modifier); - m_axis_state[sub_index] = get_scaled_value(value, axis_modifier); - } - else - { - m_axis_state[sub_index] = static_cast(std::clamp(value * 255.0f, 0.0f, 255.0f)); - } - } - else if (index < static_cast(Button::Count)) - { - const u16 bit = u16(1) << s_button_indices[static_cast(index)]; - - if (value >= 0.5f) - { - if (m_button_state & bit) - System::SetRunaheadReplayFlag(false); - - m_button_state &= ~bit; - } - else - { - if (!(m_button_state & bit)) - System::SetRunaheadReplayFlag(false); - - m_button_state |= bit; - } - } -} - -u32 NeGcon::GetButtonStateBits() const -{ - return m_button_state ^ 0xFFFF; -} - -std::optional NeGcon::GetAnalogInputBytes() const -{ - return m_axis_state[static_cast(Axis::L)] << 24 | m_axis_state[static_cast(Axis::II)] << 16 | - m_axis_state[static_cast(Axis::I)] << 8 | m_axis_state[static_cast(Axis::Steering)]; -} - void NeGcon::ResetTransferState() { m_transfer_state = TransferState::Idle; @@ -337,40 +215,3 @@ const Controller::ControllerInfo NeGcon::INFO = {ControllerType::NeGcon, "images/controllers/negcon.svg", s_binding_info, s_settings}; - -void NeGcon::LoadSettings(const SettingsInterface& si, const char* section, bool initial) -{ - m_disable_socd = si.GetBoolValue(section, "DisableSOCD", false); - m_steering_modifier = { - .deadzone = si.GetFloatValue(section, "SteeringDeadzone", DEFAULT_STEERING_MODIFIER.deadzone), - .saturation = si.GetFloatValue(section, "SteeringSaturation", DEFAULT_STEERING_MODIFIER.saturation), - .linearity = si.GetFloatValue(section, "SteeringLinearity", DEFAULT_STEERING_MODIFIER.linearity), - .scaling = si.GetFloatValue(section, "SteeringScaling", DEFAULT_STEERING_MODIFIER.scaling), - .zero = DEFAULT_STEERING_MODIFIER.zero, - .unit = DEFAULT_STEERING_MODIFIER.unit, - }; - m_half_axis_modifiers[0] = { - .deadzone = si.GetFloatValue(section, "IDeadzone", DEFAULT_PEDAL_MODIFIER.deadzone), - .saturation = si.GetFloatValue(section, "ISaturation", DEFAULT_PEDAL_MODIFIER.saturation), - .linearity = si.GetFloatValue(section, "ILinearity", DEFAULT_PEDAL_MODIFIER.linearity), - .scaling = si.GetFloatValue(section, "IScaling", DEFAULT_PEDAL_MODIFIER.scaling), - .zero = DEFAULT_PEDAL_MODIFIER.zero, - .unit = DEFAULT_PEDAL_MODIFIER.unit, - }; - m_half_axis_modifiers[1] = { - .deadzone = si.GetFloatValue(section, "IIDeadzone", DEFAULT_PEDAL_MODIFIER.deadzone), - .saturation = si.GetFloatValue(section, "IISaturation", DEFAULT_PEDAL_MODIFIER.saturation), - .linearity = si.GetFloatValue(section, "IILinearity", DEFAULT_PEDAL_MODIFIER.linearity), - .scaling = si.GetFloatValue(section, "IIScaling", DEFAULT_PEDAL_MODIFIER.scaling), - .zero = DEFAULT_PEDAL_MODIFIER.zero, - .unit = DEFAULT_PEDAL_MODIFIER.unit, - }; - m_half_axis_modifiers[2] = { - .deadzone = si.GetFloatValue(section, "LDeadzone", DEFAULT_PEDAL_MODIFIER.deadzone), - .saturation = si.GetFloatValue(section, "LSaturation", DEFAULT_PEDAL_MODIFIER.saturation), - .linearity = si.GetFloatValue(section, "LLinearity", DEFAULT_PEDAL_MODIFIER.linearity), - .scaling = si.GetFloatValue(section, "LScaling", DEFAULT_PEDAL_MODIFIER.scaling), - .zero = DEFAULT_PEDAL_MODIFIER.zero, - .unit = DEFAULT_PEDAL_MODIFIER.unit, - }; -} diff --git a/src/core/negcon.h b/src/core/negcon.h index 76a6dc403..09478092f 100644 --- a/src/core/negcon.h +++ b/src/core/negcon.h @@ -3,82 +3,13 @@ #pragma once -#include "controller.h" +#include "negcon_base.h" -#include #include -#include -class NeGcon final : public Controller +class NeGcon final : public NegConBase { public: - enum class Axis : u8 - { - Steering = 0, - I = 1, - II = 2, - L = 3, - Count - }; - - enum class Button : u8 - { - Start = 0, - Up = 1, - Right = 2, - Down = 3, - Left = 4, - R = 5, - B = 6, - A = 7, - Count - }; - - enum class HalfAxis : u8 - { - SteeringLeft, - SteeringRight, - I, - II, - L, - Count - }; - - struct AxisModifier - { - float deadzone; - float saturation; - float linearity; - float scaling; - float zero; - float unit; - }; - - static constexpr float DEFAULT_DEADZONE = 0.00f; - static constexpr float DEFAULT_SATURATION = 1.00f; - static constexpr float DEFAULT_LINEARITY = 0.00f; - static constexpr float DEFAULT_SCALING = 1.00f; - static constexpr float DEFAULT_STEERING_ZERO = 128.0f; - static constexpr float DEFAULT_STEERING_UNIT = 128.0f; - static constexpr float DEFAULT_PEDAL_ZERO = 0.0f; - static constexpr float DEFAULT_PEDAL_UNIT = 255.0f; - static constexpr AxisModifier DEFAULT_STEERING_MODIFIER = { - .deadzone = DEFAULT_DEADZONE, - .saturation = DEFAULT_SATURATION, - .linearity = DEFAULT_LINEARITY, - .scaling = DEFAULT_SCALING, - .zero = DEFAULT_STEERING_ZERO, - .unit = DEFAULT_STEERING_UNIT, - }; - static constexpr AxisModifier DEFAULT_PEDAL_MODIFIER = { - .deadzone = DEFAULT_DEADZONE, - .saturation = DEFAULT_SATURATION, - .linearity = DEFAULT_LINEARITY, - .scaling = DEFAULT_SCALING, - .zero = DEFAULT_PEDAL_ZERO, - .unit = DEFAULT_PEDAL_UNIT, - }; - static const Controller::ControllerInfo INFO; explicit NeGcon(u32 index); @@ -91,17 +22,9 @@ public: void Reset() override; bool DoState(StateWrapper& sw, bool apply_input_state) override; - float GetBindState(u32 index) const override; - void SetBindState(u32 index, float value) override; - void ResetTransferState() override; bool Transfer(const u8 data_in, u8* data_out) override; - u32 GetButtonStateBits() const override; - std::optional GetAnalogInputBytes() const override; - - void LoadSettings(const SettingsInterface& si, const char* section, bool initial) override; - private: enum class TransferState : u8 { @@ -116,21 +39,5 @@ private: AnalogL }; - std::array(Axis::Count)> m_axis_state{}; - - // steering, merged to m_axis_state - std::array m_half_axis_state; - - // buttons are active low; bits 0-2, 8-10, 14-15 are not used and are always high - u16 m_button_state = UINT16_C(0xFFFF); - TransferState m_transfer_state = TransferState::Idle; - bool m_disable_socd = false; - - AxisModifier m_steering_modifier = DEFAULT_STEERING_MODIFIER; - std::array m_half_axis_modifiers = { - DEFAULT_PEDAL_MODIFIER, - DEFAULT_PEDAL_MODIFIER, - DEFAULT_PEDAL_MODIFIER, - }; }; diff --git a/src/core/negcon_base.cpp b/src/core/negcon_base.cpp new file mode 100644 index 000000000..49c7e90c0 --- /dev/null +++ b/src/core/negcon_base.cpp @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin and contributors. +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "negcon_base.h" +#include "system.h" + +#include "common/settings_interface.h" + +#include +#include +#include + +// Mapping of Button to index of corresponding bit in m_button_state +static constexpr std::array(NegConBase::Button::Count)> s_button_indices = {3, 4, 5, 6, + 7, 11, 12, 13}; + +static float ApplyAxisModifier(float value, const NegConBase::AxisModifier& axis_modifier); +static u8 GetScaledValue(float value, const NegConBase::AxisModifier& axis_modifier); + +NegConBase::NegConBase(u32 index, u32 half_axis_bind_start) + : Controller(index), m_half_axis_bind_start(half_axis_bind_start) +{ + m_axis_state.fill(0x00); + m_axis_state[static_cast(Axis::Steering)] = 0x80; +} + +NegConBase::~NegConBase() = default; + +float NegConBase::GetBindState(u32 index) const +{ + if (index == (m_half_axis_bind_start + static_cast(HalfAxis::SteeringLeft)) || + index == (m_half_axis_bind_start + static_cast(HalfAxis::SteeringRight))) + { + float value = m_axis_state[static_cast(Axis::Steering)]; + value = value - 128.0f; + value /= value < 0.0f ? 128.0f : 127.0f; + value = std::clamp(value, -1.0f, 1.0f); + if (index == (m_half_axis_bind_start + static_cast(HalfAxis::SteeringLeft))) + value *= -1.0f; + + return std::max(0.0f, value); + } + else if (index >= m_half_axis_bind_start) + { + // less one because of the two steering axes + const u32 sub_index = index - (m_half_axis_bind_start + 1); + if (sub_index >= m_axis_state.size()) + return 0.0f; + + return static_cast(m_axis_state[sub_index]) * (1.0f / 255.0f); + } + else if (index < static_cast(Button::Count)) + { + const u32 bit = s_button_indices[index]; + return static_cast(((m_button_state >> bit) & 1u) ^ 1u); + } + else + { + return 0.0f; + } +} + +void NegConBase::SetBindState(u32 index, float value) +{ + // Steering Axis: -1..1 -> 0..255 + if (index == (m_half_axis_bind_start + static_cast(HalfAxis::SteeringLeft)) || + index == (m_half_axis_bind_start + static_cast(HalfAxis::SteeringRight))) + { + value = ApplyAxisModifier(value, m_steering_modifier); + + m_half_axis_state[index - m_half_axis_bind_start] = std::clamp(value, 0.0f, 1.0f); + + const float merged = m_half_axis_state[1] - m_half_axis_state[0]; + m_axis_state[static_cast(Axis::Steering)] = GetScaledValue(merged, m_steering_modifier); + } + else if (index >= m_half_axis_bind_start) + { + // less one because of the two steering axes + const u32 sub_index = index - (m_half_axis_bind_start + 1); + if (sub_index >= m_axis_state.size()) + return; + + if (index >= (m_half_axis_bind_start + static_cast(HalfAxis::I)) && + index <= (m_half_axis_bind_start + static_cast(HalfAxis::L))) + { + const AxisModifier& axis_modifier = + m_half_axis_modifiers[index - (m_half_axis_bind_start + static_cast(HalfAxis::I))]; + value = ApplyAxisModifier(value, axis_modifier); + m_axis_state[sub_index] = GetScaledValue(value, axis_modifier); + } + else + { + m_axis_state[sub_index] = static_cast(std::clamp(value * 255.0f, 0.0f, 255.0f)); + } + } + else if (index < static_cast(Button::Count)) + { + const u16 bit = u16(1) << s_button_indices[static_cast(index)]; + + if (value >= 0.5f) + { + if (m_button_state & bit) + System::SetRunaheadReplayFlag(false); + + m_button_state &= ~bit; + } + else + { + if (!(m_button_state & bit)) + System::SetRunaheadReplayFlag(false); + + m_button_state |= bit; + } + } +} + +u32 NegConBase::GetButtonStateBits() const +{ + return m_button_state ^ 0xFFFF; +} + +std::optional NegConBase::GetAnalogInputBytes() const +{ + return m_axis_state[static_cast(Axis::L)] << 24 | m_axis_state[static_cast(Axis::II)] << 16 | + m_axis_state[static_cast(Axis::I)] << 8 | m_axis_state[static_cast(Axis::Steering)]; +} + +void NegConBase::LoadSettings(const SettingsInterface& si, const char* section, bool initial) +{ + m_disable_socd = si.GetBoolValue(section, "DisableSOCD", false); + m_steering_modifier = { + .deadzone = si.GetFloatValue(section, "SteeringDeadzone", DEFAULT_STEERING_MODIFIER.deadzone), + .saturation = si.GetFloatValue(section, "SteeringSaturation", DEFAULT_STEERING_MODIFIER.saturation), + .linearity = si.GetFloatValue(section, "SteeringLinearity", DEFAULT_STEERING_MODIFIER.linearity), + .scaling = si.GetFloatValue(section, "SteeringScaling", DEFAULT_STEERING_MODIFIER.scaling), + .zero = DEFAULT_STEERING_MODIFIER.zero, + .unit = DEFAULT_STEERING_MODIFIER.unit, + }; + m_half_axis_modifiers[0] = { + .deadzone = si.GetFloatValue(section, "IDeadzone", DEFAULT_PEDAL_MODIFIER.deadzone), + .saturation = si.GetFloatValue(section, "ISaturation", DEFAULT_PEDAL_MODIFIER.saturation), + .linearity = si.GetFloatValue(section, "ILinearity", DEFAULT_PEDAL_MODIFIER.linearity), + .scaling = si.GetFloatValue(section, "IScaling", DEFAULT_PEDAL_MODIFIER.scaling), + .zero = DEFAULT_PEDAL_MODIFIER.zero, + .unit = DEFAULT_PEDAL_MODIFIER.unit, + }; + m_half_axis_modifiers[1] = { + .deadzone = si.GetFloatValue(section, "IIDeadzone", DEFAULT_PEDAL_MODIFIER.deadzone), + .saturation = si.GetFloatValue(section, "IISaturation", DEFAULT_PEDAL_MODIFIER.saturation), + .linearity = si.GetFloatValue(section, "IILinearity", DEFAULT_PEDAL_MODIFIER.linearity), + .scaling = si.GetFloatValue(section, "IIScaling", DEFAULT_PEDAL_MODIFIER.scaling), + .zero = DEFAULT_PEDAL_MODIFIER.zero, + .unit = DEFAULT_PEDAL_MODIFIER.unit, + }; + m_half_axis_modifiers[2] = { + .deadzone = si.GetFloatValue(section, "LDeadzone", DEFAULT_PEDAL_MODIFIER.deadzone), + .saturation = si.GetFloatValue(section, "LSaturation", DEFAULT_PEDAL_MODIFIER.saturation), + .linearity = si.GetFloatValue(section, "LLinearity", DEFAULT_PEDAL_MODIFIER.linearity), + .scaling = si.GetFloatValue(section, "LScaling", DEFAULT_PEDAL_MODIFIER.scaling), + .zero = DEFAULT_PEDAL_MODIFIER.zero, + .unit = DEFAULT_PEDAL_MODIFIER.unit, + }; +} + +float ApplyAxisModifier(float value, const NegConBase::AxisModifier& axis_modifier) +{ + value = (value - axis_modifier.deadzone) / (axis_modifier.saturation - axis_modifier.deadzone); + value = std::clamp(value, 0.0f, 1.0f); + value = std::pow(value, std::exp(axis_modifier.linearity)); + return value; +} + +u8 GetScaledValue(float value, const NegConBase::AxisModifier& axis_modifier) +{ + value = axis_modifier.scaling * axis_modifier.unit * value + axis_modifier.zero; + return static_cast(std::clamp(std::round(value), 0.0f, 255.0f)); +} diff --git a/src/core/negcon_base.h b/src/core/negcon_base.h new file mode 100644 index 000000000..66989375b --- /dev/null +++ b/src/core/negcon_base.h @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin and contributors. +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "controller.h" + +#include +#include + +class NegConBase : public Controller +{ +public: + enum class Axis : u8 + { + Steering = 0, + I = 1, + II = 2, + L = 3, + Count + }; + + enum class Button : u8 + { + Start = 0, + Up = 1, + Right = 2, + Down = 3, + Left = 4, + R = 5, + B = 6, + A = 7, + Count + }; + + enum class HalfAxis : u8 + { + SteeringLeft, + SteeringRight, + I, + II, + L, + Count + }; + + struct AxisModifier + { + float deadzone; + float saturation; + float linearity; + float scaling; + float zero; + float unit; + }; + + static constexpr float DEFAULT_DEADZONE = 0.00f; + static constexpr float DEFAULT_SATURATION = 1.00f; + static constexpr float DEFAULT_LINEARITY = 0.00f; + static constexpr float DEFAULT_SCALING = 1.00f; + static constexpr float DEFAULT_STEERING_ZERO = 128.0f; + static constexpr float DEFAULT_STEERING_UNIT = 128.0f; + static constexpr float DEFAULT_PEDAL_ZERO = 0.0f; + static constexpr float DEFAULT_PEDAL_UNIT = 255.0f; + static constexpr AxisModifier DEFAULT_STEERING_MODIFIER = { + .deadzone = DEFAULT_DEADZONE, + .saturation = DEFAULT_SATURATION, + .linearity = DEFAULT_LINEARITY, + .scaling = DEFAULT_SCALING, + .zero = DEFAULT_STEERING_ZERO, + .unit = DEFAULT_STEERING_UNIT, + }; + static constexpr AxisModifier DEFAULT_PEDAL_MODIFIER = { + .deadzone = DEFAULT_DEADZONE, + .saturation = DEFAULT_SATURATION, + .linearity = DEFAULT_LINEARITY, + .scaling = DEFAULT_SCALING, + .zero = DEFAULT_PEDAL_ZERO, + .unit = DEFAULT_PEDAL_UNIT, + }; + + ~NegConBase() override; + + float GetBindState(u32 index) const override; + void SetBindState(u32 index, float value) override; + + u32 GetButtonStateBits() const override; + std::optional GetAnalogInputBytes() const override; + + void LoadSettings(const SettingsInterface& si, const char* section, bool initial) override; + +protected: + NegConBase(u32 index, u32 half_axis_bind_start); + + std::array(Axis::Count)> m_axis_state{}; + + // steering, merged to m_axis_state + std::array m_half_axis_state{}; + + // buttons are active low; bits 0-2, 8-10, 14-15 are not used and are always high + u16 m_button_state = UINT16_C(0xFFFF); + + bool m_disable_socd = false; + +private: + u32 m_half_axis_bind_start; + AxisModifier m_steering_modifier = DEFAULT_STEERING_MODIFIER; + std::array m_half_axis_modifiers = { + DEFAULT_PEDAL_MODIFIER, + DEFAULT_PEDAL_MODIFIER, + DEFAULT_PEDAL_MODIFIER, + }; +}; diff --git a/src/core/negcon_rumble.cpp b/src/core/negcon_rumble.cpp index 336700cac..8b16eef1a 100644 --- a/src/core/negcon_rumble.cpp +++ b/src/core/negcon_rumble.cpp @@ -3,8 +3,7 @@ #include "negcon_rumble.h" #include "controller_helpers.h" -#include "settings.h" -#include "system.h" +#include "host.h" #include "util/imgui_manager.h" #include "util/input_manager.h" @@ -14,24 +13,19 @@ #include "common/assert.h" #include "common/bitutils.h" #include "common/log.h" -#include "common/string_util.h" #include "IconsFontAwesome.h" #include "IconsPromptFont.h" #include "fmt/format.h" +#include #include LOG_CHANNEL(Controller); -// Mapping of Button to index of corresponding bit in m_button_state -static constexpr std::array(NeGconRumble::Button::Count)> s_button_indices = {3, 4, 5, 6, - 7, 11, 12, 13}; -NeGconRumble::NeGconRumble(u32 index) : Controller(index) +NeGconRumble::NeGconRumble(u32 index) : NegConBase(index, HALFAXIS_BIND_START_INDEX) { m_status_byte = 0x5A; - m_axis_state.fill(0x00); - m_axis_state[static_cast(Axis::Steering)] = 0x80; m_rumble_config.fill(0xFF); } @@ -120,23 +114,9 @@ float NeGconRumble::GetBindState(u32 index) const { return GetMotorStrength(index - MOTOR_BIND_START_INDEX); } - else if (index >= (HALFAXIS_BIND_START_INDEX + static_cast(HalfAxis::I))) - { - return static_cast(m_axis_state[index - (HALFAXIS_BIND_START_INDEX + static_cast(HalfAxis::I)) + 1]) * - (1.0f / 255.0f); - } - else if (index >= HALFAXIS_BIND_START_INDEX) - { - return static_cast(m_half_axis_state[index - HALFAXIS_BIND_START_INDEX]) * (1.0f / 255.0f); - } - else if (index < static_cast(Button::Analog)) - { - const u16 bit = u16(1) << s_button_indices[static_cast(index)]; - return BoolToFloat((m_button_state & bit) == 0); - } else { - return 0.0f; + return NegConBase::GetBindState(index); } } @@ -155,60 +135,7 @@ void NeGconRumble::SetBindState(u32 index, float value) return; } - // Steering Axis: -1..1 -> 0..255 - else if (index == (static_cast(Button::Count) + static_cast(HalfAxis::SteeringLeft)) || - index == (static_cast(Button::Count) + static_cast(HalfAxis::SteeringRight))) - { - value *= m_steering_sensitivity; - if (value < m_steering_deadzone) - value = 0.0f; - - m_half_axis_state[index - static_cast(Button::Count)] = - static_cast(std::clamp(value * 255.0f, 0.0f, 255.0f)); - - m_axis_state[static_cast(Axis::Steering)] = - ControllerHelpers::MergeHalfAxes(m_half_axis_state[static_cast(HalfAxis::SteeringLeft)], - m_half_axis_state[static_cast(HalfAxis::SteeringRight)], false); - } - else if (index >= static_cast(Button::Count)) - { - // less one because of the two steering axes - const u32 sub_index = index - (static_cast(Button::Count) + 1); - if (sub_index >= m_axis_state.size()) - return; - - m_axis_state[sub_index] = static_cast(std::clamp(value * 255.0f, 0.0f, 255.0f)); - } - else if (index < static_cast(Button::Count)) - { - const u16 bit = u16(1) << s_button_indices[static_cast(index)]; - - if (value >= 0.5f) - { - if (m_button_state & bit) - System::SetRunaheadReplayFlag(false); - - m_button_state &= ~bit; - } - else - { - if (!(m_button_state & bit)) - System::SetRunaheadReplayFlag(false); - - m_button_state |= bit; - } - } -} - -u32 NeGconRumble::GetButtonStateBits() const -{ - return m_button_state ^ 0xFFFF; -} - -std::optional NeGconRumble::GetAnalogInputBytes() const -{ - return m_axis_state[static_cast(Axis::L)] << 24 | m_axis_state[static_cast(Axis::II)] << 16 | - m_axis_state[static_cast(Axis::I)] << 8 | m_axis_state[static_cast(Axis::Steering)]; + NegConBase::SetBindState(index, value); } void NeGconRumble::ResetTransferState() @@ -717,25 +644,25 @@ constinit const Controller::ControllerBindingInfo NeGconRumble::s_binding_info[] {name, display_name, icon_name, LED_BIND_START_INDEX + index, InputBindingInfo::Type::LED, genb} // clang-format off - BUTTON("Up", TRANSLATE_NOOP("NeGconRumble", "D-Pad Up"), ICON_PF_DPAD_UP, NeGconRumble::Button::Up, GenericInputBinding::DPadUp), - BUTTON("Right", TRANSLATE_NOOP("NeGconRumble", "D-Pad Right"), ICON_PF_DPAD_RIGHT, NeGconRumble::Button::Right, GenericInputBinding::DPadRight), - BUTTON("Down", TRANSLATE_NOOP("NeGconRumble", "D-Pad Down"), ICON_PF_DPAD_DOWN, NeGconRumble::Button::Down, GenericInputBinding::DPadDown), - BUTTON("Left", TRANSLATE_NOOP("NeGconRumble", "D-Pad Left"), ICON_PF_DPAD_LEFT, NeGconRumble::Button::Left, GenericInputBinding::DPadLeft), - BUTTON("Start", TRANSLATE_NOOP("NeGconRumble", "Start"),ICON_PF_START, NeGconRumble::Button::Start, GenericInputBinding::Start), - BUTTON("A", TRANSLATE_NOOP("NeGconRumble", "A Button"), ICON_PF_BUTTON_A, NeGconRumble::Button::A, GenericInputBinding::Circle), - BUTTON("B", TRANSLATE_NOOP("NeGconRumble", "B Button"), ICON_PF_BUTTON_B, NeGconRumble::Button::B, GenericInputBinding::Triangle), - AXIS("I", TRANSLATE_NOOP("NeGconRumble", "I Button"), ICON_PF_RIGHT_TRIGGER_R2, NeGconRumble::HalfAxis::I, GenericInputBinding::R2), - AXIS("II", TRANSLATE_NOOP("NeGconRumble", "II Button"), ICON_PF_LEFT_TRIGGER_L2, NeGconRumble::HalfAxis::II, GenericInputBinding::L2), - AXIS("L", TRANSLATE_NOOP("NeGconRumble", "Left Trigger"), ICON_PF_LEFT_ANALOG_LEFT, NeGconRumble::HalfAxis::L, GenericInputBinding::L1), - BUTTON("R", TRANSLATE_NOOP("NeGconRumble", "Right Trigger"), ICON_PF_RIGHT_SHOULDER_R1, NeGconRumble::Button::R, GenericInputBinding::R1), - AXIS("SteeringLeft", TRANSLATE_NOOP("NeGconRumble", "Steering (Twist) Left"), ICON_PF_LEFT_ANALOG_LEFT, NeGconRumble::HalfAxis::SteeringLeft, GenericInputBinding::LeftStickLeft), - AXIS("SteeringRight", TRANSLATE_NOOP("NeGconRumble", "Steering (Twist) Right"), ICON_PF_LEFT_ANALOG_RIGHT, NeGconRumble::HalfAxis::SteeringRight, GenericInputBinding::LeftStickRight), - BUTTON("Analog", TRANSLATE_NOOP("NeGconRumble", "Analog Toggle"), ICON_PF_ANALOG_LEFT_RIGHT, NeGconRumble::Button::Analog, GenericInputBinding::System), + BUTTON("Up", TRANSLATE_NOOP("NeGcon", "D-Pad Up"), ICON_PF_DPAD_UP, NeGconRumble::Button::Up, GenericInputBinding::DPadUp), + BUTTON("Right", TRANSLATE_NOOP("NeGcon", "D-Pad Right"), ICON_PF_DPAD_RIGHT, NeGconRumble::Button::Right, GenericInputBinding::DPadRight), + BUTTON("Down", TRANSLATE_NOOP("NeGcon", "D-Pad Down"), ICON_PF_DPAD_DOWN, NeGconRumble::Button::Down, GenericInputBinding::DPadDown), + BUTTON("Left", TRANSLATE_NOOP("NeGcon", "D-Pad Left"), ICON_PF_DPAD_LEFT, NeGconRumble::Button::Left, GenericInputBinding::DPadLeft), + BUTTON("Start", TRANSLATE_NOOP("NeGcon", "Start"),ICON_PF_START, NeGconRumble::Button::Start, GenericInputBinding::Start), + BUTTON("A", TRANSLATE_NOOP("NeGcon", "A Button"), ICON_PF_BUTTON_A, NeGconRumble::Button::A, GenericInputBinding::Circle), + BUTTON("B", TRANSLATE_NOOP("NeGcon", "B Button"), ICON_PF_BUTTON_B, NeGconRumble::Button::B, GenericInputBinding::Triangle), + AXIS("I", TRANSLATE_NOOP("NeGcon", "I Button"), ICON_PF_BUTTON_ALT_1, NeGconRumble::HalfAxis::I, GenericInputBinding::R2), + AXIS("II", TRANSLATE_NOOP("NeGcon", "II Button"), ICON_PF_BUTTON_ALT_2, NeGconRumble::HalfAxis::II, GenericInputBinding::L2), + AXIS("L", TRANSLATE_NOOP("NeGcon", "Left Trigger"), ICON_PF_LEFT_TRIGGER_LT, NeGconRumble::HalfAxis::L, GenericInputBinding::L1), + BUTTON("R", TRANSLATE_NOOP("NeGcon", "Right Trigger"), ICON_PF_RIGHT_TRIGGER_RT, NeGconRumble::Button::R, GenericInputBinding::R1), + AXIS("SteeringLeft", TRANSLATE_NOOP("NeGcon", "Steering (Twist) Left"), ICON_PF_ANALOG_LEFT, NeGconRumble::HalfAxis::SteeringLeft, GenericInputBinding::LeftStickLeft), + AXIS("SteeringRight", TRANSLATE_NOOP("NeGcon", "Steering (Twist) Right"), ICON_PF_ANALOG_RIGHT, NeGconRumble::HalfAxis::SteeringRight, GenericInputBinding::LeftStickRight), + BUTTON("Analog", TRANSLATE_NOOP("NeGcon", "Analog Toggle"), ICON_PF_ANALOG_LEFT_RIGHT, NeGconRumble::Button::Analog, GenericInputBinding::System), - MOTOR("LargeMotor", TRANSLATE_NOOP("AnalogController", "Large Motor"), ICON_PF_VIBRATION_L, LargeMotor, GenericInputBinding::LargeMotor), - MOTOR("SmallMotor", TRANSLATE_NOOP("AnalogController", "Small Motor"), ICON_PF_VIBRATION, SmallMotor, GenericInputBinding::SmallMotor), + MOTOR("LargeMotor", TRANSLATE_NOOP("NeGcon", "Large Motor"), ICON_PF_VIBRATION_L, LargeMotor, GenericInputBinding::LargeMotor), + MOTOR("SmallMotor", TRANSLATE_NOOP("NeGcon", "Small Motor"), ICON_PF_VIBRATION, SmallMotor, GenericInputBinding::SmallMotor), - MODE_LED("ModeLED", TRANSLATE_NOOP("AnalogController", "Mode LED"), ICON_PF_LED, 0, GenericInputBinding::ModeLED), + MODE_LED("ModeLED", TRANSLATE_NOOP("NeGcon", "Mode LED"), ICON_PF_LED, 0, GenericInputBinding::ModeLED), // clang-format on #undef MOTOR @@ -745,22 +672,48 @@ constinit const Controller::ControllerBindingInfo NeGconRumble::s_binding_info[] static constexpr SettingInfo s_settings[] = { {SettingInfo::Type::Boolean, "DisableSOCD", - TRANSLATE_NOOP("NeGconRumble", "Disable Simultaneous Opposing Cardinal Directions"), - TRANSLATE_NOOP("NeGconRumble", "Prevents concurrent left/right or up/down inputs from being presented to the game."), + TRANSLATE_NOOP("NeGcon", "Disable Simultaneous Opposing Cardinal Directions"), + TRANSLATE_NOOP("NeGcon", "Prevents concurrent left/right or up/down inputs from being presented to the game."), "false", nullptr, nullptr, nullptr, nullptr, nullptr, 0.0f}, - {SettingInfo::Type::Float, "SteeringDeadzone", TRANSLATE_NOOP("NeGconRumble", "Steering Axis Deadzone"), - TRANSLATE_NOOP("NeGconRumble", "Sets deadzone size for steering axis."), "0", "0", "0.99", "0.01", "%.0f%%", nullptr, - 100.0f}, - {SettingInfo::Type::Float, "SteeringSensitivity", TRANSLATE_NOOP("NeGconRumble", "Steering Axis Sensitivity"), - TRANSLATE_NOOP("NeGconRumble", "Sets the steering axis scaling factor."), "1", "0.01", "2", "0.01", "%.0f%%", - nullptr, 100.0f}, - {SettingInfo::Type::Integer, "LargeMotorVibrationBias", TRANSLATE_NOOP("NeGconRumble", "Large Motor Vibration Bias"), - TRANSLATE_NOOP("NeGconRumble", + {SettingInfo::Type::Float, "SteeringDeadzone", TRANSLATE_NOOP("NeGcon", "Steering Axis Deadzone"), + TRANSLATE_NOOP("NeGcon", "Sets deadzone for steering axis."), "0", "0", "0.99", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "SteeringSaturation", TRANSLATE_NOOP("NeGcon", "Steering Axis Saturation"), + TRANSLATE_NOOP("NeGcon", "Sets saturation for steering axis."), "1", "0.01", "1", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "SteeringLinearity", TRANSLATE_NOOP("NeGcon", "Steering Axis Linearity"), + TRANSLATE_NOOP("NeGcon", "Sets linearity for steering axis."), "0", "-2", "2", "0.05", "%.2f", nullptr, 1.0f}, + {SettingInfo::Type::Float, "SteeringScaling", TRANSLATE_NOOP("NeGcon", "Steering Scaling"), + TRANSLATE_NOOP("NeGcon", "Sets scaling for steering axis."), "1", "0.01", "10", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "IDeadzone", TRANSLATE_NOOP("NeGcon", "I Button Deadzone"), + TRANSLATE_NOOP("NeGcon", "Sets deadzone for button I."), "0", "0", "0.99", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "ISaturation", TRANSLATE_NOOP("NeGcon", "I Button Saturation"), + TRANSLATE_NOOP("NeGcon", "Sets saturation for button I."), "1", "0.01", "1", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "ILinearity", TRANSLATE_NOOP("NeGcon", "I Button Linearity"), + TRANSLATE_NOOP("NeGcon", "Sets linearity for button I."), "0", "-2", "2", "0.01", "%.2f", nullptr, 1.0f}, + {SettingInfo::Type::Float, "IScaling", TRANSLATE_NOOP("NeGcon", "I Scaling"), + TRANSLATE_NOOP("NeGcon", "Sets scaling for button I."), "1", "0.01", "10", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "IIDeadzone", TRANSLATE_NOOP("NeGcon", "II Button Deadzone"), + TRANSLATE_NOOP("NeGcon", "Sets deadzone for button II."), "0", "0", "0.99", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "IISaturation", TRANSLATE_NOOP("NeGcon", "II Button Saturation"), + TRANSLATE_NOOP("NeGcon", "Sets saturation for button II."), "1", "0.01", "1", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "IILinearity", TRANSLATE_NOOP("NeGcon", "II Button Linearity"), + TRANSLATE_NOOP("NeGcon", "Sets linearity for button II."), "0", "-2", "2", "0.01", "%.2f", nullptr, 1.0f}, + {SettingInfo::Type::Float, "IIScaling", TRANSLATE_NOOP("NeGcon", "II Scaling"), + TRANSLATE_NOOP("NeGcon", "Sets scaling for button II."), "1", "0.01", "10", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "LDeadzone", TRANSLATE_NOOP("NeGcon", "Left Trigger Deadzone"), + TRANSLATE_NOOP("NeGcon", "Sets deadzone for left trigger."), "0", "0", "0.99", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "LSaturation", TRANSLATE_NOOP("NeGcon", "Left Trigger Saturation"), + TRANSLATE_NOOP("NeGcon", "Sets saturation for left trigger."), "1", "0.01", "1", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Float, "LLinearity", TRANSLATE_NOOP("NeGcon", "Left Trigger Linearity"), + TRANSLATE_NOOP("NeGcon", "Sets linearity for left trigger."), "0", "-2", "2", "0.01", "%.2f", nullptr, 1.0f}, + {SettingInfo::Type::Float, "LScaling", TRANSLATE_NOOP("NeGcon", "Left Trigger Scaling"), + TRANSLATE_NOOP("NeGcon", "Sets scaling for left trigger."), "1", "0.01", "10", "0.01", "%.0f%%", nullptr, 100.0f}, + {SettingInfo::Type::Integer, "LargeMotorVibrationBias", TRANSLATE_NOOP("NeGcon", "Large Motor Vibration Bias"), + TRANSLATE_NOOP("NeGcon", "Sets the bias value for the large vibration motor. If vibration in some games is too weak or not " "functioning, try increasing this value. Negative values will decrease the intensity of vibration."), "8", "-255", "255", "1", "%d", nullptr, 1.0f}, - {SettingInfo::Type::Integer, "SmallMotorVibrationBias", TRANSLATE_NOOP("NeGconRumble", "Small Motor Vibration Bias"), - TRANSLATE_NOOP("NeGconRumble", + {SettingInfo::Type::Integer, "SmallMotorVibrationBias", TRANSLATE_NOOP("NeGcon", "Small Motor Vibration Bias"), + TRANSLATE_NOOP("NeGcon", "Sets the bias value for the small vibration motor. If vibration in some games is too weak or not " "functioning, try increasing this value. Negative values will decrease the intensity of vibration."), "8", "-255", "255", "1", "%d", nullptr, 1.0f}, @@ -776,11 +729,9 @@ const Controller::ControllerInfo NeGconRumble::INFO = {ControllerType::NeGconRum void NeGconRumble::LoadSettings(const SettingsInterface& si, const char* section, bool initial) { - m_steering_deadzone = si.GetFloatValue(section, "SteeringDeadzone", 0.10f); - m_steering_sensitivity = si.GetFloatValue(section, "SteeringSensitivity", 1.00f); - m_disable_socd = si.GetBoolValue(section, "DisableSOCD", false); + NegConBase::LoadSettings(si, section, initial); m_vibration_bias[0] = static_cast( std::clamp(si.GetIntValue(section, "LargeMotorVibrationBias", DEFAULT_LARGE_MOTOR_VIBRATION_BIAS), -255, 255)); m_vibration_bias[1] = static_cast( std::clamp(si.GetIntValue(section, "SmallMotorVibrationBias", DEFAULT_SMALL_MOTOR_VIBRATION_BIAS), -255, 255)); -} \ No newline at end of file +} diff --git a/src/core/negcon_rumble.h b/src/core/negcon_rumble.h index b86902488..db72b8a5f 100644 --- a/src/core/negcon_rumble.h +++ b/src/core/negcon_rumble.h @@ -3,24 +3,14 @@ #pragma once -#include "controller.h" +#include "negcon_base.h" #include #include -#include -class NeGconRumble final : public Controller +class NeGconRumble final : public NegConBase { public: - enum class Axis : u8 - { - Steering = 0, - I = 1, - II = 2, - L = 3, - Count - }; - enum class Button : u8 { Start = 0, @@ -35,16 +25,6 @@ public: Count }; - enum class HalfAxis : u8 - { - SteeringLeft, - SteeringRight, - I, - II, - L, - Count - }; - static constexpr u8 NUM_MOTORS = 2; static const Controller::ControllerInfo INFO; @@ -65,9 +45,6 @@ public: void ResetTransferState() override; bool Transfer(const u8 data_in, u8* data_out) override; - u32 GetButtonStateBits() const override; - std::optional GetAnalogInputBytes() const override; - void LoadSettings(const SettingsInterface& si, const char* section, bool initial) override; private: @@ -97,27 +74,17 @@ private: static const Controller::ControllerBindingInfo s_binding_info[]; std::array m_vibration_bias{DEFAULT_LARGE_MOTOR_VIBRATION_BIAS, DEFAULT_SMALL_MOTOR_VIBRATION_BIAS}; - bool m_disable_socd = false; - bool m_analog_mode = false; bool m_analog_locked = false; bool m_dualshock_enabled = false; bool m_configuration_mode = false; - std::array(Axis::Count)> m_axis_state{}; - enum : u8 { LargeMotor = 0, SmallMotor = 1 }; - // steering, merged to m_axis_state - std::array m_half_axis_state{}; - - // buttons are active low; bits 0-2, 8-10, 14-15 are not used and are always high - u16 m_button_state = UINT16_C(0xFFFF); - MotorState m_motor_state{}; Command m_command = Command::Idle; @@ -148,7 +115,4 @@ private: float GetMotorStrength(u32 motor) const; void ResetRumbleConfig(); void SetMotorStateForConfigIndex(int index, u8 value); - - float m_steering_deadzone = 0.00f; - float m_steering_sensitivity = 1.00f; };