rpcsx/rpcs3/Emu/Io/RB3MidiGuitar.cpp
Dark b0a346709e RB3MidiGuitar.cpp: Support 5-Fret play
Rock Band 3 has support for playing the regular guitar mode with the
Pro Guitar. While the real MPA does not support this, the game accepts
it just fine so there's no harm in emulating it.
2023-06-27 20:11:10 +02:00

352 lines
9.9 KiB
C++

// 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 frets for playing 5 fret on the pro guitar
// this actually isn't done by the real MPA, but Rock Band 3 allows this
// so there's no harm in supporting it.
for (u8 i : button_state.frets)
{
switch (i)
{
case 1:
case 6:
case 13:
buf[9] |= 0b1000'0000;
break;
case 2:
case 7:
case 14:
buf[10] |= 0b1000'0000;
break;
case 3:
case 8:
case 15:
buf[11] |= 0b1000'0000;
break;
case 4:
case 9:
case 16:
buf[12] |= 0b1000'0000;
break;
case 5:
case 10:
case 17:
buf[13] |= 0b1000'0000;
break;
default:
break;
}
// enable the solo bit for frets >= 13
if (i >= 13)
{
buf[8] |= 0b1000'0000;
}
}
// 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;
}