diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt
index 97de6a15c9..c755a20e63 100644
--- a/pcsx2/CMakeLists.txt
+++ b/pcsx2/CMakeLists.txt
@@ -437,6 +437,7 @@ set(pcsx2USBSources
USB/usb-pad/usb-pad-ff.cpp
USB/usb-pad/usb-pad.cpp
USB/usb-pad/usb-seamic.cpp
+ USB/usb-pad/usb-turntable.cpp
USB/usb-printer/usb-printer.cpp
)
diff --git a/pcsx2/USB/deviceproxy.cpp b/pcsx2/USB/deviceproxy.cpp
index 79a66025c6..d285e3c26e 100644
--- a/pcsx2/USB/deviceproxy.cpp
+++ b/pcsx2/USB/deviceproxy.cpp
@@ -21,6 +21,7 @@
#include "usb-mic/usb-mic-singstar.h"
#include "usb-msd/usb-msd.h"
#include "usb-pad/usb-pad.h"
+#include "usb-pad/usb-turntable.h"
#include "usb-printer/usb-printer.h"
#include "usb-lightgun/guncon2.h"
@@ -82,6 +83,7 @@ void RegisterDevice::Register()
inst.Add(DEVTYPE_HIDKBD, new usb_hid::HIDKbdDevice());
inst.Add(DEVTYPE_HIDMOUSE, new usb_hid::HIDMouseDevice());
inst.Add(DEVTYPE_RBKIT, new usb_pad::RBDrumKitDevice());
+ inst.Add(DEVTYPE_DJ, new usb_pad::DJTurntableDevice());
inst.Add(DEVTYPE_BUZZ, new usb_pad::BuzzDevice());
inst.Add(DEVTYPE_EYETOY, new usb_eyetoy::EyeToyWebCamDevice());
inst.Add(DEVTYPE_BEATMANIA_DADADA, new usb_hid::BeatManiaDevice());
diff --git a/pcsx2/USB/deviceproxy.h b/pcsx2/USB/deviceproxy.h
index 27942dfc3d..b393d5e2d1 100644
--- a/pcsx2/USB/deviceproxy.h
+++ b/pcsx2/USB/deviceproxy.h
@@ -50,7 +50,8 @@ enum DeviceType : s32
DEVTYPE_SEGA_SEAMIC,
DEVTYPE_PRINTER,
DEVTYPE_KEYBOARDMANIA,
- DEVTYPE_GUNCON2
+ DEVTYPE_GUNCON2,
+ DEVTYPE_DJ
};
class DeviceProxy
diff --git a/pcsx2/USB/usb-pad/usb-turntable.cpp b/pcsx2/USB/usb-pad/usb-turntable.cpp
new file mode 100644
index 0000000000..660c6b55eb
--- /dev/null
+++ b/pcsx2/USB/usb-pad/usb-turntable.cpp
@@ -0,0 +1,476 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2023 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include "Host.h"
+#include "Input/InputManager.h"
+#include "StateWrapper.h"
+#include "USB/USB.h"
+#include "USB/deviceproxy.h"
+#include "USB/qemu-usb/USBinternal.h"
+#include "USB/qemu-usb/desc.h"
+#include "USB/usb-pad/usb-turntable.h"
+#include "USB/usb-pad/usb-pad.h"
+
+namespace usb_pad
+{
+ static const USBDescStrings desc_strings = {
+ "",
+ "RedOctane DJ",
+ "Guitar Hero5 for PlayStation (R) 3"};
+
+ // Should be usb 2.0, but seems to make no difference with DJ Hero games
+ static const uint8_t dev_descriptor[] = {
+ /* bLength */ 0x12, //(18)
+ /* bDescriptorType */ 0x01, //(1)
+ /* bcdUSB */ WBVAL(0x0110), //USB 1.1
+ /* bDeviceClass */ 0x00, //(0)
+ /* bDeviceSubClass */ 0x00, //(0)
+ /* bDeviceProtocol */ 0x00, //(0)
+ /* bMaxPacketSize0 */ 0x40, //(64)
+ /* idVendor */ WBVAL(0x12ba),
+ /* idProduct */ WBVAL(0x0140),
+ /* bcdDevice */ WBVAL(0x1000), //(10.00)
+ /* iManufacturer */ 0x01, //(1)
+ /* iProduct */ 0x02, //(2)
+ /* iSerialNumber */ 0x00, //(0)
+ /* bNumConfigurations */ 0x01, //(1)
+ };
+
+ TurntableState::TurntableState(u32 port_)
+ : port(port_)
+ {
+
+ turntable_multiplier = 1;
+ }
+
+ TurntableState::~TurntableState() = default;
+
+ int TurntableState::TokenIn(u8* buf, int len)
+ {
+ std::memset(buf, 0, 8);
+
+ UpdateHatSwitch();
+
+ u32* buttons = reinterpret_cast(buf);
+ *buttons = (data.buttons & ((1 << (CID_DJ_START + 1)) - 1));
+ *buttons |= (data.hatswitch & 0xF) << 16;
+
+ // Map platter buttons to standard buttons for menus
+ if (data.buttons & (1 << CID_DJ_LEFT_GREEN) || data.buttons & (1 << CID_DJ_RIGHT_GREEN))
+ {
+ *buttons |= (1 << CID_DJ_CROSS);
+ }
+ if (data.buttons & (1 << CID_DJ_LEFT_RED) || data.buttons & (1 << CID_DJ_RIGHT_RED))
+ {
+ *buttons |= (1 << CID_DJ_CIRCLE);
+ }
+ if (data.buttons & (1 << CID_DJ_LEFT_BLUE) || data.buttons & (1 << CID_DJ_RIGHT_BLUE))
+ {
+ *buttons |= (1 << CID_DJ_SQUARE);
+ }
+ // Crossfader and Effects Knob are put into "accelerometer" bits in the PS3 reports
+ // So they need to be scaled to 1024
+ u16 crossfader = 0x0200;
+ u16 effectsknob = 0x0200;
+ if (data.crossfader_left > 0)
+ {
+ crossfader -= data.crossfader_left;
+ }
+ else
+ {
+ crossfader += data.crossfader_right;
+ }
+ if (data.effectsknob_left > 0)
+ {
+ effectsknob -= data.effectsknob_left;
+ }
+ else
+ {
+ effectsknob += data.effectsknob_right;
+ }
+
+ u8 left_turntable = 0x80;
+ u8 right_turntable = 0x80;
+ if (data.left_turntable_up > 0)
+ {
+ left_turntable -= static_cast(std::min(data.left_turntable_up * turntable_multiplier, 0x7F));
+ }
+ else
+ {
+ left_turntable += static_cast(std::min(data.left_turntable_down * turntable_multiplier, 0x7F));
+ }
+ if (data.right_turntable_up > 0)
+ {
+ right_turntable -= static_cast(std::min(data.right_turntable_up * turntable_multiplier, 0x7F));
+ }
+ else
+ {
+ right_turntable += static_cast(std::min(data.right_turntable_down * turntable_multiplier, 0x7F));
+ }
+ buf[3] = 0x80;
+ buf[4] = 0x80;
+ buf[5] = left_turntable;
+ buf[6] = right_turntable;
+
+ buf[19] = effectsknob & 0xFF;
+ buf[20] = effectsknob >> 8;
+ buf[21] = crossfader & 0xFF;
+ buf[22] = crossfader >> 8;
+
+ // Platter buttons
+ buf[23] = (data.buttons >> CID_DJ_RIGHT_GREEN) & 0xFF;
+ buf[24] = 0x02;
+ buf[26] = 0x02;
+
+ return len;
+ }
+
+ void TurntableState::UpdateSettings(SettingsInterface& si, const char* devname)
+ {
+ turntable_multiplier = USB::GetConfigInt(si, port, devname, "TurntableMultiplier", 1);
+ }
+
+ void TurntableState::UpdateHatSwitch()
+ {
+ if (data.hat_up && data.hat_right)
+ data.hatswitch = 1;
+ else if (data.hat_right && data.hat_down)
+ data.hatswitch = 3;
+ else if (data.hat_down && data.hat_left)
+ data.hatswitch = 5;
+ else if (data.hat_left && data.hat_up)
+ data.hatswitch = 7;
+ else if (data.hat_up)
+ data.hatswitch = 0;
+ else if (data.hat_right)
+ data.hatswitch = 2;
+ else if (data.hat_down)
+ data.hatswitch = 4;
+ else if (data.hat_left)
+ data.hatswitch = 6;
+ else
+ data.hatswitch = 8;
+ }
+
+ void TurntableState::SetEuphoriaLedState(bool state)
+ {
+ data.euphoria_led_state = state;
+ }
+
+ static void turntable_handle_control(USBDevice* dev, USBPacket* p,
+ int request, int value, int index, int length, uint8_t* data)
+ {
+ TurntableState* s = USB_CONTAINER_OF(dev, TurntableState, dev);
+ int ret;
+
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0)
+ {
+ return;
+ }
+
+ switch (request)
+ {
+ case SET_REPORT:
+ if (data[0] == 0x91) {
+ s->SetEuphoriaLedState(data[2]);
+ }
+ break;
+ case SET_IDLE:
+ break;
+ default:
+ ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+ if (ret >= 0)
+ {
+ return;
+ }
+ p->status = USB_RET_STALL;
+ break;
+ }
+ }
+
+ static void turntable_handle_data(USBDevice* dev, USBPacket* p)
+ {
+ TurntableState* s = USB_CONTAINER_OF(dev, TurntableState, dev);
+
+ switch (p->pid)
+ {
+ case USB_TOKEN_IN:
+ if (p->ep->nr == 1)
+ {
+ int ret = s->TokenIn(p->buffer_ptr, p->buffer_size);
+
+ if (ret > 0)
+ p->actual_length += std::min(static_cast(ret), p->buffer_size);
+ else
+ p->status = ret;
+ }
+ else
+ {
+ goto fail;
+ }
+ break;
+ case USB_TOKEN_OUT:
+ break;
+ default:
+ fail:
+ p->status = USB_RET_STALL;
+ break;
+ }
+ }
+
+ static void turntable_unrealize(USBDevice* dev)
+ {
+ TurntableState* s = USB_CONTAINER_OF(dev, TurntableState, dev);
+ delete s;
+ }
+
+ const char* DJTurntableDevice::Name() const
+ {
+ return TRANSLATE_NOOP("USB", "DJ Hero Turntable");
+ }
+
+ const char* DJTurntableDevice::TypeName() const
+ {
+ return "DJTurntable";
+ }
+
+ USBDevice* DJTurntableDevice::CreateDevice(SettingsInterface& si, u32 port, u32 subtype) const
+ {
+ TurntableState* s = new TurntableState(port);
+
+ s->desc.full = &s->desc_dev;
+ s->desc.str = desc_strings;
+
+ if (usb_desc_parse_dev(dev_descriptor, sizeof(dev_descriptor), s->desc, s->desc_dev) < 0)
+ goto fail;
+
+ // PS3 / wii instruments all share the same config descriptors and hid reports
+ if (usb_desc_parse_config(rb1_config_descriptor, sizeof(rb1_config_descriptor), s->desc_dev) < 0)
+ goto fail;
+
+ s->dev.speed = USB_SPEED_FULL;
+ s->dev.klass.handle_attach = usb_desc_attach;
+ s->dev.klass.handle_reset = nullptr;
+ s->dev.klass.handle_control = turntable_handle_control;
+ s->dev.klass.handle_data = turntable_handle_data;
+ s->dev.klass.unrealize = turntable_unrealize;
+ s->dev.klass.usb_desc = &s->desc;
+ s->dev.klass.product_desc = nullptr;
+
+ usb_desc_init(&s->dev);
+ usb_ep_init(&s->dev);
+
+ return &s->dev;
+
+ fail:
+ turntable_unrealize(&s->dev);
+ return nullptr;
+ }
+
+ void DJTurntableDevice::UpdateSettings(USBDevice* dev, SettingsInterface& si) const
+ {
+ USB_CONTAINER_OF(dev, TurntableState, dev)->UpdateSettings(si, TypeName());
+ }
+
+ float DJTurntableDevice::GetBindingValue(const USBDevice* dev, u32 bind_index) const
+ {
+ const TurntableState* s = USB_CONTAINER_OF(dev, const TurntableState, dev);
+
+ switch (bind_index)
+ {
+ case CID_DJ_CROSSFADER_LEFT:
+ return static_cast(s->data.crossfader_left) / 512.0f;
+
+ case CID_DJ_CROSSFADER_RIGHT:
+ return static_cast(s->data.crossfader_right) / 512.0f;
+
+ case CID_DJ_EFFECTSKNOB_LEFT:
+ return static_cast(s->data.effectsknob_left) / 512.0f;
+
+ case CID_DJ_EFFECTSKNOB_RIGHT:
+ return static_cast(s->data.effectsknob_right) / 512.0f;
+
+ case CID_DJ_LEFT_TURNTABLE_UP:
+ return static_cast(s->data.left_turntable_up) / 255.0f;
+
+ case CID_DJ_LEFT_TURNTABLE_DOWN:
+ return static_cast(s->data.left_turntable_down) / 255.0f;
+
+ case CID_DJ_RIGHT_TURNTABLE_UP:
+ return static_cast(s->data.right_turntable_up) / 255.0f;
+
+ case CID_DJ_RIGHT_TURNTABLE_DOWN:
+ return static_cast(s->data.right_turntable_down) / 255.0f;
+
+ case CID_DJ_DPAD_UP:
+ return static_cast(s->data.hat_up);
+
+ case CID_DJ_DPAD_DOWN:
+ return static_cast(s->data.hat_down);
+
+ case CID_DJ_DPAD_LEFT:
+ return static_cast(s->data.hat_left);
+
+ case CID_DJ_DPAD_RIGHT:
+ return static_cast(s->data.hat_right);
+
+ case CID_DJ_SQUARE:
+ case CID_DJ_CROSS:
+ case CID_DJ_CIRCLE:
+ case CID_DJ_TRIANGLE:
+ case CID_DJ_SELECT:
+ case CID_DJ_START:
+ case CID_DJ_RIGHT_GREEN:
+ case CID_DJ_RIGHT_RED:
+ case CID_DJ_RIGHT_BLUE:
+ case CID_DJ_LEFT_GREEN:
+ case CID_DJ_LEFT_RED:
+ case CID_DJ_LEFT_BLUE:
+ {
+ const u32 mask = (1u << bind_index);
+ return ((s->data.buttons & mask) != 0u) ? 1.0f : 0.0f;
+ }
+ default:
+ return 0.0f;
+ }
+ }
+
+ void DJTurntableDevice::SetBindingValue(USBDevice* dev, u32 bind_index, float value) const
+ {
+ TurntableState* s = USB_CONTAINER_OF(dev, TurntableState, dev);
+ switch (bind_index)
+ {
+ case CID_DJ_CROSSFADER_LEFT:
+ s->data.crossfader_left = static_cast(std::clamp(std::lroundf(value * 512.0f), 0, 512));
+ break;
+
+ case CID_DJ_CROSSFADER_RIGHT:
+ s->data.crossfader_right = static_cast(std::clamp(std::lroundf(value * 512.0f), 0, 512));
+ break;
+
+ case CID_DJ_EFFECTSKNOB_LEFT:
+ s->data.effectsknob_left = static_cast(std::clamp(std::lroundf(value * 512.0f), 0, 512));
+ break;
+
+ case CID_DJ_EFFECTSKNOB_RIGHT:
+ s->data.effectsknob_right = static_cast(std::clamp(std::lroundf(value * 512.0f), 0, 512));
+ break;
+
+ case CID_DJ_LEFT_TURNTABLE_UP:
+ s->data.left_turntable_up = static_cast(std::clamp(std::lroundf(value * 255.0f), 0, 255));
+ break;
+
+ case CID_DJ_LEFT_TURNTABLE_DOWN:
+ s->data.left_turntable_down = static_cast(std::clamp(std::lroundf(value * 255.0f), 0, 255));
+ break;
+
+ case CID_DJ_RIGHT_TURNTABLE_UP:
+ s->data.right_turntable_up = static_cast(std::clamp(std::lroundf(value * 255.0f), 0, 255));
+ break;
+
+ case CID_DJ_RIGHT_TURNTABLE_DOWN:
+ s->data.right_turntable_down = static_cast(std::clamp(std::lroundf(value * 255.0f), 0, 255));
+ break;
+
+ case CID_DJ_DPAD_UP:
+ s->data.hat_up = static_cast(std::clamp(std::lroundf(value * 255.0f), 0, 255));
+ s->UpdateHatSwitch();
+ break;
+ case CID_DJ_DPAD_DOWN:
+ s->data.hat_down = static_cast(std::clamp(std::lroundf(value * 255.0f), 0, 255));
+ s->UpdateHatSwitch();
+ break;
+ case CID_DJ_DPAD_LEFT:
+ s->data.hat_left = static_cast(std::clamp(std::lroundf(value * 255.0f), 0, 255));
+ s->UpdateHatSwitch();
+ break;
+ case CID_DJ_DPAD_RIGHT:
+ s->data.hat_right = static_cast(std::clamp(std::lroundf(value * 255.0f), 0, 255));
+ s->UpdateHatSwitch();
+ break;
+
+ case CID_DJ_SQUARE:
+ case CID_DJ_CROSS:
+ case CID_DJ_CIRCLE:
+ case CID_DJ_TRIANGLE:
+ case CID_DJ_SELECT:
+ case CID_DJ_START:
+ case CID_DJ_RIGHT_GREEN:
+ case CID_DJ_RIGHT_RED:
+ case CID_DJ_RIGHT_BLUE:
+ case CID_DJ_LEFT_GREEN:
+ case CID_DJ_LEFT_RED:
+ case CID_DJ_LEFT_BLUE:
+ {
+ const u32 mask = (1u << bind_index);
+ if (value >= 0.5f)
+ s->data.buttons |= mask;
+ else
+ s->data.buttons &= ~mask;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ gsl::span DJTurntableDevice::Bindings(u32 subtype) const
+ {
+ static constexpr const InputBindingInfo bindings[] = {
+ {"DPadUp", TRANSLATE_NOOP("USB", "D-Pad Up"), InputBindingInfo::Type::Button, CID_DJ_DPAD_UP, GenericInputBinding::DPadUp},
+ {"DPadDown", TRANSLATE_NOOP("USB", "D-Pad Down"), InputBindingInfo::Type::Button, CID_DJ_DPAD_DOWN, GenericInputBinding::DPadDown},
+ {"DPadLeft", TRANSLATE_NOOP("USB", "D-Pad Left"), InputBindingInfo::Type::Button, CID_DJ_DPAD_LEFT, GenericInputBinding::DPadLeft},
+ {"DPadRight", TRANSLATE_NOOP("USB", "D-Pad Right"), InputBindingInfo::Type::Button, CID_DJ_DPAD_RIGHT, GenericInputBinding::DPadRight},
+ {"Square", TRANSLATE_NOOP("USB", "Square"), InputBindingInfo::Type::Button, CID_DJ_SQUARE, GenericInputBinding::Unknown},
+ {"Cross", TRANSLATE_NOOP("USB", "Cross"), InputBindingInfo::Type::Button, CID_DJ_CROSS, GenericInputBinding::Unknown},
+ {"Circle", TRANSLATE_NOOP("USB", "Circle"), InputBindingInfo::Type::Button, CID_DJ_CIRCLE, GenericInputBinding::Unknown},
+ {"Triangle", TRANSLATE_NOOP("USB", "Triangle / Euphoria"), InputBindingInfo::Type::Button, CID_DJ_TRIANGLE, GenericInputBinding::Triangle},
+ {"Select", TRANSLATE_NOOP("USB", "Select"), InputBindingInfo::Type::Button, CID_DJ_SELECT, GenericInputBinding::Select},
+ {"Start", TRANSLATE_NOOP("USB", "Start"), InputBindingInfo::Type::Button, CID_DJ_START, GenericInputBinding::Start},
+ {"CrossFaderLeft", TRANSLATE_NOOP("USB", "Cross Fader Left"), InputBindingInfo::Type::HalfAxis, CID_DJ_CROSSFADER_LEFT, GenericInputBinding::RightStickDown},
+ {"CrossFaderRight", TRANSLATE_NOOP("USB", "Cross Fader Right"), InputBindingInfo::Type::HalfAxis, CID_DJ_CROSSFADER_RIGHT, GenericInputBinding::RightStickUp},
+ {"EffectsKnobLeft", TRANSLATE_NOOP("USB", "Effects Knob Left"), InputBindingInfo::Type::HalfAxis, CID_DJ_EFFECTSKNOB_LEFT, GenericInputBinding::RightStickLeft},
+ {"EffectsKnobRight", TRANSLATE_NOOP("USB", "Effects Knob Right"), InputBindingInfo::Type::HalfAxis, CID_DJ_EFFECTSKNOB_RIGHT, GenericInputBinding::RightStickRight},
+ {"LeftTurntableUp", TRANSLATE_NOOP("USB", "Left Turntable Up"), InputBindingInfo::Type::HalfAxis, CID_DJ_LEFT_TURNTABLE_UP, GenericInputBinding::LeftStickLeft},
+ {"LeftTurntableDown", TRANSLATE_NOOP("USB", "Left Turntable Down"), InputBindingInfo::Type::HalfAxis, CID_DJ_LEFT_TURNTABLE_DOWN, GenericInputBinding::LeftStickRight},
+ {"RightTurntableUp", TRANSLATE_NOOP("USB", "Right Turntable Up"), InputBindingInfo::Type::HalfAxis, CID_DJ_RIGHT_TURNTABLE_UP, GenericInputBinding::LeftStickUp},
+ {"RightTurntableDown", TRANSLATE_NOOP("USB", "Right Turntable Down"), InputBindingInfo::Type::HalfAxis, CID_DJ_RIGHT_TURNTABLE_DOWN, GenericInputBinding::LeftStickDown},
+ {"Right Green", TRANSLATE_NOOP("USB", "Right Green"), InputBindingInfo::Type::Button, CID_DJ_RIGHT_GREEN, GenericInputBinding::Cross},
+ {"Right Red", TRANSLATE_NOOP("USB", "Right Red"), InputBindingInfo::Type::Button, CID_DJ_RIGHT_RED, GenericInputBinding::Circle},
+ {"Right Blue", TRANSLATE_NOOP("USB", "Right Blue"), InputBindingInfo::Type::Button, CID_DJ_RIGHT_BLUE, GenericInputBinding::Square},
+ {"Left Green", TRANSLATE_NOOP("USB", "Left Green"), InputBindingInfo::Type::Button, CID_DJ_LEFT_GREEN, GenericInputBinding::Unknown},
+ {"Left Red", TRANSLATE_NOOP("USB", "Left Red"), InputBindingInfo::Type::Button, CID_DJ_LEFT_RED, GenericInputBinding::Unknown},
+ {"Left Blue", TRANSLATE_NOOP("USB", "Left Blue"), InputBindingInfo::Type::Button, CID_DJ_LEFT_BLUE, GenericInputBinding::Unknown}
+
+ };
+
+ return bindings;
+ }
+
+ gsl::span DJTurntableDevice::Settings(u32 subtype) const
+ {
+ static constexpr const SettingInfo info[] = {
+ {SettingInfo::Type::Integer, "TurntableMultiplier", TRANSLATE_NOOP("USB", "Turntable Multiplier"),
+ TRANSLATE_NOOP("USB", "Multiplies the turntable rotation speed by a constant. Useful for using Xbox 360 turntables."),
+ "1", "1", "100", "1", "%d", nullptr, nullptr, 1.0f}};
+
+ return info;
+ }
+
+} // namespace usb_pad
diff --git a/pcsx2/USB/usb-pad/usb-turntable.h b/pcsx2/USB/usb-pad/usb-turntable.h
new file mode 100644
index 0000000000..57dba1ceeb
--- /dev/null
+++ b/pcsx2/USB/usb-pad/usb-turntable.h
@@ -0,0 +1,109 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2023 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+#include "SaveState.h"
+#include
+#include
+
+namespace usb_pad
+{
+ enum TurntableControlID
+ {
+ CID_DJ_SQUARE,
+ CID_DJ_CROSS,
+ CID_DJ_CIRCLE,
+ CID_DJ_TRIANGLE,
+ CID_DJ_SELECT = 8,
+ CID_DJ_START,
+ CID_DJ_RIGHT_GREEN,
+ CID_DJ_RIGHT_RED,
+ CID_DJ_RIGHT_BLUE,
+ CID_DJ_LEFT_GREEN = 14,
+ CID_DJ_LEFT_RED,
+ CID_DJ_LEFT_BLUE,
+ CID_DJ_CROSSFADER_LEFT,
+ CID_DJ_CROSSFADER_RIGHT,
+ CID_DJ_EFFECTSKNOB_LEFT,
+ CID_DJ_EFFECTSKNOB_RIGHT,
+ CID_DJ_LEFT_TURNTABLE_UP,
+ CID_DJ_LEFT_TURNTABLE_DOWN,
+ CID_DJ_RIGHT_TURNTABLE_UP,
+ CID_DJ_RIGHT_TURNTABLE_DOWN,
+ CID_DJ_DPAD_UP,
+ CID_DJ_DPAD_DOWN,
+ CID_DJ_DPAD_LEFT,
+ CID_DJ_DPAD_RIGHT,
+ CID_DJ_COUNT,
+ };
+ struct TurntableState
+ {
+ TurntableState(u32 port_);
+ ~TurntableState();
+
+ void UpdateSettings(SettingsInterface& si, const char* devname);
+
+ float GetBindValue(u32 bind) const;
+ void SetBindValue(u32 bind, float value);
+
+ void SetEuphoriaLedState(bool state);
+ int TokenIn(u8* buf, int len);
+ int TokenOut(const u8* buf, int len);
+
+ void UpdateHatSwitch();
+
+ u32 port = 0;
+
+ USBDevice dev{};
+ USBDesc desc{};
+ USBDescDevice desc_dev{};
+
+ u16 turntable_multiplier = 1;
+
+ struct
+ {
+ // intermediate state, resolved at query time
+ s16 crossfader_left;
+ s16 crossfader_right;
+ s16 effectsknob_left;
+ s16 effectsknob_right;
+ s16 left_turntable_up;
+ s16 left_turntable_down;
+ s16 right_turntable_up;
+ s16 right_turntable_down;
+ bool hat_left : 1;
+ bool hat_right : 1;
+ bool hat_up : 1;
+ bool hat_down : 1;
+
+ u8 hatswitch; // direction
+ u32 buttons; // active high
+ bool euphoria_led_state; // 1 = on, 0 = off
+ } data = {};
+ };
+ class DJTurntableDevice final : public DeviceProxy
+ {
+ public:
+ const char* Name() const override;
+ const char* TypeName() const override;
+ float GetBindingValue(const USBDevice* dev, u32 bind_index) const override;
+ void SetBindingValue(USBDevice* dev, u32 bind_index, float value) const override;
+ void UpdateSettings(USBDevice* dev, SettingsInterface& si) const override;
+ gsl::span Bindings(u32 subtype) const override;
+ gsl::span Settings(u32 subtype) const override;
+ USBDevice* CreateDevice(SettingsInterface& si, u32 port, u32 subtype) const override;
+ };
+
+} // namespace usb_pad
diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj
index 455cfd6925..c8f2c93589 100644
--- a/pcsx2/pcsx2.vcxproj
+++ b/pcsx2/pcsx2.vcxproj
@@ -341,6 +341,7 @@
+
@@ -698,6 +699,7 @@
+
diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters
index 06e5aacba5..8b5c36a834 100644
--- a/pcsx2/pcsx2.vcxproj.filters
+++ b/pcsx2/pcsx2.vcxproj.filters
@@ -1244,6 +1244,9 @@
System\Ps2\USB\usb-pad
+
+ System\Ps2\USB\usb-pad
+
System\Ps2\USB\usb-pad
@@ -2210,6 +2213,9 @@
System\Ps2\USB\usb-pad
+
+ System\Ps2\USB\usb-pad
+
System\Ps2\USB\usb-pad\lg