Merge pull request #8036 from jordan-woyak/emu-drums-fix
WiimoteEmu: Drum extension accuracy improvements.
This commit is contained in:
commit
37c85b32c0
|
@ -4,9 +4,8 @@
|
|||
|
||||
#include "Core/HW/WiimoteEmu/Extension/Drums.h"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Common/BitUtils.h"
|
||||
#include "Common/Common.h"
|
||||
|
@ -21,12 +20,12 @@ namespace WiimoteEmu
|
|||
{
|
||||
constexpr std::array<u8, 6> drums_id{{0x01, 0x00, 0xa4, 0x20, 0x01, 0x03}};
|
||||
|
||||
constexpr std::array<u16, 6> drum_pad_bitmasks{{
|
||||
constexpr std::array<u8, 6> drum_pad_bitmasks{{
|
||||
Drums::PAD_RED,
|
||||
Drums::PAD_YELLOW,
|
||||
Drums::PAD_BLUE,
|
||||
Drums::PAD_GREEN,
|
||||
Drums::PAD_ORANGE,
|
||||
Drums::PAD_GREEN,
|
||||
Drums::PAD_BASS,
|
||||
}};
|
||||
|
||||
|
@ -34,19 +33,28 @@ constexpr std::array<const char*, 6> drum_pad_names{{
|
|||
_trans("Red"),
|
||||
_trans("Yellow"),
|
||||
_trans("Blue"),
|
||||
_trans("Green"),
|
||||
_trans("Orange"),
|
||||
_trans("Green"),
|
||||
_trans("Bass"),
|
||||
}};
|
||||
|
||||
constexpr std::array<u16, 2> drum_button_bitmasks{{
|
||||
constexpr std::array<Drums::VelocityID, 6> drum_pad_velocity_ids{{
|
||||
Drums::VelocityID::Red,
|
||||
Drums::VelocityID::Yellow,
|
||||
Drums::VelocityID::Blue,
|
||||
Drums::VelocityID::Orange,
|
||||
Drums::VelocityID::Green,
|
||||
Drums::VelocityID::Bass,
|
||||
}};
|
||||
|
||||
constexpr std::array<u8, 2> drum_button_bitmasks{{
|
||||
Drums::BUTTON_MINUS,
|
||||
Drums::BUTTON_PLUS,
|
||||
}};
|
||||
|
||||
Drums::Drums() : Extension1stParty(_trans("Drums"))
|
||||
{
|
||||
// pads
|
||||
// Pads.
|
||||
groups.emplace_back(m_pads = new ControllerEmu::Buttons(_trans("Pads")));
|
||||
for (auto& drum_pad_name : drum_pad_names)
|
||||
{
|
||||
|
@ -54,12 +62,18 @@ Drums::Drums() : Extension1stParty(_trans("Drums"))
|
|||
new ControllerEmu::Input(ControllerEmu::Translate, drum_pad_name));
|
||||
}
|
||||
|
||||
// stick
|
||||
constexpr auto gate_radius = ControlState(STICK_GATE_RADIUS) / STICK_RADIUS;
|
||||
groups.emplace_back(m_stick =
|
||||
new ControllerEmu::OctagonAnalogStick(_trans("Stick"), gate_radius));
|
||||
m_pads->AddSetting(&m_hit_strength_setting,
|
||||
// i18n: Refers to how hard emulated drum pads are struck.
|
||||
{_trans("Hit Strength"),
|
||||
// i18n: The symbol for percent.
|
||||
_trans("%")},
|
||||
50);
|
||||
|
||||
// buttons
|
||||
// Stick.
|
||||
groups.emplace_back(m_stick =
|
||||
new ControllerEmu::OctagonAnalogStick(_trans("Stick"), GATE_RADIUS));
|
||||
|
||||
// Buttons.
|
||||
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
|
||||
m_buttons->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "-"));
|
||||
m_buttons->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "+"));
|
||||
|
@ -69,39 +83,98 @@ void Drums::Update()
|
|||
{
|
||||
DataFormat drum_data = {};
|
||||
|
||||
// stick
|
||||
// The meaning of these bits are unknown but they are usually set.
|
||||
drum_data.unk1 = 0b11;
|
||||
drum_data.unk2 = 0b11;
|
||||
drum_data.unk3 = 0b1;
|
||||
drum_data.unk4 = 0b1;
|
||||
drum_data.unk5 = 0b11;
|
||||
|
||||
// Send no velocity data by default.
|
||||
drum_data.velocity_id = u8(VelocityID::None);
|
||||
drum_data.no_velocity_data_1 = 1;
|
||||
drum_data.no_velocity_data_2 = 1;
|
||||
drum_data.softness = 7;
|
||||
|
||||
// Stick.
|
||||
{
|
||||
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
|
||||
|
||||
drum_data.sx = static_cast<u8>((stick_state.x * STICK_RADIUS) + STICK_CENTER);
|
||||
drum_data.sy = static_cast<u8>((stick_state.y * STICK_RADIUS) + STICK_CENTER);
|
||||
drum_data.stick_x = MapFloat(stick_state.x, STICK_CENTER, STICK_MIN, STICK_MAX);
|
||||
drum_data.stick_y = MapFloat(stick_state.y, STICK_CENTER, STICK_MIN, STICK_MAX);
|
||||
}
|
||||
|
||||
// TODO: Implement these:
|
||||
drum_data.which = 0x1F;
|
||||
drum_data.none = 1;
|
||||
drum_data.hhp = 1;
|
||||
drum_data.velocity = 0xf;
|
||||
drum_data.softness = 7;
|
||||
// Buttons.
|
||||
m_buttons->GetState(&drum_data.buttons, drum_button_bitmasks.data());
|
||||
|
||||
// buttons
|
||||
m_buttons->GetState(&drum_data.bt, drum_button_bitmasks.data());
|
||||
// Drum pads.
|
||||
u8 current_pad_input = 0;
|
||||
m_pads->GetState(¤t_pad_input, drum_pad_bitmasks.data());
|
||||
m_new_pad_hits |= ~m_prev_pad_input & current_pad_input;
|
||||
m_prev_pad_input = current_pad_input;
|
||||
|
||||
// pads
|
||||
m_pads->GetState(&drum_data.bt, drum_pad_bitmasks.data());
|
||||
static_assert(std::tuple_size<decltype(m_pad_remaining_frames)>::value ==
|
||||
drum_pad_bitmasks.size(),
|
||||
"Array sizes do not match.");
|
||||
|
||||
// flip button bits
|
||||
drum_data.bt ^= 0xFFFF;
|
||||
// Figure out which velocity id to send. (needs to be sent once for each newly hit drum-pad)
|
||||
for (std::size_t i = 0; i != drum_pad_bitmasks.size(); ++i)
|
||||
{
|
||||
const auto drum_pad = drum_pad_bitmasks[i];
|
||||
|
||||
if (m_new_pad_hits & drum_pad)
|
||||
{
|
||||
// Clear the bit so velocity data is not sent again until the next hit.
|
||||
m_new_pad_hits &= ~drum_pad;
|
||||
|
||||
drum_data.velocity_id = u8(drum_pad_velocity_ids[i]);
|
||||
|
||||
drum_data.no_velocity_data_1 = 0;
|
||||
drum_data.no_velocity_data_2 = 0;
|
||||
|
||||
// Set softness from user-configured hit strength setting.
|
||||
drum_data.softness = u8(7 - std::lround(m_hit_strength_setting.GetValue() * 7 / 100));
|
||||
|
||||
// A drum-pad hit causes the relevent bit to be triggered for the next 10 frames.
|
||||
constexpr u8 HIT_FRAME_COUNT = 10;
|
||||
|
||||
m_pad_remaining_frames[i] = HIT_FRAME_COUNT;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out which drum-pad bits to send.
|
||||
// Note: Relevent bits are not set until after velocity data has been sent.
|
||||
// My drums never exposed simultaneous hits. One pad bit was always sent before the other.
|
||||
for (std::size_t i = 0; i != drum_pad_bitmasks.size(); ++i)
|
||||
{
|
||||
auto& remaining_frames = m_pad_remaining_frames[i];
|
||||
|
||||
if (remaining_frames != 0)
|
||||
{
|
||||
drum_data.drum_pads |= drum_pad_bitmasks[i];
|
||||
--remaining_frames;
|
||||
}
|
||||
}
|
||||
|
||||
// Flip button and drum-pad bits. (0 == pressed)
|
||||
drum_data.buttons ^= 0xff;
|
||||
drum_data.drum_pads ^= 0xff;
|
||||
|
||||
// Copy data to proper region in the "register".
|
||||
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = drum_data;
|
||||
}
|
||||
|
||||
bool Drums::IsButtonPressed() const
|
||||
{
|
||||
u16 buttons = 0;
|
||||
u8 buttons = 0;
|
||||
m_buttons->GetState(&buttons, drum_button_bitmasks.data());
|
||||
m_pads->GetState(&buttons, drum_pad_bitmasks.data());
|
||||
return buttons != 0;
|
||||
|
||||
u8 pads = 0;
|
||||
m_pads->GetState(&pads, drum_pad_bitmasks.data());
|
||||
|
||||
return buttons != 0 || pads != 0;
|
||||
}
|
||||
|
||||
void Drums::Reset()
|
||||
|
@ -110,7 +183,21 @@ void Drums::Reset()
|
|||
|
||||
m_reg.identifier = drums_id;
|
||||
|
||||
// TODO: Is there calibration data?
|
||||
// Both 16-byte blocks of calibration data seem to be 0xff filled.
|
||||
m_reg.calibration.fill(0xff);
|
||||
|
||||
m_prev_pad_input = 0;
|
||||
m_new_pad_hits = 0;
|
||||
m_pad_remaining_frames = {};
|
||||
}
|
||||
|
||||
void Drums::DoState(PointerWrap& p)
|
||||
{
|
||||
EncryptedExtension::DoState(p);
|
||||
|
||||
p.Do(m_prev_pad_input);
|
||||
p.Do(m_new_pad_hits);
|
||||
p.Do(m_pad_remaining_frames);
|
||||
}
|
||||
|
||||
ControllerEmu::ControlGroup* Drums::GetGroup(DrumsGroup group)
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "Core/HW/WiimoteEmu/Extension/Extension.h"
|
||||
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
|
||||
|
||||
namespace ControllerEmu
|
||||
{
|
||||
|
@ -28,25 +31,51 @@ class Drums : public Extension1stParty
|
|||
public:
|
||||
struct DataFormat
|
||||
{
|
||||
u8 sx : 6;
|
||||
u8 pad1 : 2; // always 0
|
||||
u8 stick_x : 6;
|
||||
// Seemingly random.
|
||||
u8 unk1 : 2;
|
||||
|
||||
u8 sy : 6;
|
||||
u8 pad2 : 2; // always 0
|
||||
u8 stick_y : 6;
|
||||
// Seemingly random.
|
||||
u8 unk2 : 2;
|
||||
|
||||
u8 pad3 : 1; // unknown
|
||||
u8 which : 5;
|
||||
u8 none : 1;
|
||||
u8 hhp : 1;
|
||||
// Always 1 with no velocity data and seemingly random otherwise.
|
||||
u8 unk3 : 1;
|
||||
// For which "pad" the velocity data is for.
|
||||
u8 velocity_id : 7;
|
||||
|
||||
u8 pad4 : 1; // unknown
|
||||
u8 velocity : 4; // unknown
|
||||
// Always 1 with no velocity data and seemingly random otherwise.
|
||||
u8 unk4 : 1;
|
||||
// 1 with no velocity data and 0 when velocity data is present.
|
||||
u8 no_velocity_data_1 : 1;
|
||||
// These two bits seem to always be set. (0b11)
|
||||
u8 unk5 : 2;
|
||||
// 1 with no velocity data and 0 when velocity data is present.
|
||||
u8 no_velocity_data_2 : 1;
|
||||
// How "soft" a drum pad has been hit as a range from 0:very-hard to 7:very-soft.
|
||||
u8 softness : 3;
|
||||
|
||||
u16 bt; // buttons
|
||||
// Button bits.
|
||||
u8 buttons;
|
||||
|
||||
// Drum-pad bits.
|
||||
u8 drum_pads;
|
||||
};
|
||||
static_assert(sizeof(DataFormat) == 6, "Wrong size");
|
||||
|
||||
enum class VelocityID : u8
|
||||
{
|
||||
None = 0b1111111,
|
||||
Bass = 0b1011011,
|
||||
// TODO: Implement HiHat.
|
||||
// HiHat = 0b0011011,
|
||||
Red = 0b1011001,
|
||||
Yellow = 0b1010001,
|
||||
Blue = 0b1001111,
|
||||
Orange = 0b1001110,
|
||||
Green = 0b1010010,
|
||||
};
|
||||
|
||||
Drums();
|
||||
|
||||
void Update() override;
|
||||
|
@ -55,28 +84,47 @@ public:
|
|||
|
||||
ControllerEmu::ControlGroup* GetGroup(DrumsGroup group);
|
||||
|
||||
enum
|
||||
void DoState(PointerWrap& p) override;
|
||||
|
||||
enum : u8
|
||||
{
|
||||
BUTTON_PLUS = 0x04,
|
||||
BUTTON_MINUS = 0x10,
|
||||
|
||||
PAD_BASS = 0x0400,
|
||||
PAD_BLUE = 0x0800,
|
||||
PAD_GREEN = 0x1000,
|
||||
PAD_YELLOW = 0x2000,
|
||||
PAD_RED = 0x4000,
|
||||
PAD_ORANGE = 0x8000,
|
||||
// FYI: The low/high bits of the button byte are "random" when velocity data is present.
|
||||
// HAVE_VELOCITY_DATA = 0b10000001,
|
||||
};
|
||||
|
||||
static const u8 STICK_CENTER = 0x20;
|
||||
static const u8 STICK_RADIUS = 0x1f;
|
||||
enum : u8
|
||||
{
|
||||
// FYI: HiHat sets no bits here.
|
||||
PAD_BASS = 0x04,
|
||||
PAD_BLUE = 0x08,
|
||||
PAD_GREEN = 0x10,
|
||||
PAD_YELLOW = 0x20,
|
||||
PAD_RED = 0x40,
|
||||
PAD_ORANGE = 0x80,
|
||||
};
|
||||
|
||||
// TODO: Test real hardware. Is this accurate?
|
||||
static const u8 STICK_GATE_RADIUS = 0x16;
|
||||
// Note: My hardware's octagon stick produced the complete range of values (0 - 0x3f)
|
||||
// It also had perfect center values of 0x20 with absolutely no "play".
|
||||
static constexpr ControlState GATE_RADIUS = 1.0;
|
||||
static constexpr u8 STICK_MIN = 0x00;
|
||||
static constexpr u8 STICK_CENTER = 0x20;
|
||||
static constexpr u8 STICK_MAX = 0x3f;
|
||||
|
||||
private:
|
||||
ControllerEmu::Buttons* m_buttons;
|
||||
ControllerEmu::Buttons* m_pads;
|
||||
ControllerEmu::AnalogStick* m_stick;
|
||||
|
||||
ControllerEmu::SettingValue<double> m_hit_strength_setting;
|
||||
|
||||
// Holds previous user input state to watch for "new" hits.
|
||||
u8 m_prev_pad_input;
|
||||
// Holds new drum pad hits that still need velocity data to be sent.
|
||||
u8 m_new_pad_hits;
|
||||
// Holds how many more frames to send each drum-pad bit.
|
||||
std::array<u8, 6> m_pad_remaining_frames;
|
||||
};
|
||||
} // namespace WiimoteEmu
|
||||
|
|
|
@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
|
|||
static std::thread g_save_thread;
|
||||
|
||||
// Don't forget to increase this after doing changes on the savestate system
|
||||
static const u32 STATE_VERSION = 109; // Last changed in PR 7861
|
||||
static const u32 STATE_VERSION = 110; // Last changed in PR 8036
|
||||
|
||||
// Maps savestate versions to Dolphin versions.
|
||||
// Versions after 42 don't need to be added to this list,
|
||||
|
|
Loading…
Reference in New Issue