diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.cpp index 8675434315..311648b781 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.cpp @@ -4,9 +4,8 @@ #include "Core/HW/WiimoteEmu/Extension/Drums.h" -#include #include -#include +#include #include "Common/BitUtils.h" #include "Common/Common.h" @@ -21,12 +20,12 @@ namespace WiimoteEmu { constexpr std::array drums_id{{0x01, 0x00, 0xa4, 0x20, 0x01, 0x03}}; -constexpr std::array drum_pad_bitmasks{{ +constexpr std::array 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 drum_pad_names{{ _trans("Red"), _trans("Yellow"), _trans("Blue"), - _trans("Green"), _trans("Orange"), + _trans("Green"), _trans("Bass"), }}; -constexpr std::array drum_button_bitmasks{{ +constexpr std::array 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 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((stick_state.x * STICK_RADIUS) + STICK_CENTER); - drum_data.sy = static_cast((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::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(&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) diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.h b/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.h index 6b6159eaa8..baaf497319 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Drums.h @@ -4,7 +4,10 @@ #pragma once +#include + #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 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 m_pad_remaining_frames; }; } // namespace WiimoteEmu diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index c0a452f63a..4ee6227987 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -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,