Merge pull request #8036 from jordan-woyak/emu-drums-fix

WiimoteEmu: Drum extension accuracy improvements.
This commit is contained in:
Connor McLaughlin 2019-05-01 11:25:31 +10:00 committed by GitHub
commit 37c85b32c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 189 additions and 54 deletions

View File

@ -4,9 +4,8 @@
#include "Core/HW/WiimoteEmu/Extension/Drums.h" #include "Core/HW/WiimoteEmu/Extension/Drums.h"
#include <array>
#include <cassert> #include <cassert>
#include <cstring> #include <type_traits>
#include "Common/BitUtils.h" #include "Common/BitUtils.h"
#include "Common/Common.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<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_RED,
Drums::PAD_YELLOW, Drums::PAD_YELLOW,
Drums::PAD_BLUE, Drums::PAD_BLUE,
Drums::PAD_GREEN,
Drums::PAD_ORANGE, Drums::PAD_ORANGE,
Drums::PAD_GREEN,
Drums::PAD_BASS, Drums::PAD_BASS,
}}; }};
@ -34,19 +33,28 @@ constexpr std::array<const char*, 6> drum_pad_names{{
_trans("Red"), _trans("Red"),
_trans("Yellow"), _trans("Yellow"),
_trans("Blue"), _trans("Blue"),
_trans("Green"),
_trans("Orange"), _trans("Orange"),
_trans("Green"),
_trans("Bass"), _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_MINUS,
Drums::BUTTON_PLUS, Drums::BUTTON_PLUS,
}}; }};
Drums::Drums() : Extension1stParty(_trans("Drums")) Drums::Drums() : Extension1stParty(_trans("Drums"))
{ {
// pads // Pads.
groups.emplace_back(m_pads = new ControllerEmu::Buttons(_trans("Pads"))); groups.emplace_back(m_pads = new ControllerEmu::Buttons(_trans("Pads")));
for (auto& drum_pad_name : drum_pad_names) 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)); new ControllerEmu::Input(ControllerEmu::Translate, drum_pad_name));
} }
// stick m_pads->AddSetting(&m_hit_strength_setting,
constexpr auto gate_radius = ControlState(STICK_GATE_RADIUS) / STICK_RADIUS; // i18n: Refers to how hard emulated drum pads are struck.
groups.emplace_back(m_stick = {_trans("Hit Strength"),
new ControllerEmu::OctagonAnalogStick(_trans("Stick"), gate_radius)); // 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"))); 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, "-"));
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 = {}; 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(); const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
drum_data.sx = static_cast<u8>((stick_state.x * STICK_RADIUS) + STICK_CENTER); drum_data.stick_x = MapFloat(stick_state.x, STICK_CENTER, STICK_MIN, STICK_MAX);
drum_data.sy = static_cast<u8>((stick_state.y * STICK_RADIUS) + STICK_CENTER); drum_data.stick_y = MapFloat(stick_state.y, STICK_CENTER, STICK_MIN, STICK_MAX);
} }
// TODO: Implement these: // Buttons.
drum_data.which = 0x1F; m_buttons->GetState(&drum_data.buttons, drum_button_bitmasks.data());
drum_data.none = 1;
drum_data.hhp = 1;
drum_data.velocity = 0xf;
drum_data.softness = 7;
// buttons // Drum pads.
m_buttons->GetState(&drum_data.bt, drum_button_bitmasks.data()); u8 current_pad_input = 0;
m_pads->GetState(&current_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 static_assert(std::tuple_size<decltype(m_pad_remaining_frames)>::value ==
m_pads->GetState(&drum_data.bt, drum_pad_bitmasks.data()); drum_pad_bitmasks.size(),
"Array sizes do not match.");
// flip button bits // Figure out which velocity id to send. (needs to be sent once for each newly hit drum-pad)
drum_data.bt ^= 0xFFFF; 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; Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = drum_data;
} }
bool Drums::IsButtonPressed() const bool Drums::IsButtonPressed() const
{ {
u16 buttons = 0; u8 buttons = 0;
m_buttons->GetState(&buttons, drum_button_bitmasks.data()); 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() void Drums::Reset()
@ -110,7 +183,21 @@ void Drums::Reset()
m_reg.identifier = drums_id; 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) ControllerEmu::ControlGroup* Drums::GetGroup(DrumsGroup group)

View File

@ -4,7 +4,10 @@
#pragma once #pragma once
#include <array>
#include "Core/HW/WiimoteEmu/Extension/Extension.h" #include "Core/HW/WiimoteEmu/Extension/Extension.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
namespace ControllerEmu namespace ControllerEmu
{ {
@ -28,25 +31,51 @@ class Drums : public Extension1stParty
public: public:
struct DataFormat struct DataFormat
{ {
u8 sx : 6; u8 stick_x : 6;
u8 pad1 : 2; // always 0 // Seemingly random.
u8 unk1 : 2;
u8 sy : 6; u8 stick_y : 6;
u8 pad2 : 2; // always 0 // Seemingly random.
u8 unk2 : 2;
u8 pad3 : 1; // unknown // Always 1 with no velocity data and seemingly random otherwise.
u8 which : 5; u8 unk3 : 1;
u8 none : 1; // For which "pad" the velocity data is for.
u8 hhp : 1; u8 velocity_id : 7;
u8 pad4 : 1; // unknown // Always 1 with no velocity data and seemingly random otherwise.
u8 velocity : 4; // unknown 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; u8 softness : 3;
u16 bt; // buttons // Button bits.
u8 buttons;
// Drum-pad bits.
u8 drum_pads;
}; };
static_assert(sizeof(DataFormat) == 6, "Wrong size"); 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(); Drums();
void Update() override; void Update() override;
@ -55,28 +84,47 @@ public:
ControllerEmu::ControlGroup* GetGroup(DrumsGroup group); ControllerEmu::ControlGroup* GetGroup(DrumsGroup group);
enum void DoState(PointerWrap& p) override;
enum : u8
{ {
BUTTON_PLUS = 0x04, BUTTON_PLUS = 0x04,
BUTTON_MINUS = 0x10, BUTTON_MINUS = 0x10,
PAD_BASS = 0x0400, // FYI: The low/high bits of the button byte are "random" when velocity data is present.
PAD_BLUE = 0x0800, // HAVE_VELOCITY_DATA = 0b10000001,
PAD_GREEN = 0x1000,
PAD_YELLOW = 0x2000,
PAD_RED = 0x4000,
PAD_ORANGE = 0x8000,
}; };
static const u8 STICK_CENTER = 0x20; enum : u8
static const u8 STICK_RADIUS = 0x1f; {
// 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? // Note: My hardware's octagon stick produced the complete range of values (0 - 0x3f)
static const u8 STICK_GATE_RADIUS = 0x16; // 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: private:
ControllerEmu::Buttons* m_buttons; ControllerEmu::Buttons* m_buttons;
ControllerEmu::Buttons* m_pads; ControllerEmu::Buttons* m_pads;
ControllerEmu::AnalogStick* m_stick; 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 } // namespace WiimoteEmu

View File

@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread; static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system // 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. // Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list, // Versions after 42 don't need to be added to this list,