NeGcon: Create common base class for NeGcon controllers

And move common logic into base class.
This commit is contained in:
Stenzek committed 2026-09-25 15:25:46 +10:00
1 parent 62299a5092
commit e68536dcd7
13 files changed
+600 -405

No files matched your search

+1
View File
@@ -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
)
+2 -1
View File
@@ -6,6 +6,7 @@
<ClCompile Include="cheats_tests.cpp" />
<ClCompile Include="cpu_disasm_tests.cpp" />
<ClCompile Include="lightgun_controller_tests.cpp" />
<ClCompile Include="negcon_base_tests.cpp" />
<ClCompile Include="spu_tests.cpp" />
<ClCompile Include="stub_host.cpp" />
</ItemGroup>
@@ -43,4 +44,4 @@
</Link>
</ItemDefinitionGroup>
<Import Project="..\..\dep\vsprops\Targets.props" />
</Project>
</Project>
@@ -6,6 +6,8 @@
<ClCompile Include="cpu_disasm_tests.cpp" />
<ClCompile Include="stub_host.cpp" />
<ClCompile Include="spu_tests.cpp" />
<ClCompile Include="lightgun_controller_tests.cpp" />
<ClCompile Include="negcon_base_tests.cpp" />
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />
+233
View File
@@ -0,0 +1,233 @@
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> 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 <array>
#include <cstring>
#include <gtest/gtest.h>
#include <span>
#include <string_view>
#include <type_traits>
static_assert(std::is_base_of_v<NegConBase, NeGcon>);
static_assert(std::is_base_of_v<NegConBase, NeGconRumble>);
static_assert(static_cast<u32>(NeGcon::Button::Count) == 8);
static_assert(static_cast<u32>(NeGconRumble::Button::Analog) == 8);
static_assert(static_cast<u32>(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<u32>(NeGcon::Button::Up), 1.0f);
negcon.SetBindState(static_cast<u32>(NeGcon::Button::A), 1.0f);
rumble.SetBindState(static_cast<u32>(NeGconRumble::Button::Up), 1.0f);
rumble.SetBindState(static_cast<u32>(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<u32>(NeGcon::Button::Up)), 1.0f);
EXPECT_EQ(rumble.GetBindState(static_cast<u32>(NeGconRumble::Button::Up)), 1.0f);
EXPECT_EQ(rumble.GetBindState(static_cast<u32>(NeGconRumble::Button::Analog)), 0.0f);
}
TEST(NegConBase, DefaultAxisModifiers)
{
NeGcon negcon(0);
NeGconRumble rumble(0);
constexpr u32 negcon_axis_start = static_cast<u32>(NeGcon::Button::Count);
constexpr u32 rumble_axis_start = static_cast<u32>(NeGconRumble::Button::Count);
negcon.SetBindState(negcon_axis_start + static_cast<u32>(NeGcon::HalfAxis::SteeringRight), 1.0f);
rumble.SetBindState(rumble_axis_start + static_cast<u32>(NeGconRumble::HalfAxis::SteeringRight), 1.0f);
negcon.SetBindState(negcon_axis_start + static_cast<u32>(NeGcon::HalfAxis::I), 0.5f);
rumble.SetBindState(rumble_axis_start + static_cast<u32>(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<u32>(NeGcon::HalfAxis::SteeringRight)), 1.0f);
EXPECT_EQ(rumble.GetBindState(rumble_axis_start + static_cast<u32>(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<u32>(NeGcon::Button::Count);
constexpr u32 rumble_axis_start = static_cast<u32>(NeGconRumble::Button::Count);
negcon.SetBindState(negcon_axis_start + static_cast<u32>(NeGcon::HalfAxis::SteeringRight), 0.5f);
rumble.SetBindState(rumble_axis_start + static_cast<u32>(NeGconRumble::HalfAxis::SteeringRight), 0.5f);
negcon.SetBindState(negcon_axis_start + static_cast<u32>(NeGcon::HalfAxis::I), 0.5f);
rumble.SetBindState(rumble_axis_start + static_cast<u32>(NeGconRumble::HalfAxis::I), 0.5f);
negcon.SetBindState(negcon_axis_start + static_cast<u32>(NeGcon::HalfAxis::II), 0.5f);
rumble.SetBindState(rumble_axis_start + static_cast<u32>(NeGconRumble::HalfAxis::II), 0.5f);
negcon.SetBindState(negcon_axis_start + static_cast<u32>(NeGcon::HalfAxis::L), 0.5f);
rumble.SetBindState(rumble_axis_start + static_cast<u32>(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<u8, 64> state_data = {};
NeGcon negcon(0);
negcon.SetBindState(static_cast<u32>(NeGcon::Button::A), 1.0f);
StateWrapper negcon_sw(std::span<u8>(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<u32>(NeGconRumble::Button::A), 1.0f);
StateWrapper rumble_sw(std::span<u8>(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<u8, 6> legacy_state_data = {};
NeGconRumble legacy_rumble(0);
legacy_rumble.SetBindState(static_cast<u32>(NeGconRumble::Button::A), 1.0f);
StateWrapper legacy_rumble_sw(std::span<const u8>(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<u32>(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<u32>(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;
}
+2
View File
@@ -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
+2
View File
@@ -67,6 +67,7 @@
<ClCompile Include="multitap.cpp" />
<ClCompile Include="guncon.cpp" />
<ClCompile Include="negcon.cpp" />
<ClCompile Include="negcon_base.cpp" />
<ClCompile Include="negcon_rumble.cpp" />
<ClCompile Include="pad.cpp" />
<ClCompile Include="controller.cpp" />
@@ -164,6 +165,7 @@
<ClInclude Include="multitap.h" />
<ClInclude Include="guncon.h" />
<ClInclude Include="negcon.h" />
<ClInclude Include="negcon_base.h" />
<ClInclude Include="negcon_rumble.h" />
<ClInclude Include="pad.h" />
<ClInclude Include="controller.h" />
+2
View File
@@ -33,6 +33,7 @@
<ClCompile Include="guncon.cpp" />
<ClCompile Include="playstation_mouse.cpp" />
<ClCompile Include="negcon.cpp" />
<ClCompile Include="negcon_base.cpp" />
<ClCompile Include="cpu_pgxp.cpp" />
<ClCompile Include="cheats.cpp" />
<ClCompile Include="memory_card_image.cpp" />
@@ -112,6 +113,7 @@
<ClInclude Include="guncon.h" />
<ClInclude Include="playstation_mouse.h" />
<ClInclude Include="negcon.h" />
<ClInclude Include="negcon_base.h" />
<ClInclude Include="gte_types.h" />
<ClInclude Include="cpu_pgxp.h" />
<ClInclude Include="cpu_core_private.h" />
+1 -160
View File
@@ -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 <array>
#include <cmath>
// Mapping of Button to index of corresponding bit in m_button_state
static constexpr std::array<u8, static_cast<size_t>(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<u32>(Button::Count))
{
m_axis_state.fill(0x00);
m_axis_state[static_cast<u8>(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<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringLeft)) ||
index == (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringRight)))
{
float value = m_axis_state[static_cast<u32>(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<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringLeft)))
{
value *= -1.0f;
}
return std::max(0.0f, value);
}
else if (index >= static_cast<u32>(Button::Count))
{
// less one because of the two steering axes
const u32 sub_index = index - (static_cast<u32>(Button::Count) + 1);
if (sub_index >= m_axis_state.size())
return 0.0f;
return static_cast<float>(m_axis_state[sub_index]) * (1.0f / 255.0f);
}
else
{
const u32 bit = s_button_indices[index];
return static_cast<float>(((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<u8>(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<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringLeft)) ||
index == (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringRight)))
{
value = apply_axis_modifier(value, m_steering_modifier);
m_half_axis_state[index - static_cast<u32>(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<u32>(Axis::Steering)] = get_scaled_value(merged, m_steering_modifier);
}
else if (index >= static_cast<u32>(Button::Count))
{
// less one because of the two steering axes
const u32 sub_index = index - (static_cast<u32>(Button::Count) + 1);
if (sub_index >= m_axis_state.size())
return;
if (index >= (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::I)) &&
index <= (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::L)))
{
const AxisModifier& axis_modifier =
m_half_axis_modifiers[index - (static_cast<u32>(Button::Count) + static_cast<u32>(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<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f));
}
}
else if (index < static_cast<u32>(Button::Count))
{
const u16 bit = u16(1) << s_button_indices[static_cast<u8>(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<u32> NeGcon::GetAnalogInputBytes() const
{
return m_axis_state[static_cast<size_t>(Axis::L)] << 24 | m_axis_state[static_cast<size_t>(Axis::II)] << 16 |
m_axis_state[static_cast<size_t>(Axis::I)] << 8 | m_axis_state[static_cast<size_t>(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,
};
}
+2 -95
View File
@@ -3,82 +3,13 @@
#pragma once
#include "controller.h"
#include "negcon_base.h"
#include <array>
#include <memory>
#include <optional>
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<u32> 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<u8, static_cast<u8>(Axis::Count)> m_axis_state{};
// steering, merged to m_axis_state
std::array<float, 2> 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<AxisModifier, 3> m_half_axis_modifiers = {
DEFAULT_PEDAL_MODIFIER,
DEFAULT_PEDAL_MODIFIER,
DEFAULT_PEDAL_MODIFIER,
};
};
+177
View File
@@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "negcon_base.h"
#include "system.h"
#include "common/settings_interface.h"
#include <algorithm>
#include <array>
#include <cmath>
// Mapping of Button to index of corresponding bit in m_button_state
static constexpr std::array<u8, static_cast<size_t>(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<u8>(Axis::Steering)] = 0x80;
}
NegConBase::~NegConBase() = default;
float NegConBase::GetBindState(u32 index) const
{
if (index == (m_half_axis_bind_start + static_cast<u32>(HalfAxis::SteeringLeft)) ||
index == (m_half_axis_bind_start + static_cast<u32>(HalfAxis::SteeringRight)))
{
float value = m_axis_state[static_cast<u32>(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<u32>(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<float>(m_axis_state[sub_index]) * (1.0f / 255.0f);
}
else if (index < static_cast<u32>(Button::Count))
{
const u32 bit = s_button_indices[index];
return static_cast<float>(((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<u32>(HalfAxis::SteeringLeft)) ||
index == (m_half_axis_bind_start + static_cast<u32>(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<u32>(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<u32>(HalfAxis::I)) &&
index <= (m_half_axis_bind_start + static_cast<u32>(HalfAxis::L)))
{
const AxisModifier& axis_modifier =
m_half_axis_modifiers[index - (m_half_axis_bind_start + static_cast<u32>(HalfAxis::I))];
value = ApplyAxisModifier(value, axis_modifier);
m_axis_state[sub_index] = GetScaledValue(value, axis_modifier);
}
else
{
m_axis_state[sub_index] = static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f));
}
}
else if (index < static_cast<u32>(Button::Count))
{
const u16 bit = u16(1) << s_button_indices[static_cast<u8>(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<u32> NegConBase::GetAnalogInputBytes() const
{
return m_axis_state[static_cast<size_t>(Axis::L)] << 24 | m_axis_state[static_cast<size_t>(Axis::II)] << 16 |
m_axis_state[static_cast<size_t>(Axis::I)] << 8 | m_axis_state[static_cast<size_t>(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<u8>(std::clamp(std::round(value), 0.0f, 255.0f));
}
+112
View File
@@ -0,0 +1,112 @@
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "controller.h"
#include <array>
#include <optional>
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<u32> 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<u8, static_cast<u8>(Axis::Count)> m_axis_state{};
// steering, merged to m_axis_state
std::array<float, 2> 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<AxisModifier, 3> m_half_axis_modifiers = {
DEFAULT_PEDAL_MODIFIER,
DEFAULT_PEDAL_MODIFIER,
DEFAULT_PEDAL_MODIFIER,
};
};
+62 -111
View File
@@ -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 <algorithm>
#include <cmath>
LOG_CHANNEL(Controller);
// Mapping of Button to index of corresponding bit in m_button_state
static constexpr std::array<u8, static_cast<size_t>(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<u8>(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<u32>(HalfAxis::I)))
{
return static_cast<float>(m_axis_state[index - (HALFAXIS_BIND_START_INDEX + static_cast<u32>(HalfAxis::I)) + 1]) *
(1.0f / 255.0f);
}
else if (index >= HALFAXIS_BIND_START_INDEX)
{
return static_cast<float>(m_half_axis_state[index - HALFAXIS_BIND_START_INDEX]) * (1.0f / 255.0f);
}
else if (index < static_cast<u32>(Button::Analog))
{
const u16 bit = u16(1) << s_button_indices[static_cast<u8>(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<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringLeft)) ||
index == (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringRight)))
{
value *= m_steering_sensitivity;
if (value < m_steering_deadzone)
value = 0.0f;
m_half_axis_state[index - static_cast<u32>(Button::Count)] =
static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f));
m_axis_state[static_cast<u32>(Axis::Steering)] =
ControllerHelpers::MergeHalfAxes(m_half_axis_state[static_cast<size_t>(HalfAxis::SteeringLeft)],
m_half_axis_state[static_cast<size_t>(HalfAxis::SteeringRight)], false);
}
else if (index >= static_cast<u32>(Button::Count))
{
// less one because of the two steering axes
const u32 sub_index = index - (static_cast<u32>(Button::Count) + 1);
if (sub_index >= m_axis_state.size())
return;
m_axis_state[sub_index] = static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f));
}
else if (index < static_cast<u32>(Button::Count))
{
const u16 bit = u16(1) << s_button_indices[static_cast<u8>(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<u32> NeGconRumble::GetAnalogInputBytes() const
{
return m_axis_state[static_cast<size_t>(Axis::L)] << 24 | m_axis_state[static_cast<size_t>(Axis::II)] << 16 |
m_axis_state[static_cast<size_t>(Axis::I)] << 8 | m_axis_state[static_cast<size_t>(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<s16>(
std::clamp(si.GetIntValue(section, "LargeMotorVibrationBias", DEFAULT_LARGE_MOTOR_VIBRATION_BIAS), -255, 255));
m_vibration_bias[1] = static_cast<s16>(
std::clamp(si.GetIntValue(section, "SmallMotorVibrationBias", DEFAULT_SMALL_MOTOR_VIBRATION_BIAS), -255, 255));
}
}
+2 -38
View File
@@ -3,24 +3,14 @@
#pragma once
#include "controller.h"
#include "negcon_base.h"
#include <array>
#include <memory>
#include <optional>
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<u32> 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<s16, NUM_MOTORS> 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<u8, static_cast<u8>(Axis::Count)> m_axis_state{};
enum : u8
{
LargeMotor = 0,
SmallMotor = 1
};
// steering, merged to m_axis_state
std::array<u8, 2> 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;
};