1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-25 04:02:42 +01:00

Implement support for emulating Rock Band 3's MIDI Pro Adapter

Co-authored-by: Megamouse <studienricky89@googlemail.com>
This commit is contained in:
Dark 2023-04-09 04:34:41 -04:00 committed by Megamouse
parent 8e15afb2c4
commit 24dde5d42b
33 changed files with 1329 additions and 76 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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)

1
3rdparty/rtmidi/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1 @@
add_subdirectory(rtmidi EXCLUDE_FROM_ALL)

1
3rdparty/rtmidi/rtmidi vendored Submodule

@ -0,0 +1 @@
Subproject commit 84a99422a3faf1ab417fe71c0903a48debb9376a

61
3rdparty/rtmidi/rtmidi.vcxproj vendored Normal file
View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClInclude Include="rtmidi\RtMidi.h" />
<ClInclude Include="rtmidi\rtmidi_c.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="rtmidi\RtMidi.cpp" />
<ClCompile Include="rtmidi\rtmidi_c.cpp" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{2C902C67-985C-4BE0-94A3-E0FE2EB929A3}</ProjectGuid>
<RootNamespace>rtmidi</RootNamespace>
</PropertyGroup>
<Import Project="$(SolutionDir)\buildfiles\msvc\common_default.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(SolutionDir)\buildfiles\msvc\common_default_macros.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_default.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_debug.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(SolutionDir)\buildfiles\msvc\rpcs3_release.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<SDLCheck>false</SDLCheck>
<PreprocessorDefinitions>__WINDOWS_MM__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>./rtmidi/;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<DebugInformationFormat Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">ProgramDatabase</DebugInformationFormat>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -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);
}
}

View File

@ -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}

View File

@ -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

View File

@ -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 <libusb.h>
@ -315,6 +318,28 @@ usb_handler_thread::usb_handler_thread()
usb_devices.push_back(std::make_shared<usb_device_usio>(get_new_location()));
}
const std::vector<std::string> 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<usb_device_rb3_midi_guitar>(get_new_location(), device.name, false));
break;
case midi_device_type::guitar_22fret:
usb_devices.push_back(std::make_shared<usb_device_rb3_midi_guitar>(get_new_location(), device.name, true));
break;
case midi_device_type::keyboard:
usb_devices.push_back(std::make_shared<usb_device_rb3_midi_keyboard>(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)");

View File

@ -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<u8, 7>& 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<u8, 40> 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<u8, 40> 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<usz>(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<usz>(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<u8, 27> 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;
}

View File

@ -0,0 +1,44 @@
#pragma once
#include "Emu/Io/usb_device.h"
#include <rtmidi_c.h>
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<u8, 6> frets{};
std::array<u8, 6> 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<u8, 7>& 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;
};

View File

@ -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<u8, 7>& 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<u8, 40> 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<u8, 40> 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<usz>(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<usz>(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<u8, 27> 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<u8>(std::max<u8>(0x5, wheel_pos), 0x75);
}
}

View File

@ -0,0 +1,49 @@
#pragma once
#include "Emu/Io/usb_device.h"
#include <rtmidi_c.h>
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<bool, 25> keys{};
std::array<u8, 25> 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<u8, 7>& 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;
};

View File

@ -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<midi_device_type>::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<midi_device>::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<std::string> parts = fmt::split(str, {"ßßß"}); !parts.empty())
{
u64 result;
if (cfg::try_to_enum_value(&result, &fmt_class_string<midi_device_type>::format, ::at32(parts, 0)))
{
res.type = static_cast<midi_device_type>(static_cast<std::underlying_type_t<midi_device_type>>(result));
}
if (parts.size() == 2)
{
res.name = ::at32(parts, 1);
}
}
return res;
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <string>
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);
};

View File

@ -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<u8>(std::min(u16str.size() * sizeof(u16) + 2, static_cast<usz>(0xFF)));
buf[0] = len;
expected_count = std::min(len, ::narrow<u8>(buf_size));
expected_count = std::min(len, ::narrow<u8>(std::min<u32>(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));
}

View File

@ -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);

View File

@ -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

View File

@ -40,7 +40,7 @@
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<AdditionalIncludeDirectories>..\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</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\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</AdditionalIncludeDirectories>
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization>
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -69,6 +69,9 @@
<ClCompile Include="Emu\Cell\Modules\HLE_PATCHES.cpp" />
<ClCompile Include="Emu\games_config.cpp" />
<ClCompile Include="Emu\Io\camera_config.cpp" />
<ClCompile Include="Emu\Io\midi_config_types.cpp" />
<ClCompile Include="Emu\Io\RB3MidiGuitar.cpp" />
<ClCompile Include="Emu\Io\RB3MidiKeyboard.cpp" />
<ClCompile Include="Emu\Io\recording_config.cpp" />
<ClCompile Include="Emu\Io\Turntable.cpp" />
<ClCompile Include="Emu\Io\GHLtar.cpp" />
@ -503,9 +506,12 @@
<ClInclude Include="Emu\games_config.h" />
<ClInclude Include="Emu\Io\camera_config.h" />
<ClInclude Include="Emu\Io\camera_handler_base.h" />
<ClInclude Include="Emu\Io\midi_config_types.h" />
<ClInclude Include="Emu\Io\music_handler_base.h" />
<ClInclude Include="Emu\Io\Null\null_camera_handler.h" />
<ClInclude Include="Emu\Io\Null\null_music_handler.h" />
<ClInclude Include="Emu\Io\RB3MidiGuitar.h" />
<ClInclude Include="Emu\Io\RB3MidiKeyboard.h" />
<ClInclude Include="Emu\Io\recording_config.h" />
<ClInclude Include="Emu\Io\Turntable.h" />
<ClInclude Include="Emu\Io\GHLtar.h" />

View File

@ -1153,6 +1153,15 @@
<ClCompile Include="Emu\games_config.cpp">
<Filter>Emu</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\RB3MidiGuitar.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\RB3MidiKeyboard.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\midi_config_types.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Crypto\aes.h">
@ -2326,6 +2335,15 @@
<ClInclude Include="Emu\config_mode.h">
<Filter>Emu</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\RB3MidiGuitar.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\RB3MidiKeyboard.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\midi_config_types.h">
<Filter>Emu\Io</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">

View File

@ -71,7 +71,7 @@
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<AdditionalOptions>-Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
<AssemblerListingLocation>release\</AssemblerListingLocation>
<BrowseInformation>false</BrowseInformation>
@ -89,7 +89,7 @@
<ExternalWarningLevel>TurnOffAllWarnings</ExternalWarningLevel>
</ClCompile>
<Link>
<AdditionalDependencies>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)</AdditionalDependencies>
<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)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\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</AdditionalLibraryDirectories>
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
<DataExecutionPrevention>true</DataExecutionPrevention>
@ -123,7 +123,7 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>..\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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<AdditionalOptions>-Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
<AssemblerListingLocation>debug\</AssemblerListingLocation>
<BrowseInformation>false</BrowseInformation>
@ -140,7 +140,7 @@
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<AdditionalDependencies>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)</AdditionalDependencies>
<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)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\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</AdditionalLibraryDirectories>
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /VERBOSE %(AdditionalOptions)</AdditionalOptions>
<DataExecutionPrevention>true</DataExecutionPrevention>
@ -723,6 +723,7 @@
<ClCompile Include="rpcs3qt\localized.cpp" />
<ClCompile Include="rpcs3qt\log_viewer.cpp" />
<ClCompile Include="rpcs3qt\microphone_creator.cpp" />
<ClCompile Include="rpcs3qt\midi_creator.cpp" />
<ClCompile Include="rpcs3qt\movie_item.cpp" />
<ClCompile Include="rpcs3qt\movie_item_base.cpp" />
<ClCompile Include="rpcs3qt\osk_dialog_frame.cpp" />
@ -1312,6 +1313,7 @@
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(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"</Command>
</CustomBuild>
<ClInclude Include="rpcs3qt\midi_creator.h" />
<ClInclude Include="rpcs3qt\movie_item.h" />
<ClInclude Include="rpcs3qt\movie_item_base.h" />
<ClInclude Include="rpcs3qt\numbered_widget_item.h" />

View File

@ -1023,6 +1023,9 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_elf_memory_dumping_dialog.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\midi_creator.cpp">
<Filter>Gui\settings</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Input\ds4_pad_handler.h">
@ -1199,6 +1202,9 @@
<ClInclude Include="rpcs3qt\movie_item_base.h">
<Filter>Gui\custom items</Filter>
</ClInclude>
<ClInclude Include="rpcs3qt\midi_creator.h">
<Filter>Gui\settings</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="debug\moc_predefs.h.cbt">

View File

@ -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)

View File

@ -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<midi_device_type>(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;
}

View File

@ -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 = "");

View File

@ -155,6 +155,7 @@ enum class emu_settings_type
Buzz,
Turntable,
GHLtar,
MidiDevices,
// Misc
ExitRPCS3OnFinish,
@ -335,6 +336,7 @@ inline static const QMap<emu_settings_type, cfg_location> 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" }},

View File

@ -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<u32>(4, ::size32(devices_list)); index++)
for (u32 index = 0; index < std::min<u32>(m_sel_list.size(), ::size32(devices_list)); index++)
{
m_sel_list[index] = devices_list[index];
}

View File

@ -0,0 +1,103 @@
#include "stdafx.h"
#include "midi_creator.h"
#include "Utilities/StrFmt.h"
#include "Utilities/StrUtil.h"
#include <rtmidi_c.h>
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_device, max_midi_devices> 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<std::string> 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]);
}
}

View File

@ -0,0 +1,23 @@
#pragma once
#include "util/types.hpp"
#include "Emu/Io/midi_config_types.h"
#include <QObject>
#include <QStringList>
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<midi_device, max_midi_devices> get_selection_list() const;
private:
QStringList m_midi_list;
std::array<midi_device, max_midi_devices> m_sel_list;
};

View File

@ -980,7 +980,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> 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> gui_settings, std
const std::array<std::string, 4> mic_sel_list = m_emu_settings->m_microphone_creator.get_selection_list();
for (s32 index = 3; index >= 0; index--)
for (s32 index = static_cast<int>(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> 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<std::string> midi_device_types = cfg::try_to_enum_list(&fmt_class_string<midi_device_type>::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<midi_device_type>::format, type_str))
{
type = static_cast<midi_device_type>(static_cast<std::underlying_type_t<midi_device_type>>(result));
}
const QString type_name = m_emu_settings->GetLocalizedSetting(QString::fromStdString(fmt::format("%s", type)), emu_settings_type::MidiDevices, static_cast<int>(type), false);
m_midi_type_combo[i]->addItem(type_name, static_cast<int>(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<midi_device_type>(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<midi_device_type>(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_device, max_midi_devices> midi_sel_list = m_emu_settings->m_midi_creator.get_selection_list();
for (s32 index = static_cast<int>(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<int>(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
// _____ _ _______ _
// / ____| | | |__ __| | |
// | (___ _ _ ___| |_ ___ _ __ ___ | | __ _| |__

View File

@ -45,6 +45,9 @@ private:
QString m_old_renderer;
// Audio tab
std::array<QComboBox*, 4> m_mics_combo;
// IO tab
std::array<QComboBox*, 3> m_midi_type_combo;
std::array<QComboBox*, 3> m_midi_device_combo;
int m_tab_index;
std::unique_ptr<Ui::settings_dialog> ui;

View File

@ -43,8 +43,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>838</width>
<height>641</height>
<width>821</width>
<height>716</height>
</rect>
</property>
<property name="sizePolicy">
@ -940,12 +940,6 @@
</item>
<item>
<widget class="QGroupBox" name="gb_additional_settings">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Additional Settings</string>
</property>
@ -1613,19 +1607,7 @@
</attribute>
<layout class="QVBoxLayout" name="inputTab_layout">
<item>
<layout class="QGridLayout" name="ioGridLayout">
<item row="0" column="2">
<widget class="QGroupBox" name="gb_buzz_emulated">
<property name="title">
<string>Buzz! emulated controller</string>
</property>
<layout class="QVBoxLayout" name="gb_buzz_emulated_layout">
<item>
<widget class="QComboBox" name="buzzBox"/>
</item>
</layout>
</widget>
</item>
<layout class="QGridLayout" name="ioGridLayout" columnstretch="1,1,1">
<item row="2" column="2">
<widget class="QGroupBox" name="gb_ghltar_emulated">
<property name="title">
@ -1638,14 +1620,14 @@
</layout>
</widget>
</item>
<item row="3" column="1">
<widget class="QGroupBox" name="gb_camera_flip">
<item row="2" column="0">
<widget class="QGroupBox" name="gb_move_handler">
<property name="title">
<string>Camera Flip</string>
<string>Move Handler</string>
</property>
<layout class="QVBoxLayout" name="gb_camera_flip_layout">
<layout class="QVBoxLayout" name="gb_move_handler_layout">
<item>
<widget class="QComboBox" name="cameraFlipBox"/>
<widget class="QComboBox" name="moveBox"/>
</item>
</layout>
</widget>
@ -1662,6 +1644,30 @@
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="gb_mouse_handler">
<property name="title">
<string>Mouse Handler</string>
</property>
<layout class="QVBoxLayout" name="gb_mouse_handler_layout">
<item>
<widget class="QComboBox" name="mouseHandlerBox"/>
</item>
</layout>
</widget>
</item>
<item row="0" column="2">
<widget class="QGroupBox" name="gb_buzz_emulated">
<property name="title">
<string>Buzz! emulated controller</string>
</property>
<layout class="QVBoxLayout" name="gb_buzz_emulated_layout">
<item>
<widget class="QComboBox" name="buzzBox"/>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="gb_keyboard_handler">
<property name="title">
@ -1686,14 +1692,26 @@
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="gb_mouse_handler">
<item row="0" column="1">
<widget class="QGroupBox" name="gb_camera_type">
<property name="title">
<string>Mouse Handler</string>
<string>Camera Input</string>
</property>
<layout class="QVBoxLayout" name="gb_mouse_handler_layout">
<layout class="QVBoxLayout" name="gb_camera_type_layout">
<item>
<widget class="QComboBox" name="mouseHandlerBox"/>
<widget class="QComboBox" name="cameraTypeBox"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="1">
<widget class="QGroupBox" name="gb_camera_flip">
<property name="title">
<string>Camera Flip</string>
</property>
<layout class="QVBoxLayout" name="gb_camera_flip_layout">
<item>
<widget class="QComboBox" name="cameraFlipBox"/>
</item>
</layout>
</widget>
@ -1710,30 +1728,6 @@
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="gb_camera_type">
<property name="title">
<string>Camera Input</string>
</property>
<layout class="QVBoxLayout" name="gb_camera_type_layout">
<item>
<widget class="QComboBox" name="cameraTypeBox"/>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="gb_move_handler">
<property name="title">
<string>Move Handler</string>
</property>
<layout class="QVBoxLayout" name="gb_move_handler_layout">
<item>
<widget class="QComboBox" name="moveBox"/>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="gb_camera_setting">
<property name="title">
@ -1746,7 +1740,64 @@
</layout>
</widget>
</item>
<item row="4" column="0">
<item row="3" column="2">
<widget class="QGroupBox" name="gb_midi_1">
<property name="title">
<string>Emulated Midi device 1</string>
</property>
<layout class="QVBoxLayout" name="midLayout1">
<item>
<layout class="QHBoxLayout" name="midiHLayout1" stretch="1,2">
<item>
<widget class="QComboBox" name="midiTypeBox1"/>
</item>
<item>
<widget class="QComboBox" name="midiDeviceBox1"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="6" column="2">
<widget class="QGroupBox" name="gb_midi_3">
<property name="title">
<string>Emulated Midi device 3</string>
</property>
<layout class="QVBoxLayout" name="midLayout3">
<item>
<layout class="QHBoxLayout" name="midiHLayout3" stretch="1,2">
<item>
<widget class="QComboBox" name="midiTypeBox3"/>
</item>
<item>
<widget class="QComboBox" name="midiDeviceBox3"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="5" column="2">
<widget class="QGroupBox" name="gb_midi_2">
<property name="title">
<string>Emulated Midi device 2</string>
</property>
<layout class="QVBoxLayout" name="midLayout2">
<item>
<layout class="QHBoxLayout" name="midiHLayout2" stretch="1,2">
<item>
<widget class="QComboBox" name="midiTypeBox2"/>
</item>
<item>
<widget class="QComboBox" name="midiDeviceBox2"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="5" column="0" rowspan="2">
<widget class="QGroupBox" name="gb_additional_io_settings">
<property name="title">
<string>Additional Settings</string>
@ -1766,6 +1817,22 @@
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacerIoAdditionalSettings">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>

View File

@ -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