diff --git a/rpcs3/Emu/Io/RB3MidiDrums.cpp b/rpcs3/Emu/Io/RB3MidiDrums.cpp index 427e50e317..df6acecfd2 100644 --- a/rpcs3/Emu/Io/RB3MidiDrums.cpp +++ b/rpcs3/Emu/Io/RB3MidiDrums.cpp @@ -9,9 +9,6 @@ using namespace std::chrono_literals; LOG_CHANNEL(rb3_midi_drums_log); -namespace -{ - namespace controller { @@ -158,67 +155,6 @@ u8 min_velocity() return g_cfg_rb3drums.minimum_velocity; } -enum class Id : u8 -{ - // Each 'Note' can be triggered by multiple different numbers. - // Keeping them flattened in an enum for simplicity / switch statement usage. - - // These follow the rockband 3 midi pro adapter support. - Snare0 = 38, - Snare1 = 31, - Snare2 = 34, - Snare3 = 37, - Snare4 = 39, - HiTom0 = 48, - HiTom1 = 50, - LowTom0 = 45, - LowTom1 = 47, - FloorTom0 = 41, - FloorTom1 = 43, - Hihat0 = 22, - Hihat1 = 26, - Hihat2 = 42, - Hihat3 = 54, - Ride0 = 51, - Ride1 = 53, - Ride2 = 56, - Ride3 = 59, - Crash0 = 49, - Crash1 = 52, - Crash2 = 55, - Crash3 = 57, - Kick0 = 33, - Kick1 = 35, - Kick2 = 36, - HihatPedal = 44, - - // These are from alesis nitro mesh max. ymmv. - SnareRim = 40, // midi pro adapter counts this as snare. - HihatWithPedalUp = 46, // The midi pro adapter considers this a normal hihat hit. - HihatPedalPartial = 23, // If pedal is not 100% down, this will be sent instead of a normal hihat hit. - - // Internal value used for converting midi CC. - // Values past 127 are not used in midi notes. - MidiCC = 255, -}; - -// Intermediate mapping regardless of which midi ids triggered it. -enum class Note : u8 -{ - Invalid, - Kick, - HihatPedal, - Snare, - SnareRim, - HiTom, - LowTom, - FloorTom, - HihatWithPedalUp, - Hihat, - Ride, - Crash, -}; - Note str_to_note(const std::string_view name) { static const std::unordered_map mapping{ @@ -298,27 +234,21 @@ std::unordered_map create_id_to_note_mapping() {Id::Crash2, Note::Crash}, {Id::Crash3, Note::Crash}, }; + // Apply configured overrides. - auto split = fmt::split(g_cfg_rb3drums.midi_overrides.to_string(), {","}); - for (const auto& segment : split) + const std::vector segments = fmt::split(g_cfg_rb3drums.midi_overrides.to_string(), {","}); + for (const std::string& segment : segments) { - if (auto midi_override = parse_midi_override(segment)) + if (const auto midi_override = parse_midi_override(segment)) { - auto id = midi_override->first; - auto note = midi_override->second; + const auto id = midi_override->first; + const auto note = midi_override->second; mapping[id] = note; } } return mapping; } -Note id_to_note(Id id) -{ - static const auto mapping = create_id_to_note_mapping(); - const auto it = mapping.find(id); - return it != mapping.cend() ? it->second : Note::Invalid; -} - namespace combo { @@ -345,39 +275,18 @@ std::vector parse_combo(const std::string_view name, const std::string_view return notes; } -struct Definition -{ - std::string name; - std::vector notes; - std::function create_state; - - Definition(std::string name, const std::string_view csv, const std::function create_state) - : name{std::move(name)} - , notes{parse_combo(this->name, csv)} - , create_state{create_state} - {} -}; - std::chrono::milliseconds window() { return std::chrono::milliseconds{g_cfg_rb3drums.combo_window_ms}; } -const std::vector& definitions() -{ - // Only parse once and cache. - static const std::vector defs{ - {"start", g_cfg_rb3drums.combo_start.to_string(), []{ return drum::start_state(); }}, - {"select", g_cfg_rb3drums.combo_select.to_string(), []{ return drum::select_state(); }}, - {"hold kick", g_cfg_rb3drums.combo_toggle_hold_kick.to_string(), []{ return drum::toggle_hold_kick_state(); }} - }; - return defs; -} - } } // namespace midi +namespace +{ + void set_flag(u8* buf, [[maybe_unused]] std::string_view name, const controller::FlagByIndex& fbi) { auto i = fbi[drum::INDEX]; @@ -397,6 +306,12 @@ void set_flag_if_any(u8* buf, std::string_view name, const controller::FlagByInd } +usb_device_rb3_midi_drums::Definition::Definition(std::string name, const std::string_view csv, const std::function create_state) + : name{std::move(name)} + , notes{midi::combo::parse_combo(this->name, csv)} + , create_state{create_state} +{} + usb_device_rb3_midi_drums::usb_device_rb3_midi_drums(const std::array& location, const std::string& device_name) : usb_device_emulated(location) { @@ -603,6 +518,12 @@ void usb_device_rb3_midi_drums::interrupt_transfer(u32 buf_size, u8* buf, u32 /* } memcpy(buf, bytes.data(), bytes.size()); + if (g_cfg_rb3drums.reload_requested) + { + m_id_to_note_mapping = midi::create_id_to_note_mapping(); + combo.reload_definitions(); + } + while (true) { u8 midi_msg[32]; @@ -712,6 +633,12 @@ rb3drums::KitState usb_device_rb3_midi_drums::parse_midi_message(u8* msg, usz si return rb3drums::KitState{}; } +midi::Note usb_device_rb3_midi_drums::id_to_note(midi::Id id) +{ + const auto it = m_id_to_note_mapping.find(id); + return it != m_id_to_note_mapping.cend() ? it->second : midi::Note::Invalid; +} + rb3drums::KitState usb_device_rb3_midi_drums::parse_midi_note(const u8 id, const u8 velocity) { if (velocity < midi::min_velocity()) @@ -722,7 +649,7 @@ rb3drums::KitState usb_device_rb3_midi_drums::parse_midi_note(const u8 id, const rb3drums::KitState kit_state{}; kit_state.expiry = std::chrono::steady_clock::now() + drum::hit_duration(); - auto note = midi::id_to_note(static_cast(id)); + const midi::Note note = id_to_note(static_cast(id)); switch (note) { case midi::Note::Kick: kit_state.kick_pedal = velocity; break; @@ -751,7 +678,8 @@ bool usb_device_rb3_midi_drums::is_midi_cc(const u8 id, const u8 value) { return false; } - auto is_past_threshold = [](u8 value) + + const auto is_past_threshold = [](u8 value) { const u8 threshold = g_cfg_rb3drums.midi_cc_threshold; return g_cfg_rb3drums.midi_cc_invert_threshold @@ -834,6 +762,15 @@ bool rb3drums::KitState::is_drum() const return std::max({snare, hi_tom, low_tom, floor_tom}) >= midi::min_velocity(); } +void usb_device_rb3_midi_drums::ComboTracker::reload_definitions() +{ + m_definitions = { + {"start", g_cfg_rb3drums.combo_start.to_string(), []{ return drum::start_state(); }}, + {"select", g_cfg_rb3drums.combo_select.to_string(), []{ return drum::select_state(); }}, + {"hold kick", g_cfg_rb3drums.combo_toggle_hold_kick.to_string(), []{ return drum::toggle_hold_kick_state(); }} + }; +} + void usb_device_rb3_midi_drums::ComboTracker::add(u8 note) { if (!midi_notes.empty() && std::chrono::steady_clock::now() >= expiry) @@ -843,9 +780,8 @@ void usb_device_rb3_midi_drums::ComboTracker::add(u8 note) } const usz i = midi_notes.size(); - const auto& defs = midi::combo::definitions(); bool is_in_combo = false; - for (const auto& def : defs) + for (const auto& def : m_definitions) { if (i < def.notes.size() && note == def.notes[i]) { @@ -879,7 +815,7 @@ std::optional usb_device_rb3_midi_drums::ComboTracker::take_ { return {}; } - for (const auto& combo : midi::combo::definitions()) + for (const auto& combo : m_definitions) { if (midi_notes == combo.notes) { diff --git a/rpcs3/Emu/Io/RB3MidiDrums.h b/rpcs3/Emu/Io/RB3MidiDrums.h index 8c6bd4cf99..9d13a81812 100644 --- a/rpcs3/Emu/Io/RB3MidiDrums.h +++ b/rpcs3/Emu/Io/RB3MidiDrums.h @@ -38,10 +38,83 @@ struct KitState bool is_drum() const; }; -}; // namespace rb3drums +} // namespace rb3drums + +namespace midi +{ + +enum class Id : u8 +{ + // Each 'Note' can be triggered by multiple different numbers. + // Keeping them flattened in an enum for simplicity / switch statement usage. + + // These follow the rockband 3 midi pro adapter support. + Snare0 = 38, + Snare1 = 31, + Snare2 = 34, + Snare3 = 37, + Snare4 = 39, + HiTom0 = 48, + HiTom1 = 50, + LowTom0 = 45, + LowTom1 = 47, + FloorTom0 = 41, + FloorTom1 = 43, + Hihat0 = 22, + Hihat1 = 26, + Hihat2 = 42, + Hihat3 = 54, + Ride0 = 51, + Ride1 = 53, + Ride2 = 56, + Ride3 = 59, + Crash0 = 49, + Crash1 = 52, + Crash2 = 55, + Crash3 = 57, + Kick0 = 33, + Kick1 = 35, + Kick2 = 36, + HihatPedal = 44, + + // These are from alesis nitro mesh max. ymmv. + SnareRim = 40, // midi pro adapter counts this as snare. + HihatWithPedalUp = 46, // The midi pro adapter considers this a normal hihat hit. + HihatPedalPartial = 23, // If pedal is not 100% down, this will be sent instead of a normal hihat hit. + + // Internal value used for converting midi CC. + // Values past 127 are not used in midi notes. + MidiCC = 255, +}; + +// Intermediate mapping regardless of which midi ids triggered it. +enum class Note : u8 +{ + Invalid, + Kick, + HihatPedal, + Snare, + SnareRim, + HiTom, + LowTom, + FloorTom, + HihatWithPedalUp, + Hihat, + Ride, + Crash, +}; + +} class usb_device_rb3_midi_drums : public usb_device_emulated { +public: + usb_device_rb3_midi_drums(const std::array& location, const std::string& device_name); + ~usb_device_rb3_midi_drums(); + + void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override; + void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) override; + private: usz response_pos{}; bool buttons_enabled{}; @@ -50,9 +123,19 @@ private: bool hold_kick{}; bool midi_cc_triggered{}; + struct Definition + { + std::string name; + std::vector notes; + std::function create_state; + + Definition(std::string name, const std::string_view csv, const std::function create_state); + }; + class ComboTracker { public: + void reload_definitions(); void add(u8 note); void reset(); std::optional take_state(); @@ -60,18 +143,15 @@ private: private: std::chrono::steady_clock::time_point expiry; std::vector midi_notes; + std::vector m_definitions; }; ComboTracker combo; + std::unordered_map m_id_to_note_mapping; + + midi::Note id_to_note(midi::Id id); rb3drums::KitState parse_midi_message(u8* msg, usz size); rb3drums::KitState parse_midi_note(u8 id, u8 velocity); bool is_midi_cc(u8 id, u8 value); void write_state(u8* buf, const rb3drums::KitState&); - -public: - usb_device_rb3_midi_drums(const std::array& location, const std::string& device_name); - ~usb_device_rb3_midi_drums(); - - void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override; - void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) override; }; diff --git a/rpcs3/Emu/Io/rb3drums_config.cpp b/rpcs3/Emu/Io/rb3drums_config.cpp index 2d40bfb431..1e308d8b70 100644 --- a/rpcs3/Emu/Io/rb3drums_config.cpp +++ b/rpcs3/Emu/Io/rb3drums_config.cpp @@ -33,7 +33,7 @@ bool cfg_rb3drums::load() return false; } -void cfg_rb3drums::save() const +void cfg_rb3drums::save() { cfg_log.notice("Saving rb3drums config to '%s'", path); @@ -41,4 +41,6 @@ void cfg_rb3drums::save() const { cfg_log.error("Failed to save rb3drums config to '%s' (error=%s)", path, fs::g_tls_error); } + + reload_requested = true; } diff --git a/rpcs3/Emu/Io/rb3drums_config.h b/rpcs3/Emu/Io/rb3drums_config.h index cd638e0270..5910d88d72 100644 --- a/rpcs3/Emu/Io/rb3drums_config.h +++ b/rpcs3/Emu/Io/rb3drums_config.h @@ -6,22 +6,24 @@ struct cfg_rb3drums final : cfg::node { cfg_rb3drums(); bool load(); - void save() const; + void save(); cfg::uint<1, 100> pulse_ms{this, "Pulse width ms", 30, true}; cfg::uint<1, 127> minimum_velocity{this, "Minimum velocity", 10, true}; cfg::uint<1, 5000> combo_window_ms{this, "Combo window in milliseconds", 2000, true}; cfg::_bool stagger_cymbals{this, "Stagger cymbal hits", true, true}; - cfg::string midi_overrides{this, "Midi id to note override", ""}; - cfg::string combo_start{this, "Combo Start", "HihatPedal,HihatPedal,HihatPedal,Snare"}; - cfg::string combo_select{this, "Combo Select", "HihatPedal,HihatPedal,HihatPedal,SnareRim"}; - cfg::string combo_toggle_hold_kick{this, "Combo Toggle Hold Kick", "HihatPedal,HihatPedal,HihatPedal,Kick"}; + cfg::string midi_overrides{this, "Midi id to note override", "", true}; + cfg::string combo_start{this, "Combo Start", "HihatPedal,HihatPedal,HihatPedal,Snare", true}; + cfg::string combo_select{this, "Combo Select", "HihatPedal,HihatPedal,HihatPedal,SnareRim", true}; + cfg::string combo_toggle_hold_kick{this, "Combo Toggle Hold Kick", "HihatPedal,HihatPedal,HihatPedal,Kick", true}; cfg::uint<0, 255> midi_cc_status{this, "Midi CC status", 0xB0, true}; cfg::uint<0, 127> midi_cc_number{this, "Midi CC control number", 4, true}; cfg::uint<0, 127> midi_cc_threshold{this, "Midi CC threshold", 64, true}; cfg::_bool midi_cc_invert_threshold{this, "Midi CC invert threshold", false, true}; const std::string path; + + atomic_t reload_requested = false; }; extern cfg_rb3drums g_cfg_rb3drums;