From 24dde5d42b091f249221c3cc40c1475c5d7d0799 Mon Sep 17 00:00:00 2001 From: Dark Date: Sun, 9 Apr 2023 04:34:41 -0400 Subject: [PATCH] Implement support for emulating Rock Band 3's MIDI Pro Adapter Co-authored-by: Megamouse --- .gitmodules | 3 + 3rdparty/CMakeLists.txt | 4 + 3rdparty/rtmidi/CMakeLists.txt | 1 + 3rdparty/rtmidi/rtmidi | 1 + 3rdparty/rtmidi/rtmidi.vcxproj | 61 +++++ Utilities/Config.cpp | 4 +- rpcs3.sln | 9 + rpcs3/Emu/CMakeLists.txt | 7 + rpcs3/Emu/Cell/lv2/sys_usbd.cpp | 25 ++ rpcs3/Emu/Io/RB3MidiGuitar.cpp | 308 +++++++++++++++++++++++++ rpcs3/Emu/Io/RB3MidiGuitar.h | 44 ++++ rpcs3/Emu/Io/RB3MidiKeyboard.cpp | 333 +++++++++++++++++++++++++++ rpcs3/Emu/Io/RB3MidiKeyboard.h | 49 ++++ rpcs3/Emu/Io/midi_config_types.cpp | 49 ++++ rpcs3/Emu/Io/midi_config_types.h | 20 ++ rpcs3/Emu/Io/usb_device.cpp | 6 +- rpcs3/Emu/Io/usb_device.h | 2 +- rpcs3/Emu/system_config.h | 1 + rpcs3/emucore.vcxproj | 8 +- rpcs3/emucore.vcxproj.filters | 18 ++ rpcs3/rpcs3.vcxproj | 10 +- rpcs3/rpcs3.vcxproj.filters | 8 +- rpcs3/rpcs3qt/CMakeLists.txt | 4 +- rpcs3/rpcs3qt/emu_settings.cpp | 8 + rpcs3/rpcs3qt/emu_settings.h | 4 + rpcs3/rpcs3qt/emu_settings_type.h | 2 + rpcs3/rpcs3qt/microphone_creator.cpp | 7 +- rpcs3/rpcs3qt/midi_creator.cpp | 103 +++++++++ rpcs3/rpcs3qt/midi_creator.h | 23 ++ rpcs3/rpcs3qt/settings_dialog.cpp | 100 +++++++- rpcs3/rpcs3qt/settings_dialog.h | 3 + rpcs3/rpcs3qt/settings_dialog.ui | 179 +++++++++----- rpcs3/rpcs3qt/tooltips.h | 1 + 33 files changed, 1329 insertions(+), 76 deletions(-) create mode 100644 3rdparty/rtmidi/CMakeLists.txt create mode 160000 3rdparty/rtmidi/rtmidi create mode 100644 3rdparty/rtmidi/rtmidi.vcxproj create mode 100644 rpcs3/Emu/Io/RB3MidiGuitar.cpp create mode 100644 rpcs3/Emu/Io/RB3MidiGuitar.h create mode 100644 rpcs3/Emu/Io/RB3MidiKeyboard.cpp create mode 100644 rpcs3/Emu/Io/RB3MidiKeyboard.h create mode 100644 rpcs3/Emu/Io/midi_config_types.cpp create mode 100644 rpcs3/Emu/Io/midi_config_types.h create mode 100644 rpcs3/rpcs3qt/midi_creator.cpp create mode 100644 rpcs3/rpcs3qt/midi_creator.h diff --git a/.gitmodules b/.gitmodules index 4636ad8885..b9aa5ccf26 100644 --- a/.gitmodules +++ b/.gitmodules @@ -84,3 +84,6 @@ path = 3rdparty/miniupnp/miniupnp url = ../../miniupnp/miniupnp.git ignore = dirty +[submodule "3rdparty/rtmidi/rtmidi"] + path = 3rdparty/rtmidi/rtmidi + url = ../../thestk/rtmidi diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 6edcd672ae..6d9383d4a3 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -363,6 +363,9 @@ endif() # MINIUPNP add_subdirectory(miniupnp EXCLUDE_FROM_ALL) +# RTMIDI +add_subdirectory(rtmidi EXCLUDE_FROM_ALL) + # add nice ALIAS targets for ease of use if(USE_SYSTEM_LIBUSB) add_library(3rdparty::libusb ALIAS usb-1.0-shared) @@ -391,3 +394,4 @@ add_library(3rdparty::libcurl ALIAS libcurl) add_library(3rdparty::soundtouch ALIAS soundtouch) add_library(3rdparty::sdl2 ALIAS ${SDL2_TARGET}) add_library(3rdparty::miniupnpc ALIAS libminiupnpc-static) +add_library(3rdparty::rtmidi ALIAS rtmidi) diff --git a/3rdparty/rtmidi/CMakeLists.txt b/3rdparty/rtmidi/CMakeLists.txt new file mode 100644 index 0000000000..fb223ee365 --- /dev/null +++ b/3rdparty/rtmidi/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(rtmidi EXCLUDE_FROM_ALL) diff --git a/3rdparty/rtmidi/rtmidi b/3rdparty/rtmidi/rtmidi new file mode 160000 index 0000000000..84a99422a3 --- /dev/null +++ b/3rdparty/rtmidi/rtmidi @@ -0,0 +1 @@ +Subproject commit 84a99422a3faf1ab417fe71c0903a48debb9376a diff --git a/3rdparty/rtmidi/rtmidi.vcxproj b/3rdparty/rtmidi/rtmidi.vcxproj new file mode 100644 index 0000000000..fb1ffebc35 --- /dev/null +++ b/3rdparty/rtmidi/rtmidi.vcxproj @@ -0,0 +1,61 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + {2C902C67-985C-4BE0-94A3-E0FE2EB929A3} + rtmidi + + + + + + StaticLibrary + false + true + Unicode + x64 + + + + + + + + + + + + + + + + + + TurnOffAllWarnings + false + __WINDOWS_MM__;%(PreprocessorDefinitions) + ./rtmidi/;%(AdditionalIncludeDirectories) + ProgramDatabase + + + + + + diff --git a/Utilities/Config.cpp b/Utilities/Config.cpp index 545bb17e7e..d2cf920095 100644 --- a/Utilities/Config.cpp +++ b/Utilities/Config.cpp @@ -23,9 +23,9 @@ namespace cfg { for (const auto& node : owner->m_nodes) { - if (node->get_name() == name) + if (node->get_name() == m_name) { - cfg_log.fatal("Node already exists: %s", name); + cfg_log.fatal("Node already exists: %s", m_name); } } diff --git a/rpcs3.sln b/rpcs3.sln index 4679ade564..36b88e1358 100644 --- a/rpcs3.sln +++ b/rpcs3.sln @@ -6,6 +6,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "asmjit", "3rdparty\asmjit\a EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "emucore", "rpcs3\emucore.vcxproj", "{C4A10229-4712-4BD2-B63E-50D93C67A038}" ProjectSection(ProjectDependencies) = postProject + {2C902C67-985C-4BE0-94A3-E0FE2EB929A3} = {2C902C67-985C-4BE0-94A3-E0FE2EB929A3} {5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0} = {5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0} {939FE206-1182-ABC3-1234-FEAB88E98404} = {939FE206-1182-ABC3-1234-FEAB88E98404} EndProjectSection @@ -41,6 +42,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "yaml-cpp", "3rdparty\yaml-c EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rpcs3", "rpcs3\rpcs3.vcxproj", "{70CD65B0-91D6-4FAE-9A7B-4AF55D0D1B12}" ProjectSection(ProjectDependencies) = postProject + {2C902C67-985C-4BE0-94A3-E0FE2EB929A3} = {2C902C67-985C-4BE0-94A3-E0FE2EB929A3} {3384223A-6D97-4799-9862-359F85312892} = {3384223A-6D97-4799-9862-359F85312892} {349EE8F9-7D25-4909-AAF5-FF3FADE72187} = {349EE8F9-7D25-4909-AAF5-FF3FADE72187} {3EE5F075-B546-42C4-B6A8-E3CCEF38B78D} = {3EE5F075-B546-42C4-B6A8-E3CCEF38B78D} @@ -96,6 +98,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pine", "pine", "{A55DA1B5-C EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniupnpc_static", "3rdparty\miniupnp\miniupnpc_static.vcxproj", "{5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rtmidi", "3rdparty\rtmidi\rtmidi.vcxproj", "{2C902C67-985C-4BE0-94A3-E0FE2EB929A3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -194,6 +198,10 @@ Global {5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0}.Debug|x64.Build.0 = Debug|x64 {5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0}.Release|x64.ActiveCfg = Release|x64 {5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0}.Release|x64.Build.0 = Release|x64 + {2C902C67-985C-4BE0-94A3-E0FE2EB929A3}.Debug|x64.ActiveCfg = Debug|x64 + {2C902C67-985C-4BE0-94A3-E0FE2EB929A3}.Debug|x64.Build.0 = Debug|x64 + {2C902C67-985C-4BE0-94A3-E0FE2EB929A3}.Release|x64.ActiveCfg = Release|x64 + {2C902C67-985C-4BE0-94A3-E0FE2EB929A3}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -224,6 +232,7 @@ Global {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} {A55DA1B5-CC17-4525-BE7F-1659CE17BB56} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} {5228F863-E0DD-4DE7-AA7B-5C52B14CD4D0} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} + {2C902C67-985C-4BE0-94A3-E0FE2EB929A3} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {06CC7920-E085-4B81-9582-8DE8AAD42510} diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 6142ec6aad..b644fa2b4c 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -398,11 +398,18 @@ target_sources(rpcs3_emu PRIVATE Io/Infinity.cpp Io/Skylander.cpp Io/GHLtar.cpp + Io/midi_config_types.cpp + Io/RB3MidiKeyboard.cpp + Io/RB3MidiGuitar.cpp Io/Buzz.cpp Io/Turntable.cpp Io/usio.cpp ) +target_link_libraries(rpcs3_emu PRIVATE + 3rdparty::rtmidi +) + # Np target_sources(rpcs3_emu PRIVATE NP/fb_helpers.cpp diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index ff2f4b0e77..d73c58e567 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -20,7 +20,10 @@ #include "Emu/Io/GHLtar.h" #include "Emu/Io/Buzz.h" #include "Emu/Io/Turntable.h" +#include "Emu/Io/RB3MidiKeyboard.h" +#include "Emu/Io/RB3MidiGuitar.h" #include "Emu/Io/usio.h" +#include "Emu/Io/midi_config_types.h" #include @@ -315,6 +318,28 @@ usb_handler_thread::usb_handler_thread() usb_devices.push_back(std::make_shared(get_new_location())); } + const std::vector devices_list = fmt::split(g_cfg.io.midi_devices.to_string(), { "@@@" }); + for (usz index = 0; index < std::min(max_midi_devices, devices_list.size()); index++) + { + const midi_device device = midi_device::from_string(::at32(devices_list, index)); + if (device.name.empty()) continue; + + sys_usbd.notice("Adding Emulated Midi Pro Adapter (type=%s, name=%s)", device.type, device.name); + + switch (device.type) + { + case midi_device_type::guitar: + usb_devices.push_back(std::make_shared(get_new_location(), device.name, false)); + break; + case midi_device_type::guitar_22fret: + usb_devices.push_back(std::make_shared(get_new_location(), device.name, true)); + break; + case midi_device_type::keyboard: + usb_devices.push_back(std::make_shared(get_new_location(), device.name)); + break; + } + } + if (g_cfg.io.ghltar == ghltar_handler::one_controller || g_cfg.io.ghltar == ghltar_handler::two_controllers) { sys_usbd.notice("Adding emulated GHLtar (1 player)"); diff --git a/rpcs3/Emu/Io/RB3MidiGuitar.cpp b/rpcs3/Emu/Io/RB3MidiGuitar.cpp new file mode 100644 index 0000000000..fcac0e3885 --- /dev/null +++ b/rpcs3/Emu/Io/RB3MidiGuitar.cpp @@ -0,0 +1,308 @@ +// Rock Band 3 MIDI Pro Adapter Emulator (Guitar Mode) + +#include "stdafx.h" +#include "RB3MidiGuitar.h" +#include "Emu/Cell/lv2/sys_usbd.h" + +LOG_CHANNEL(rb3_midi_guitar_log); + +usb_device_rb3_midi_guitar::usb_device_rb3_midi_guitar(const std::array& location, const std::string& device_name, bool twentytwo_fret) + : usb_device_emulated(location) +{ + // For the 22-fret guitar (Fender Squier), the only thing that's different + // is the device ID reported by the MIDI Pro Adapter. + // + // Everything else is *exactly* the same as the 17-fret guitar (Fender Mustang). + if (twentytwo_fret) + { + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 64, 0x12ba, 0x2538, 0x01, 0x01, 0x02, 0x00, 0x01}); + } + else + { + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 64, 0x12ba, 0x2438, 0x01, 0x01, 0x02, 0x00, 0x01}); + } + + auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, UsbDeviceConfiguration{41, 1, 1, 0, 0x80, 32})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, UsbDeviceInterface{0, 0, 2, 3, 0, 0, 0})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_HID, UsbDeviceHID{0x0111, 0x00, 0x01, 0x22, 137})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x81, 0x03, 0x0040, 10})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x02, 0x03, 0x0040, 10})); + + usb_device_emulated::add_string("Licensed by Sony Computer Entertainment America"); + usb_device_emulated::add_string("Harmonix RB3 MIDI Guitar Interface for PlayStation®3"); + + // connect to midi device + midi_in = rtmidi_in_create_default(); + ensure(midi_in); + + rb3_midi_guitar_log.notice("Using %s API", rtmidi_api_name(rtmidi_in_get_current_api(midi_in))); + + if (!midi_in->ok) + { + rb3_midi_guitar_log.error("Could not get MIDI in ptr: %s", midi_in->msg); + return; + } + + rtmidi_in_ignore_types(midi_in, false, true, true); + + const s32 port_count = rtmidi_get_port_count(midi_in); + + if (port_count == -1) + { + rb3_midi_guitar_log.error("Could not get MIDI port count: %s", midi_in->msg); + return; + } + + for (s32 port_number = 0; port_number < port_count; port_number++) + { + char buf[128]{}; + s32 size = sizeof(buf); + if (rtmidi_get_port_name(midi_in, port_number, buf, &size) == -1) + { + rb3_midi_guitar_log.error("Error getting port name for port %d: %s", port_number, midi_in->msg); + return; + } + + rb3_midi_guitar_log.notice("Found device with name: %s", buf); + + if (device_name == buf) + { + rtmidi_open_port(midi_in, port_number, "RPCS3 MIDI Guitar Input"); + rb3_midi_guitar_log.success("Connected to device: %s", device_name); + return; + } + } + + rb3_midi_guitar_log.error("Could not find device with name: %s", device_name); +} + +usb_device_rb3_midi_guitar::~usb_device_rb3_midi_guitar() +{ + rtmidi_in_free(midi_in); +} + +static const std::array disabled_response = { + 0xe9, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0f, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x82, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x26, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00}; + +static const std::array enabled_response = { + 0xe9, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x8a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x26, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00}; + +void usb_device_rb3_midi_guitar::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) +{ + transfer->fake = true; + + // configuration packets sent by rock band 3 + // we only really need to check 1 byte here to figure out if the game + // wants to enable midi data or disable it + if (bmRequestType == 0x21 && bRequest == 0x9 && wLength == 40) + { + if (buf_size < 3) + { + rb3_midi_guitar_log.warning("buffer size < 3, bailing out early (buf_size=0x%x)", buf_size); + return; + } + + switch (buf[2]) + { + case 0x89: + rb3_midi_guitar_log.notice("MIDI data enabled."); + buttons_enabled = true; + response_pos = 0; + break; + case 0x81: + rb3_midi_guitar_log.notice("MIDI data disabled."); + buttons_enabled = false; + response_pos = 0; + break; + default: + rb3_midi_guitar_log.warning("Unhandled SET_REPORT request: 0x%02X"); + break; + } + } + // the game expects some sort of response to the configuration packet + else if (bmRequestType == 0xa1 && bRequest == 0x1) + { + transfer->expected_count = buf_size; + if (buttons_enabled) + { + const usz remaining_bytes = enabled_response.size() - response_pos; + const usz copied_bytes = std::min(remaining_bytes, buf_size); + memcpy(buf, &enabled_response[response_pos], copied_bytes); + response_pos += copied_bytes; + } + else + { + const usz remaining_bytes = disabled_response.size() - response_pos; + const usz copied_bytes = std::min(remaining_bytes, buf_size); + memcpy(buf, &disabled_response[response_pos], copied_bytes); + response_pos += copied_bytes; + } + } + else if (bmRequestType == 0x21 && bRequest == 0x9 && wLength == 8) + { + // the game uses this request to do things like set the LEDs + // we don't have any LEDs, so do nothing + } + else + { + usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer); + } +} + +void usb_device_rb3_midi_guitar::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint*/, UsbTransfer* transfer) +{ + transfer->fake = true; + transfer->expected_count = buf_size; + transfer->expected_result = HC_CC_NOERR; + // the real device takes 8ms to send a response, but there is + // no reason we can't make it faster + transfer->expected_time = get_timestamp() + 1'000; + + + // default input state + const std::array bytes = { + 0x00, 0x00, 0x08, 0x80, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; + + if (buf_size < bytes.size()) + { + rb3_midi_guitar_log.warning("buffer size < %x, bailing out early (buf_size=0x%x)", bytes.size(), buf_size); + return; + } + + memcpy(buf, bytes.data(), bytes.size()); + + while (true) + { + u8 midi_msg[32]; + usz size = sizeof(midi_msg); + + // this returns a double as some sort of delta time, with -1.0 + // being used to signal an error + if (rtmidi_in_get_message(midi_in, midi_msg, &size) == -1.0) + { + rb3_midi_guitar_log.error("Error getting MIDI message: %s", midi_in->msg); + return; + } + + if (size == 0) + { + break; + } + + parse_midi_message(midi_msg, size); + } + + write_state(buf); +} + +void usb_device_rb3_midi_guitar::parse_midi_message(u8* msg, usz size) +{ + // this is not emulated correctly but the game doesn't seem to care + button_state.count++; + + // read frets + if (size == 8 && msg[0] == 0xF0 && msg[4] == 0x01) + { + switch (msg[5]) + { + case 1: + button_state.frets[0] = msg[6] - 0x40; + break; + case 2: + button_state.frets[1] = msg[6] - 0x3B; + break; + case 3: + button_state.frets[2] = msg[6] - 0x37; + break; + case 4: + button_state.frets[3] = msg[6] - 0x32; + break; + case 5: + button_state.frets[4] = msg[6] - 0x2D; + break; + case 6: + button_state.frets[5] = msg[6] - 0x28; + break; + default: + rb3_midi_guitar_log.warning("Invalid string for fret event: %d", msg[5]); + break; + } + } + + // read strings + if (size == 8 && msg[0] == 0xF0 && msg[4] == 0x05) + { + button_state.string_velocities[msg[5] - 1] = msg[6]; + } + + // read buttons + if (size == 10 && msg[0] == 0xF0 && msg[4] == 0x08) + { + button_state.dpad = msg[7] & 0x0f; + + button_state.square = (msg[5] & 0b0000'0001) == 0b0000'0001; + button_state.cross = (msg[5] & 0b0000'0010) == 0b0000'0010; + button_state.circle = (msg[5] & 0b0000'0100) == 0b0000'0100; + button_state.triangle = (msg[5] & 0b0000'1000) == 0b0000'1000; + + button_state.select = (msg[6] & 0b0000'0001) == 0b0000'0001; + button_state.start = (msg[6] & 0b0000'0010) == 0b0000'0010; + button_state.tilt_sensor = (msg[7] & 0b0100'0000) == 0b0100'0000; + } + + // sustain pedal + if (size == 3 && msg[0] == 0xB0 && msg[1] == 0x40) + { + button_state.sustain_pedal = msg[2] >= 40; + } +} + +void usb_device_rb3_midi_guitar::write_state(u8* buf) +{ + // encode frets + buf[8] |= (button_state.frets[0] & 0b11111) << 2; + buf[8] |= (button_state.frets[1] & 0b11000) >> 3; + buf[7] |= (button_state.frets[1] & 0b00111) << 5; + buf[7] |= (button_state.frets[2] & 0b11111) >> 0; + buf[6] |= (button_state.frets[3] & 0b11111) << 2; + buf[6] |= (button_state.frets[4] & 0b11000) >> 3; + buf[5] |= (button_state.frets[4] & 0b00111) << 5; + buf[5] |= (button_state.frets[5] & 0b11111) >> 0; + + // encode strings + buf[14] = button_state.string_velocities[0]; + buf[13] = button_state.string_velocities[1]; + buf[12] = button_state.string_velocities[2]; + buf[11] = button_state.string_velocities[3]; + buf[10] = button_state.string_velocities[4]; + buf[9] = button_state.string_velocities[5]; + + // encode tilt sensor/sustain_pedal + if (button_state.tilt_sensor || button_state.sustain_pedal) + { + buf[15] = 0x7f; + buf[16] = 0x7f; + buf[17] = 0x7f; + } + + buf[1] |= 0b0000'0001 * button_state.select; + buf[1] |= 0b0000'0010 * button_state.start; + + buf[0] |= 0b0000'0010 * button_state.cross; + buf[0] |= 0b0000'0100 * button_state.circle; + buf[0] |= 0b0000'1000 * button_state.triangle; + buf[0] |= 0b0000'0001 * button_state.square; + + buf[2] = button_state.dpad; +} diff --git a/rpcs3/Emu/Io/RB3MidiGuitar.h b/rpcs3/Emu/Io/RB3MidiGuitar.h new file mode 100644 index 0000000000..3dbc712541 --- /dev/null +++ b/rpcs3/Emu/Io/RB3MidiGuitar.h @@ -0,0 +1,44 @@ +#pragma once + +#include "Emu/Io/usb_device.h" + +#include + +class usb_device_rb3_midi_guitar : public usb_device_emulated +{ +private: + u32 response_pos = 0; + bool buttons_enabled = false; + RtMidiInPtr midi_in{}; + + // button states + struct + { + u8 count = 0; + + bool cross = false; + bool circle = false; + bool square = false; + bool triangle = false; + + bool start = false; + bool select = false; + bool tilt_sensor = false; + bool sustain_pedal = false; // used for overdrive + + u8 dpad = 8; + + std::array frets{}; + std::array string_velocities{}; + } button_state; + + void parse_midi_message(u8* msg, usz size); + void write_state(u8* buf); + +public: + usb_device_rb3_midi_guitar(const std::array& location, const std::string& device_name, bool twentytwo_fret); + ~usb_device_rb3_midi_guitar(); + + 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/RB3MidiKeyboard.cpp b/rpcs3/Emu/Io/RB3MidiKeyboard.cpp new file mode 100644 index 0000000000..4d515cc090 --- /dev/null +++ b/rpcs3/Emu/Io/RB3MidiKeyboard.cpp @@ -0,0 +1,333 @@ +// Rock Band 3 MIDI Pro Adapter Emulator (Keyboard Mode) + +#include "stdafx.h" +#include "RB3MidiKeyboard.h" +#include "Emu/Cell/lv2/sys_usbd.h" + +LOG_CHANNEL(rb3_midi_keyboard_log); + +usb_device_rb3_midi_keyboard::usb_device_rb3_midi_keyboard(const std::array& location, const std::string& device_name) + : usb_device_emulated(location) +{ + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, UsbDeviceDescriptor{0x0200, 0x00, 0x00, 0x00, 64, 0x12ba, 0x2338, 0x01, 0x01, 0x02, 0x00, 0x01}); + auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, UsbDeviceConfiguration{41, 1, 1, 0, 0x80, 32})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, UsbDeviceInterface{0, 0, 2, 3, 0, 0, 0})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_HID, UsbDeviceHID{0x0111, 0x00, 0x01, 0x22, 137})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x81, 0x03, 0x0040, 10})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, UsbDeviceEndpoint{0x02, 0x03, 0x0040, 10})); + + usb_device_emulated::add_string("Licensed by Sony Computer Entertainment America"); + usb_device_emulated::add_string("Harmonix RB3 MIDI Keyboard Interface for PlayStation®3"); + + // connect to midi device + midi_in = rtmidi_in_create_default(); + ensure(midi_in); + + rb3_midi_keyboard_log.notice("Using %s API", rtmidi_api_name(rtmidi_in_get_current_api(midi_in))); + + if (!midi_in->ok) + { + rb3_midi_keyboard_log.error("Could not get MIDI in ptr: %s", midi_in->msg); + return; + } + + const s32 port_count = rtmidi_get_port_count(midi_in); + + if (port_count == -1) + { + rb3_midi_keyboard_log.error("Could not get MIDI port count: %s", midi_in->msg); + return; + } + + for (s32 port_number = 0; port_number < port_count; port_number++) + { + char buf[128]{}; + s32 size = sizeof(buf); + if (rtmidi_get_port_name(midi_in, port_number, buf, &size) == -1) + { + rb3_midi_keyboard_log.error("Error getting port name for port %d: %s", port_number, midi_in->msg); + return; + } + + rb3_midi_keyboard_log.notice("Found device with name: %s", buf); + + if (device_name == buf) + { + rtmidi_open_port(midi_in, port_number, "RPCS3 MIDI Keyboard Input"); + rb3_midi_keyboard_log.success("Connected to device: %s", device_name); + return; + } + } + + rb3_midi_keyboard_log.error("Could not find device with name: %s", device_name); +} + +usb_device_rb3_midi_keyboard::~usb_device_rb3_midi_keyboard() +{ + rtmidi_in_free(midi_in); +} + +static const std::array disabled_response = { + 0xe9, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0d, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x82, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x26, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00}; + +static const std::array enabled_response = { + 0xe9, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x8a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x26, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00}; + +void usb_device_rb3_midi_keyboard::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) +{ + transfer->fake = true; + + // configuration packets sent by rock band 3 + // we only really need to check 1 byte here to figure out if the game + // wants to enable midi data or disable it + if (bmRequestType == 0x21 && bRequest == 0x9 && wLength == 40) + { + if (buf_size < 3) + { + rb3_midi_keyboard_log.warning("buffer size < 3, bailing out early (buf_size=0x%x)", buf_size); + return; + } + + switch (buf[2]) + { + case 0x89: + rb3_midi_keyboard_log.notice("MIDI data enabled."); + buttons_enabled = true; + response_pos = 0; + break; + case 0x81: + rb3_midi_keyboard_log.notice("MIDI data disabled."); + buttons_enabled = false; + response_pos = 0; + break; + default: + rb3_midi_keyboard_log.warning("Unhandled SET_REPORT request: 0x%02X"); + break; + } + } + // the game expects some sort of response to the configuration packet + else if (bmRequestType == 0xa1 && bRequest == 0x1) + { + transfer->expected_count = buf_size; + if (buttons_enabled) + { + const usz remaining_bytes = enabled_response.size() - response_pos; + const usz copied_bytes = std::min(remaining_bytes, buf_size); + memcpy(buf, &enabled_response[response_pos], copied_bytes); + response_pos += copied_bytes; + } + else + { + const usz remaining_bytes = disabled_response.size() - response_pos; + const usz copied_bytes = std::min(remaining_bytes, buf_size); + memcpy(buf, &disabled_response[response_pos], copied_bytes); + response_pos += copied_bytes; + } + } + else if (bmRequestType == 0x21 && bRequest == 0x9 && wLength == 8) + { + // the game uses this request to do things like set the LEDs + // we don't have any LEDs, so do nothing + } + else + { + usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer); + } +} + +void usb_device_rb3_midi_keyboard::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint*/, UsbTransfer* transfer) +{ + transfer->fake = true; + transfer->expected_count = buf_size; + transfer->expected_result = HC_CC_NOERR; + // the real device takes 8ms to send a response, but there is + // no reason we can't make it faster + transfer->expected_time = get_timestamp() + 1'000; + + // default input state + static const std::array bytes = { + 0x00, 0x00, 0x08, 0x80, 0x80, 0x80, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02}; + + if (buf_size < bytes.size()) + { + rb3_midi_keyboard_log.warning("buffer size < %x, bailing out early (buf_size=0x%x)", bytes.size(), buf_size); + return; + } + + memcpy(buf, bytes.data(), bytes.size()); + + while (true) + { + u8 midi_msg[32]; + usz size = sizeof(midi_msg); + + // this returns a double as some sort of delta time, with -1.0 + // being used to signal an error + if (rtmidi_in_get_message(midi_in, midi_msg, &size) == -1.0) + { + rb3_midi_keyboard_log.error("Error getting MIDI message: %s", midi_in->msg); + return; + } + + if (size == 0) + { + break; + } + + parse_midi_message(midi_msg, size); + } + + write_state(buf); +} + +void usb_device_rb3_midi_keyboard::parse_midi_message(u8* msg, usz size) +{ + // this is not emulated correctly but the game doesn't seem to care + button_state.count++; + + // handle note on/off messages + if (size == 3 && (msg[0] == 0x80 || msg[0] == 0x90)) + { + // handle navigation buttons + switch (msg[1]) + { + case 44: // G#2 + button_state.cross = ((0x10 & msg[0]) == 0x10); + break; + case 42: // F#2 + button_state.circle = ((0x10 & msg[0]) == 0x10); + break; + case 39: // D#2 + button_state.square = ((0x10 & msg[0]) == 0x10); + break; + case 37: // C#2 + button_state.triangle = ((0x10 & msg[0]) == 0x10); + break; + case 46: // A#2 + button_state.start = ((0x10 & msg[0]) == 0x10); + break; + case 36: // C2 + button_state.select = ((0x10 & msg[0]) == 0x10); + break; + case 45: // A2 + button_state.overdrive = ((0x10 & msg[0]) == 0x10); + break; + case 41: // F2 + button_state.dpad_up = ((0x10 & msg[0]) == 0x10); + break; + case 43: // G2 + button_state.dpad_down = ((0x10 & msg[0]) == 0x10); + break; + case 38: // D2 + button_state.dpad_left = ((0x10 & msg[0]) == 0x10); + break; + case 40: // E2 + button_state.dpad_right = ((0x10 & msg[0]) == 0x10); + break; + default: + break; + } + + // handle keyboard keys + if (msg[1] >= 48 && msg[1] <= (48 + button_state.keys.size())) + { + const u32 key = msg[1] - 48; + button_state.keys[key] = ((0x10 & msg[0]) == 0x10); + button_state.velocities[key] = msg[2]; + } + } + + // control channel for overdrive + else if (size == 3 && msg[0] == 0xB0) + { + switch (msg[1]) + { + case 0x1: + case 0x40: + button_state.overdrive = msg[2] > 40; + break; + default: + break; + } + } + + // pitch wheel + else if (size == 3 && msg[0] == 0xE0) + { + const u16 msb = msg[2]; + const u16 lsb = msg[1]; + button_state.pitch_wheel = (msb << 7) | lsb; + } +} + +void usb_device_rb3_midi_keyboard::write_state(u8* buf) +{ + // buttons + buf[0] |= 0b0000'0010 * button_state.cross; + buf[0] |= 0b0000'0100 * button_state.circle; + buf[0] |= 0b0000'0001 * button_state.square; + buf[0] |= 0b0000'1000 * button_state.triangle; + buf[1] |= 0b0000'0010 * button_state.start; + buf[1] |= 0b0000'0001 * button_state.select; + + // dpad + if (button_state.dpad_up) + { + buf[2] = 0; + } + else if (button_state.dpad_down) + { + buf[2] = 4; + } + else if (button_state.dpad_left) + { + buf[2] = 6; + } + else if (button_state.dpad_right) + { + buf[2] = 2; + } + + // build key bitfield and write velocities + u32 key_mask = 0; + u8 vel_idx = 0; + + for (usz i = 0; i < button_state.keys.size(); i++) + { + key_mask <<= 1; + key_mask |= 0x1 * button_state.keys[i]; + + // the keyboard can only report 5 velocities from left to right + if (button_state.keys[i] && vel_idx < 5) + { + buf[8 + vel_idx++] = button_state.velocities[i]; + } + } + + // write keys + buf[5] = (key_mask >> 17) & 0xff; + buf[6] = (key_mask >> 9) & 0xff; + buf[7] = (key_mask >> 1) & 0xff; + buf[8] |= 0b1000'0000 * (key_mask & 0x1); + + // overdrive + buf[13] |= 0b1000'0000 * button_state.overdrive; + + // pitch wheel + const u8 wheel_pos = std::abs((button_state.pitch_wheel >> 6) - 0x80); + if (wheel_pos >= 5) + { + buf[15] = std::min(std::max(0x5, wheel_pos), 0x75); + } +} diff --git a/rpcs3/Emu/Io/RB3MidiKeyboard.h b/rpcs3/Emu/Io/RB3MidiKeyboard.h new file mode 100644 index 0000000000..13fb63f5a1 --- /dev/null +++ b/rpcs3/Emu/Io/RB3MidiKeyboard.h @@ -0,0 +1,49 @@ +#pragma once + +#include "Emu/Io/usb_device.h" + +#include + +class usb_device_rb3_midi_keyboard : public usb_device_emulated +{ +private: + u32 response_pos = 0; + bool buttons_enabled = false; + RtMidiInPtr midi_in{}; + + // button states + // TODO: emulate velocity + struct + { + u8 count = 0; + + bool cross = false; + bool circle = false; + bool square = false; + bool triangle = false; + + bool start = false; + bool select = false; + bool overdrive = false; + + bool dpad_up = false; + bool dpad_down = false; + bool dpad_left = false; + bool dpad_right = false; + + std::array keys{}; + std::array velocities{}; + + s16 pitch_wheel = 0; + } button_state; + + void parse_midi_message(u8* msg, usz size); + void write_state(u8* buf); + +public: + usb_device_rb3_midi_keyboard(const std::array& location, const std::string& device_name); + ~usb_device_rb3_midi_keyboard(); + + 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/midi_config_types.cpp b/rpcs3/Emu/Io/midi_config_types.cpp new file mode 100644 index 0000000000..ab11c87cf0 --- /dev/null +++ b/rpcs3/Emu/Io/midi_config_types.cpp @@ -0,0 +1,49 @@ +#include "stdafx.h" +#include "midi_config_types.h" +#include "Utilities/StrUtil.h" +#include "Utilities/Config.h" + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](midi_device_type value) + { + switch (value) + { + case midi_device_type::guitar: return "Guitar (17 frets)"; + case midi_device_type::guitar_22fret: return "Guitar (22 frets)"; + case midi_device_type::keyboard: return "Keyboard"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + const midi_device& obj = get_object(arg); + fmt::append(out, "%sßßß%s", obj.type, obj.name); +} + +midi_device midi_device::from_string(const std::string& str) +{ + midi_device res{}; + + if (const std::vector parts = fmt::split(str, {"ßßß"}); !parts.empty()) + { + u64 result; + + if (cfg::try_to_enum_value(&result, &fmt_class_string::format, ::at32(parts, 0))) + { + res.type = static_cast(static_cast>(result)); + } + + if (parts.size() == 2) + { + res.name = ::at32(parts, 1); + } + } + + return res; +} diff --git a/rpcs3/Emu/Io/midi_config_types.h b/rpcs3/Emu/Io/midi_config_types.h new file mode 100644 index 0000000000..49b67a93f5 --- /dev/null +++ b/rpcs3/Emu/Io/midi_config_types.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +static constexpr usz max_midi_devices = 3; + +enum class midi_device_type +{ + keyboard, + guitar, + guitar_22fret, +}; + +struct midi_device +{ + midi_device_type type{}; + std::string name; + + static midi_device from_string(const std::string& str); +}; diff --git a/rpcs3/Emu/Io/usb_device.cpp b/rpcs3/Emu/Io/usb_device.cpp index dc1a6ae563..a12139c3c1 100644 --- a/rpcs3/Emu/Io/usb_device.cpp +++ b/rpcs3/Emu/Io/usb_device.cpp @@ -247,7 +247,7 @@ u32 usb_device_emulated::get_descriptor(u8 type, u8 index, u8* buf, u32 buf_size const std::u16string u16str = utf8_to_utf16(strings[index - 1]); const u8 len = static_cast(std::min(u16str.size() * sizeof(u16) + 2, static_cast(0xFF))); buf[0] = len; - expected_count = std::min(len, ::narrow(buf_size)); + expected_count = std::min(len, ::narrow(std::min(255, buf_size))); memcpy(buf + 2, u16str.data(), expected_count - 2); } } @@ -316,7 +316,7 @@ void usb_device_emulated::isochronous_transfer(UsbTransfer* transfer) { } -void usb_device_emulated::add_string(char* str) +void usb_device_emulated::add_string(std::string str) { - strings.emplace_back(str); + strings.emplace_back(std::move(str)); } diff --git a/rpcs3/Emu/Io/usb_device.h b/rpcs3/Emu/Io/usb_device.h index 28f04638b1..67dce955f8 100644 --- a/rpcs3/Emu/Io/usb_device.h +++ b/rpcs3/Emu/Io/usb_device.h @@ -235,7 +235,7 @@ public: void isochronous_transfer(UsbTransfer* transfer) override; // Emulated specific functions - void add_string(char* str); + void add_string(std::string str); u32 get_descriptor(u8 type, u8 index, u8* buf, u32 buf_size); u32 get_status(bool self_powered, bool remote_wakeup, u8* buf, u32 buf_size); diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index aeaec1fb3a..425d25a281 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -276,6 +276,7 @@ struct cfg_root : cfg::node cfg::uint<0, 100'000> pad_sleep{this, "Pad handler sleep (microseconds)", 1'000, true}; cfg::_bool background_input_enabled{this, "Background input enabled", true, true}; cfg::_bool show_move_cursor{this, "Show move cursor", false, true}; + cfg::string midi_devices{ this, "Emulated Midi devices", "ßßß@@@ßßß@@@ßßß@@@" }; } io{ this }; struct node_sys : cfg::node diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 9f5eaa168b..9bea91eca1 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -40,7 +40,7 @@ Use - ..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\zlib\zlib;..\3rdparty\llvm\llvm\include;..\3rdparty\llvm\llvm\llvm\include;..\3rdparty\llvm\llvm_build\include;$(VULKAN_SDK)\Include + ..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;..\3rdparty\llvm\llvm\include;..\3rdparty\llvm\llvm\llvm\include;..\3rdparty\llvm\llvm_build\include;$(VULKAN_SDK)\Include MaxSpeed MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions) MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions) @@ -69,6 +69,9 @@ + + + @@ -503,9 +506,12 @@ + + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index ca7673c6fd..250a857259 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1153,6 +1153,15 @@ Emu + + Emu\Io + + + Emu\Io + + + Emu\Io + @@ -2326,6 +2335,15 @@ Emu + + Emu\Io + + + Emu\Io + + + Emu\Io + diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index b86eb250a9..8d05ca6e35 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -71,7 +71,7 @@ - ..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\libsdl-org\SDL\include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\release;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) + ..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\libsdl-org\SDL\include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\release;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) -Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) release\ false @@ -89,7 +89,7 @@ TurnOffAllWarnings - DbgHelp.lib;Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;miniupnpc_static.lib;imm32.lib;ksuser.lib;version.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimedia.lib;Qt5MultimediaWidgets.lib;Qt5Svg.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;SDL.lib;%(AdditionalDependencies) + DbgHelp.lib;Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;miniupnpc_static.lib;rtmidi.lib;imm32.lib;ksuser.lib;version.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimedia.lib;Qt5MultimediaWidgets.lib;Qt5Svg.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;SDL.lib;%(AdditionalDependencies) ..\3rdparty\OpenAL\libs\Win64;..\3rdparty\glslang\build\hlsl\Release;..\3rdparty\glslang\build\SPIRV\Release;..\3rdparty\glslang\build\OGLCompilersDLL\Release;..\3rdparty\glslang\build\glslang\OSDependent\Windows\Release;..\3rdparty\glslang\build\glslang\Release;..\3rdparty\SPIRV\build\source\Release;..\3rdparty\SPIRV\build\source\opt\Release;..\lib\$(CONFIGURATION)-$(PLATFORM);..\3rdparty\XAudio2Redist\libs;..\3rdparty\discord-rpc\lib;$(QTDIR)\lib;%(AdditionalLibraryDirectories);$(VULKAN_SDK)\Lib "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions) true @@ -123,7 +123,7 @@ - ..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\debug;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) + ..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\debug;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) -Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) debug\ false @@ -140,7 +140,7 @@ $(IntDir)vc$(PlatformToolsetVersion).pdb - DbgHelp.lib;Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;miniupnpc_static.lib;ksuser.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmaind.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgetsd.lib;$(QTDIR)\lib\Qt5Guid.lib;$(QTDIR)\lib\Qt5Cored.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5WinExtrasd.lib;Qt5Concurrentd.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimediad.lib;Qt5MultimediaWidgetsd.lib;Qt5Svgd.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;%(AdditionalDependencies) + DbgHelp.lib;Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;miniupnpc_static.lib;rtmidi.lib;ksuser.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslangd.lib;OSDependentd.lib;OGLCompilerd.lib;SPIRVd.lib;MachineIndependentd.lib;GenericCodeGend.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmaind.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgetsd.lib;$(QTDIR)\lib\Qt5Guid.lib;$(QTDIR)\lib\Qt5Cored.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5WinExtrasd.lib;Qt5Concurrentd.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimediad.lib;Qt5MultimediaWidgetsd.lib;Qt5Svgd.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;%(AdditionalDependencies) ..\3rdparty\OpenAL\libs\Win64;..\3rdparty\glslang\build\hlsl\Debug;..\3rdparty\glslang\build\SPIRV\Debug;..\3rdparty\glslang\build\OGLCompilersDLL\Debug;..\3rdparty\glslang\build\glslang\OSDependent\Windows\Debug;..\3rdparty\glslang\build\glslang\Debug;..\3rdparty\SPIRV\build\source\opt\Debug;..\3rdparty\XAudio2Redist\libs;..\3rdparty\discord-rpc\lib;..\lib\$(CONFIGURATION)-$(PLATFORM);$(QTDIR)\lib;%(AdditionalLibraryDirectories);$(VULKAN_SDK)\Lib "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /VERBOSE %(AdditionalOptions) true @@ -723,6 +723,7 @@ + @@ -1312,6 +1313,7 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 0cd2af3c27..11aebfff59 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -1023,6 +1023,9 @@ Generated Files\Release + + Gui\settings + @@ -1199,6 +1202,9 @@ Gui\custom items + + Gui\settings + @@ -1534,4 +1540,4 @@ StyleSheets - \ No newline at end of file + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 9a36b38063..c25f35d642 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -51,6 +51,7 @@ set(SRC_FILES memory_string_searcher.cpp memory_viewer_panel.cpp microphone_creator.cpp + midi_creator.cpp movie_item.cpp movie_item_base.cpp msg_dialog_frame.cpp @@ -156,4 +157,5 @@ target_link_libraries(rpcs3_ui 3rdparty::libpng 3rdparty::7z 3rdparty::wolfssl - 3rdparty::libcurl) + 3rdparty::libcurl + 3rdparty::rtmidi) diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 0c30a0dbdd..a7f69cfef4 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -1277,6 +1277,14 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case stereo_render_mode_options::over_under: return tr("Over-under", "3D Display Mode"); } break; + case emu_settings_type::MidiDevices: + switch (static_cast(index)) + { + case midi_device_type::guitar: return tr("Guitar (17 frets)", "Midi Device Type"); + case midi_device_type::guitar_22fret: return tr("Guitar (22 frets)", "Midi Device Type"); + case midi_device_type::keyboard: return tr("Keyboard", "Midi Device Type"); + } + break; default: break; } diff --git a/rpcs3/rpcs3qt/emu_settings.h b/rpcs3/rpcs3qt/emu_settings.h index 4f75645d6c..61b6422280 100644 --- a/rpcs3/rpcs3qt/emu_settings.h +++ b/rpcs3/rpcs3qt/emu_settings.h @@ -4,6 +4,7 @@ #include "util/yaml.hpp" #include "microphone_creator.h" +#include "midi_creator.h" #include "render_creator.h" #include "emu_settings_type.h" @@ -80,6 +81,9 @@ public: /** Gets a list of all the microphones available.*/ microphone_creator m_microphone_creator; + /** Gets a list of all the midi devices available.*/ + midi_creator m_midi_creator; + /** Loads the settings from path.*/ void LoadSettings(const std::string& title_id = ""); diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index da7bd4fa23..a1bdad2754 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -155,6 +155,7 @@ enum class emu_settings_type Buzz, Turntable, GHLtar, + MidiDevices, // Misc ExitRPCS3OnFinish, @@ -335,6 +336,7 @@ inline static const QMap settings_location = { emu_settings_type::Buzz, { "Input/Output", "Buzz emulated controller" }}, { emu_settings_type::Turntable, { "Input/Output", "Turntable emulated controller" }}, { emu_settings_type::GHLtar, { "Input/Output", "GHLtar emulated controller" }}, + { emu_settings_type::MidiDevices, { "Input/Output", "Emulated Midi devices" }}, // Misc { emu_settings_type::ExitRPCS3OnFinish, { "Miscellaneous", "Exit RPCS3 when process finishes" }}, diff --git a/rpcs3/rpcs3qt/microphone_creator.cpp b/rpcs3/rpcs3qt/microphone_creator.cpp index 809c96be49..762e6036f1 100644 --- a/rpcs3/rpcs3qt/microphone_creator.cpp +++ b/rpcs3/rpcs3qt/microphone_creator.cpp @@ -69,13 +69,10 @@ std::string microphone_creator::set_device(u32 num, const QString& text) void microphone_creator::parse_devices(const std::string& list) { - for (u32 index = 0; index < 4; index++) - { - m_sel_list[index] = ""; - } + m_sel_list = {}; const auto devices_list = fmt::split(list, { "@@@" }); - for (u32 index = 0; index < std::min(4, ::size32(devices_list)); index++) + for (u32 index = 0; index < std::min(m_sel_list.size(), ::size32(devices_list)); index++) { m_sel_list[index] = devices_list[index]; } diff --git a/rpcs3/rpcs3qt/midi_creator.cpp b/rpcs3/rpcs3qt/midi_creator.cpp new file mode 100644 index 0000000000..ba4938ed42 --- /dev/null +++ b/rpcs3/rpcs3qt/midi_creator.cpp @@ -0,0 +1,103 @@ +#include "stdafx.h" +#include "midi_creator.h" + +#include "Utilities/StrFmt.h" +#include "Utilities/StrUtil.h" + +#include + +LOG_CHANNEL(cfg_log, "CFG"); + +midi_creator::midi_creator() +{ + refresh_list(); +} + +// We need to recreate the localized string because the midi creator is currently only created once. +QString midi_creator::get_none() +{ + return tr("None", "Midi device"); +} + +void midi_creator::refresh_list() +{ + m_midi_list.clear(); + m_midi_list.append(get_none()); + + RtMidiInPtr midi_in = rtmidi_in_create_default(); + ensure(midi_in); + + cfg_log.notice("MIDI: Using %s api", rtmidi_api_name(rtmidi_in_get_current_api(midi_in))); + + if (!midi_in->ok) + { + cfg_log.error("Could not get MIDI in ptr: %s", midi_in->msg); + return; + } + + const s32 port_count = rtmidi_get_port_count(midi_in); + + if (port_count == -1) + { + cfg_log.error("Could not get MIDI port count: %s", midi_in->msg); + return; + } + + for (s32 port_number = 0; port_number < port_count; port_number++) + { + char buf[128]{}; + s32 size = sizeof(buf); + if (rtmidi_get_port_name(midi_in, port_number, buf, &size) == -1) + { + cfg_log.error("Error getting midi port name for port %d: %s", port_number, midi_in->msg); + continue; + } + + cfg_log.notice("Found midi device with name: %s", buf); + m_midi_list.append(QString::fromUtf8(buf)); + } + + rtmidi_in_free(midi_in); +} + +QStringList midi_creator::get_midi_list() const +{ + return m_midi_list; +} + +std::array midi_creator::get_selection_list() const +{ + return m_sel_list; +} + +std::string midi_creator::set_device(u32 num, const midi_device& device) +{ + ensure(num < m_sel_list.size()); + + m_sel_list[num] = device; + + if (device.name == get_none().toStdString()) + { + m_sel_list[num].name.clear(); + } + + std::string result; + + for (const midi_device& device : m_sel_list) + { + fmt::append(result, "%s@@@", device); + } + + return result; +} + +void midi_creator::parse_devices(const std::string& list) +{ + m_sel_list = {}; + + const std::vector devices_list = fmt::split(list, { "@@@" }); + for (usz index = 0; index < std::min(m_sel_list.size(), devices_list.size()); index++) + { + m_sel_list[index] = midi_device::from_string(devices_list[index]); + } +} diff --git a/rpcs3/rpcs3qt/midi_creator.h b/rpcs3/rpcs3qt/midi_creator.h new file mode 100644 index 0000000000..bb9d4fcaff --- /dev/null +++ b/rpcs3/rpcs3qt/midi_creator.h @@ -0,0 +1,23 @@ +#pragma once + +#include "util/types.hpp" +#include "Emu/Io/midi_config_types.h" + +#include +#include + +class midi_creator : public QObject +{ +public: + midi_creator(); + QString get_none(); + std::string set_device(u32 num, const midi_device& device); + void parse_devices(const std::string& list); + void refresh_list(); + QStringList get_midi_list() const; + std::array get_selection_list() const; + +private: + QStringList m_midi_list; + std::array m_sel_list; +}; diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 55899d4662..a6904b79ef 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -980,7 +980,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std const auto change_microphone_device = [mic_none, propagate_used_devices, this](u32 index, const QString& text) { m_emu_settings->SetSetting(emu_settings_type::MicrophoneDevices, m_emu_settings->m_microphone_creator.set_device(index, text)); - if (const u32 next_index = index + 1; next_index < 4 && text == mic_none) + if (const u32 next_index = index + 1; next_index < m_mics_combo.size() && text == mic_none) m_mics_combo[next_index]->setCurrentText(mic_none); propagate_used_devices(); }; @@ -1133,7 +1133,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std const std::array mic_sel_list = m_emu_settings->m_microphone_creator.get_selection_list(); - for (s32 index = 3; index >= 0; index--) + for (s32 index = static_cast(mic_sel_list.size()) - 1; index >= 0; index--) { const QString qmic = qstr(mic_sel_list[index]); @@ -1257,6 +1257,102 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceCheckBox(ui->showMoveCursorBox, emu_settings_type::ShowMoveCursor); SubscribeTooltip(ui->showMoveCursorBox, tooltips.settings.show_move_cursor); + // Midi + const QString midi_none = m_emu_settings->m_midi_creator.get_none(); + const midi_device def_midi_device{ .type = midi_device_type::keyboard, .name = midi_none.toStdString() }; + const std::vector midi_device_types = cfg::try_to_enum_list(&fmt_class_string::format); + + m_midi_type_combo[0] = ui->midiTypeBox1; + m_midi_type_combo[1] = ui->midiTypeBox2; + m_midi_type_combo[2] = ui->midiTypeBox3; + m_midi_device_combo[0] = ui->midiDeviceBox1; + m_midi_device_combo[1] = ui->midiDeviceBox2; + m_midi_device_combo[2] = ui->midiDeviceBox3; + + SubscribeTooltip(ui->gb_midi_1, tooltips.settings.midi_devices); + SubscribeTooltip(ui->gb_midi_2, tooltips.settings.midi_devices); + SubscribeTooltip(ui->gb_midi_3, tooltips.settings.midi_devices); + + const auto propagate_midi_devices = [midi_none, this]() + { + for (u32 index = 0; index < m_midi_device_combo.size(); index++) + { + const QString cur_item = m_midi_device_combo[index]->currentText(); + QStringList cur_list = m_emu_settings->m_midi_creator.get_midi_list(); + for (u32 subindex = 0; subindex < m_midi_device_combo.size(); subindex++) + { + if (subindex != index && m_midi_device_combo[subindex]->currentText() != midi_none) + cur_list.removeOne(m_midi_device_combo[subindex]->currentText()); + } + m_midi_device_combo[index]->blockSignals(true); + m_midi_device_combo[index]->clear(); + m_midi_device_combo[index]->addItems(cur_list); + m_midi_device_combo[index]->setCurrentText(cur_item); + m_midi_device_combo[index]->blockSignals(false); + } + }; + + const auto change_midi_device = [propagate_midi_devices, this](u32 index, const midi_device& device) + { + m_emu_settings->SetSetting(emu_settings_type::MidiDevices, m_emu_settings->m_midi_creator.set_device(index, device)); + propagate_midi_devices(); + }; + + for (u32 i = 0; i < m_midi_type_combo.size(); i++) + { + m_midi_type_combo[i]->blockSignals(true); + for (const std::string& type_str : midi_device_types) + { + midi_device_type type{}; + if (u64 result; cfg::try_to_enum_value(&result, &fmt_class_string::format, type_str)) + { + type = static_cast(static_cast>(result)); + } + const QString type_name = m_emu_settings->GetLocalizedSetting(QString::fromStdString(fmt::format("%s", type)), emu_settings_type::MidiDevices, static_cast(type), false); + m_midi_type_combo[i]->addItem(type_name, static_cast(type)); + } + m_midi_type_combo[i]->blockSignals(false); + + connect(m_midi_type_combo[i], &QComboBox::currentTextChanged, this, [this, change_midi_device, i](const QString& /*text*/) + { + const midi_device device{ .type = static_cast(m_midi_type_combo[i]->currentData().toInt()), .name = m_midi_device_combo[i]->currentText().toStdString() }; + change_midi_device(i, device); + }); + connect(m_midi_device_combo[i], &QComboBox::currentTextChanged, this, [this, change_midi_device, i](const QString& text) + { + const midi_device device{ .type = static_cast(m_midi_type_combo[i]->currentData().toInt()), .name = text.toStdString() }; + change_midi_device(i, device); + }); + connect(this, &settings_dialog::signal_restore_dependant_defaults, this, [change_midi_device, i, def_midi_device]() { change_midi_device(i, def_midi_device); }); + } + + m_emu_settings->m_midi_creator.refresh_list(); + propagate_midi_devices(); // Fills comboboxes list + + m_emu_settings->m_midi_creator.parse_devices(m_emu_settings->GetSetting(emu_settings_type::MidiDevices)); + + const std::array midi_sel_list = m_emu_settings->m_midi_creator.get_selection_list(); + + for (s32 index = static_cast(midi_sel_list.size()) - 1; index >= 0; index--) + { + const midi_device& device = midi_sel_list[index]; + const QString qmidi = QString::fromStdString(device.name); + + m_midi_type_combo[index]->setCurrentIndex(m_midi_type_combo[index]->findData(static_cast(device.type))); + + if (qmidi.isEmpty() || m_midi_device_combo[index]->findText(qmidi) == -1) + { + m_midi_device_combo[index]->setCurrentText(midi_none); + change_midi_device(index, def_midi_device); // Ensures the value is set in config + } + else + { + m_midi_device_combo[index]->setCurrentText(qmidi); + } + } + + propagate_midi_devices(); // Enables/Disables comboboxes and checks values from config for sanity + // _____ _ _______ _ // / ____| | | |__ __| | | // | (___ _ _ ___| |_ ___ _ __ ___ | | __ _| |__ diff --git a/rpcs3/rpcs3qt/settings_dialog.h b/rpcs3/rpcs3qt/settings_dialog.h index 2f35273d9f..5a0d075063 100644 --- a/rpcs3/rpcs3qt/settings_dialog.h +++ b/rpcs3/rpcs3qt/settings_dialog.h @@ -45,6 +45,9 @@ private: QString m_old_renderer; // Audio tab std::array m_mics_combo; + // IO tab + std::array m_midi_type_combo; + std::array m_midi_device_combo; int m_tab_index; std::unique_ptr ui; diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index da3a4dbcab..59582bd5b1 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -43,8 +43,8 @@ 0 0 - 838 - 641 + 821 + 716 @@ -940,12 +940,6 @@ - - - 0 - 0 - - Additional Settings @@ -1613,19 +1607,7 @@ - - - - - Buzz! emulated controller - - - - - - - - + @@ -1638,14 +1620,14 @@ - - + + - Camera Flip + Move Handler - + - + @@ -1662,6 +1644,30 @@ + + + + Mouse Handler + + + + + + + + + + + + Buzz! emulated controller + + + + + + + + @@ -1686,14 +1692,26 @@ - - + + - Mouse Handler + Camera Input - + - + + + + + + + + + Camera Flip + + + + @@ -1710,30 +1728,6 @@ - - - - Camera Input - - - - - - - - - - - - Move Handler - - - - - - - - @@ -1746,7 +1740,64 @@ - + + + + Emulated Midi device 1 + + + + + + + + + + + + + + + + + + + Emulated Midi device 3 + + + + + + + + + + + + + + + + + + + Emulated Midi device 2 + + + + + + + + + + + + + + + + Additional Settings @@ -1766,6 +1817,22 @@ + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 0 + 0 + + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index c1645f5a9e..4a1065e930 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -223,6 +223,7 @@ public: const QString ghltar = tr("Guitar Hero Live (GHL) Guitar controller support.\nSelect 1 or 2 controllers if the game requires GHL Guitar controllers and you don't have real guitar controllers.\nSelect Null if the game has support for DualShock or if you have real guitar controllers.\nA real guitar controller can be used at the same time as an emulated guitar controller."); const QString background_input = tr("Allows pad and keyboard input while the game window is unfocused."); const QString show_move_cursor = tr("Shows the raw position of the PS Move input.\nThis can be very helpful during calibration screens."); + const QString midi_devices = tr("Select up to 3 emulated midi devices and their type."); // network