Skip to content
Permalink
Browse files

WiimoteEmu: Drum extension accuracy improvements.

  • Loading branch information...
jordan-woyak committed Feb 3, 2019
1 parent e39aa5b commit 0c81af74e1ec1a2fb513931f03a20565c8cb7737
@@ -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,45 +20,60 @@ 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,
}};

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)
{
m_pads->controls.emplace_back(
new ControllerEmu::Input(ControllerEmu::Translate, drum_pad_name));
}

// stick
constexpr auto gate_radius = ControlState(STICK_GATE_RADIUS) / STICK_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);

// Stick.
groups.emplace_back(m_stick =
new ControllerEmu::OctagonAnalogStick(_trans("Stick"), gate_radius));
new ControllerEmu::OctagonAnalogStick(_trans("Stick"), GATE_RADIUS));

// buttons
// 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());

// Drum pads.
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;

static_assert(std::tuple_size<decltype(m_pad_remaining_frames)>::value ==
drum_pad_bitmasks.size(),
"Array sizes do not match.");

// 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]);

// buttons
m_buttons->GetState(&drum_data.bt, drum_button_bitmasks.data());
drum_data.no_velocity_data_1 = 0;
drum_data.no_velocity_data_2 = 0;

// pads
m_pads->GetState(&drum_data.bt, drum_pad_bitmasks.data());
// Set softness from user-configured hit strength setting.
drum_data.softness = u8(7 - std::lround(m_hit_strength_setting.GetValue() * 7 / 100));

// flip button bits
drum_data.bt ^= 0xFFFF;
// 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 sy : 6;
u8 pad2 : 2; // always 0

u8 pad3 : 1; // unknown
u8 which : 5;
u8 none : 1;
u8 hhp : 1;

u8 pad4 : 1; // unknown
u8 velocity : 4; // unknown
u8 stick_x : 6;
// Seemingly random.
u8 unk1 : 2;

u8 stick_y : 6;
// Seemingly random.
u8 unk2 : 2;

// 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;

// 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 @@ class Drums : public Extension1stParty

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,

0 comments on commit 0c81af7

Please sign in to comment.
You can’t perform that action at this time.