proof of concept: control crackin'dj with traktor S4

This commit is contained in:
Flyinghead 2024-09-24 09:53:20 +02:00
parent a46dc71156
commit 1d4106968e
5 changed files with 188 additions and 1 deletions

View File

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

View File

@ -30,6 +30,11 @@
#include <array>
#include <memory>
#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<int>(crossFader, -2030, 2030)) * 16;
#endif
u16 v;
if (player_axis >= 0 && player_axis < 4)
v = mapleInputState[player_num].fullAxes[player_axis] + 0x8000;

144
core/sdl/hid.cpp Normal file
View File

@ -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 <SDL.h>
#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

9
core/sdl/hid.h Normal file
View File

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

View File

@ -31,6 +31,7 @@
#include "switch_gamepad.h"
#endif
#include <unordered_map>
#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<SDLMouse> getMouse(u64 mouseId)
void input_sdl_handle()
{
SDLGamepad::UpdateRumble();
hidInput();
SDL_Event event;
while (SDL_PollEvent(&event))