From 1d4106968ee3e1ee2cd1e1b13996501525765eb8 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Tue, 24 Sep 2024 09:53:20 +0200 Subject: [PATCH] proof of concept: control crackin'dj with traktor S4 --- CMakeLists.txt | 2 +- core/hw/maple/maple_jvs.cpp | 30 ++++++++ core/sdl/hid.cpp | 144 ++++++++++++++++++++++++++++++++++++ core/sdl/hid.h | 9 +++ core/sdl/sdl.cpp | 4 + 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 core/sdl/hid.cpp create mode 100644 core/sdl/hid.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f12f63ba6..c4aecd110 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -448,7 +448,7 @@ if(NOT LIBRETRO) endif() target_compile_definitions(${PROJECT_NAME} PRIVATE USE_SDL USE_SDL_AUDIO) - target_sources(${PROJECT_NAME} PRIVATE core/sdl/sdl.cpp core/sdl/sdl.h core/sdl/sdl_gamepad.h core/sdl/sdl_keyboard.h) + target_sources(${PROJECT_NAME} PRIVATE core/sdl/sdl.cpp core/sdl/sdl.h core/sdl/sdl_gamepad.h core/sdl/sdl_keyboard.h core/sdl/hid.cpp) if((UNIX AND NOT APPLE) OR NINTENDO_SWITCH) find_package(CURL REQUIRED) diff --git a/core/hw/maple/maple_jvs.cpp b/core/hw/maple/maple_jvs.cpp index 284bd16b7..2b0589888 100644 --- a/core/hw/maple/maple_jvs.cpp +++ b/core/hw/maple/maple_jvs.cpp @@ -30,6 +30,11 @@ #include #include +#define CRACKINDJ_TRAKTOR +#ifdef CRACKINDJ_TRAKTOR +#include "sdl/hid.h" +#endif + #define LOGJVS(...) DEBUG_LOG(JVS, __VA_ARGS__) u8 *EEPROM; @@ -236,6 +241,9 @@ protected: networkOutput.output(name.c_str(), (newOutput >> i) & 1); } digOutput = newOutput; +#ifdef CRACKINDJ_TRAKTOR + hidOutput(*data & 0x80, *data & 8, *data & 0x20, *data & 0x40, *data & 4); +#endif } virtual void read_lightgun(int playerNum, u32 buttons, u16& x, u16& y) @@ -482,6 +490,7 @@ protected: { jvs_io_board& outputBoard = *parent->io_boards[1]; bool turntableOn = outputBoard.getDigitalOutput() & 0x10; +#ifndef CRACKINDJ_TRAKTOR switch (channel) { case 0: // Left turntable @@ -497,11 +506,28 @@ protected: default: return 0; } +#else + if (channel != 0 && channel != 2) + return 0; + channel /= 2; + rotary[channel] -= jogWheelsDelta[channel]; + if (turntableOn && !jogWheelsTouched[channel]) + // should be around 10, possibly a bit less to match real hw (based on yt videos) + // half a turn should skip 3 to 4 tunes (more like 4?) + // currently half turn -> 7 + rotary[channel] -= 10; + //printf("wheel[%d] %d motor %d\n", channel, rotary[channel], turntableOn); + jogWheelsDelta[channel] = 0; + return rotary[channel]; +#endif } private: s16 motorRotation[2]{}; s16 lastRel[2]{}; +#ifdef CRACKINDJ_TRAKTOR + s16 rotary[2] {}; +#endif }; // Sega Marine Fishing, 18 Wheeler (TODO) @@ -1978,6 +2004,10 @@ void maple_naomi_jamma::deserialize(Deserializer& deser) u16 jvs_io_board::read_analog_axis(int player_num, int player_axis, bool inverted) { +#ifdef CRACKINDJ_TRAKTOR + if (player_num == 0 && player_axis == 0) + return (2048 - std::clamp(crossFader, -2030, 2030)) * 16; +#endif u16 v; if (player_axis >= 0 && player_axis < 4) v = mapleInputState[player_num].fullAxes[player_axis] + 0x8000; diff --git a/core/sdl/hid.cpp b/core/sdl/hid.cpp new file mode 100644 index 000000000..4663fe317 --- /dev/null +++ b/core/sdl/hid.cpp @@ -0,0 +1,144 @@ +int jogWheelsDelta[2]; +bool jogWheelsTouched[2]; +short crossFader; + +#if defined(USE_SDL) +#include "types.h" +#include "hid.h" +#include "input/gamepad_device.h" +#include + +#if SDL_VERSION_ATLEAST(2, 0, 18) +static SDL_hid_device *device; +static u8 lastJogTick[2]; +static u16 lastJogTime[2]; + +// need rw access to the hid device (sudo chmod o+rw /dev/hidraw4) +void hidInit() +{ + SDL_hid_init(); + + /* + SDL_hid_device_info *devInfo = SDL_hid_enumerate(0, 0); // vendor id, product id + while (devInfo != nullptr) + { + printf("path %s vendor/prod %04x/%04x manufact %S product %S\n", devInfo->path, devInfo->vendor_id, devInfo->product_id, + devInfo->manufacturer_string, devInfo->product_string); + devInfo = devInfo->next; + } + SDL_hid_free_enumeration(devInfo); + */ + + // 17cc/1310 is traktor s4 + device = SDL_hid_open(0x17cc, 0x1310, nullptr); + if (device == nullptr) + WARN_LOG(INPUT, "SDL_hid_open failed"); + else + SDL_hid_set_nonblocking(device, 1); + hidOutput(false, false, false, false, false); +} + +void hidTerm() +{ + if (device != nullptr) + { + hidOutput(false, false, false, false, false); + SDL_hid_close(device); + device = nullptr; + } + SDL_hid_exit(); +} + +void hidInput() +{ + if (device == nullptr) + return; + u8 data[256]; + for (;;) + { + int read = SDL_hid_read(device, data, sizeof(data)); + if (read <= 0) + { + if (read < 0) + WARN_LOG(INPUT, "hid_read failed"); + break; + } + if (read >= 10) + { + // addControl(group, name, offset, pack, bitmask, isEncoder, callback) + // I: u32 + // H: u16 + if (data[0] == 1) // short msg + { + // MessageShort.addControl("deck1", "!jog_wheel", 0x01, "I") + // MessageShort.addControl("deck2", "!jog_wheel", 0x05, "I") + // MessageShort.addControl("deck1", "!jog_touch", 0x11, "B", 0x01) + // MessageShort.addControl("deck2", "!jog_touch", 0x11, "B", 0x02) + // MessageShort.addControl("deck1", "!play", 0x0D, "B", 0x01); + // MessageShort.addControl("deck2", "!play", 0x0C, "B", 0x01); + for (int i = 0; i < 2; i++) + { + u32 jog = *(u32 *)&data[i * 4 + 1]; + u8 tickval = jog & 0xff; + u16 timeval = jog >> 16; + if (lastJogTime[i] > timeval) { + // We looped around. Adjust current time so that subtraction works. + timeval += 0x10000; + } + int time_delta = timeval - lastJogTime[i]; + if (time_delta == 0) + // Spinning too fast to detect speed! By not dividing we are guessing it took 1ms. + time_delta = 1; + int tick_delta = 0; + if (lastJogTick[i] >= 200 && tickval <= 100) + tick_delta = tickval + 256 - lastJogTick[i]; + else if (lastJogTick[i] <= 100 && tickval >= 200) + tick_delta = tickval - lastJogTick[i] - 256; + else + tick_delta = tickval - lastJogTick[i]; + + lastJogTick[i] = tickval; + lastJogTime[i] = timeval; + jogWheelsDelta[i] += tick_delta; + //printf("%s wheel %d\n", i == 0 ? "left" : "right", jogWheelsDelta[i]); + + jogWheelsTouched[i] = data[0x11] & (1 << i); + } + if ((data[0xd] & 1) || (data[0xc] & 1)) + kcode[0] &= ~DC_BTN_START; + else + kcode[0] |= DC_BTN_START; + } + else + { + // reportID=2: long msg + //MessageLong.addControl("[Master]", "crossfader", 0x07, "H"); + crossFader = *(u16 *)&data[7] - 2048; + //printf("cross fader %d\n", crossFader); + } + } + } +} + +void hidOutput(bool play, bool spotL, bool spotR, bool backL, bool backR) +{ + if (device == nullptr) + return; + u8 msg[0x3E]{}; + msg[0] = 0x81; + msg[0x20] = play ? 0xff : 0; // deck1 Play + msg[0x28] = play ? 0xff : 0; // deck2 Play + msg[0x2d] = spotL ? 0xff : 0; // deck1 on air + msg[0x33] = spotR ? 0xff : 0; // deck2 on air + msg[0x2e] = backL ? 0xff : 0; // deck A + msg[0x34] = backR ? 0xff : 0; // deck B + SDL_hid_write(device, msg, sizeof(msg)); +} + +#else // SDL < 2.0.18 +void hidOutput(bool play, bool spotL, bool spotR, bool backL, bool backR) {} +#endif + +#else // !USE_SDL +void hidOutput(bool play, bool spotL, bool spotR, bool backL, bool backR) {} +#endif diff --git a/core/sdl/hid.h b/core/sdl/hid.h new file mode 100644 index 000000000..a8e21dfbf --- /dev/null +++ b/core/sdl/hid.h @@ -0,0 +1,9 @@ +#pragma once +void hidInit(); +void hidTerm(); +void hidInput(); +void hidOutput(bool play, bool spotL, bool spotR, bool backL, bool backR); + +extern int jogWheelsDelta[2]; +extern bool jogWheelsTouched[2]; +extern short crossFader; diff --git a/core/sdl/sdl.cpp b/core/sdl/sdl.cpp index 9027dfa25..a792ba1dd 100644 --- a/core/sdl/sdl.cpp +++ b/core/sdl/sdl.cpp @@ -31,6 +31,7 @@ #include "switch_gamepad.h" #endif #include +#include "hid.h" static SDL_Window* window = NULL; static u32 windowFlags; @@ -225,6 +226,7 @@ void input_sdl_init() SDL_InitSubSystem(SDL_INIT_HAPTIC); SDL_SetRelativeMouseMode(SDL_FALSE); + hidInit(); // Event::Start is called on a background thread, so we can't use it to change the window title (macOS) // However it's followed by Event::Resume which is fine. @@ -269,6 +271,7 @@ void input_sdl_quit() EventManager::unlisten(Event::Resume, emuEventCallback); SDLGamepad::closeAllGamepads(); SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC); + hidTerm(); } inline void SDLMouse::setAbsPos(int x, int y) @@ -293,6 +296,7 @@ static std::shared_ptr getMouse(u64 mouseId) void input_sdl_handle() { SDLGamepad::UpdateRumble(); + hidInput(); SDL_Event event; while (SDL_PollEvent(&event))