mirror of
https://git.sr.ht/~thestr4ng3r/chiaki
synced 2025-08-14 18:57:07 -07:00
gui: Support for DualSense haptics and trigger effects
Haptics with PulseAudio does not seem to be working properly, so using Pipewire as a backend is recommended (and picked by default, if available via an SDL hint).
This commit is contained in:
parent
7a490b5aae
commit
c2f0932670
12 changed files with 343 additions and 71 deletions
|
@ -67,12 +67,12 @@ if(CHIAKI_ENABLE_CLI)
|
|||
endif()
|
||||
|
||||
target_link_libraries(chiaki Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Concurrent Qt5::Multimedia Qt5::OpenGL Qt5::Svg)
|
||||
target_link_libraries(chiaki SDL2::SDL2)
|
||||
if(APPLE)
|
||||
target_link_libraries(chiaki Qt5::MacExtras)
|
||||
target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_QT_MACEXTRAS)
|
||||
endif()
|
||||
if(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER)
|
||||
target_link_libraries(chiaki SDL2::SDL2)
|
||||
target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER)
|
||||
endif()
|
||||
if(CHIAKI_ENABLE_SETSU)
|
||||
|
|
|
@ -77,6 +77,7 @@ class Controller : public QObject
|
|||
int id;
|
||||
ChiakiOrientationTracker orientation_tracker;
|
||||
ChiakiControllerState state;
|
||||
bool is_dualsense;
|
||||
|
||||
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
|
||||
QMap<QPair<Sint64, Sint64>, uint8_t> touch_ids;
|
||||
|
@ -91,9 +92,43 @@ class Controller : public QObject
|
|||
QString GetName();
|
||||
ChiakiControllerState GetState();
|
||||
void SetRumble(uint8_t left, uint8_t right);
|
||||
void SetTriggerEffects(uint8_t type_left, const uint8_t *data_left, uint8_t type_right, const uint8_t *data_right);
|
||||
bool IsDualSense();
|
||||
|
||||
signals:
|
||||
void StateChanged();
|
||||
};
|
||||
|
||||
/* PS5 trigger effect documentation:
|
||||
https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes
|
||||
|
||||
Taken from SDL2, licensed under the zlib license,
|
||||
Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
|
||||
https://github.com/libsdl-org/SDL/blob/release-2.24.1/test/testgamecontroller.c#L263-L289
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
Uint8 ucEnableBits1; /* 0 */
|
||||
Uint8 ucEnableBits2; /* 1 */
|
||||
Uint8 ucRumbleRight; /* 2 */
|
||||
Uint8 ucRumbleLeft; /* 3 */
|
||||
Uint8 ucHeadphoneVolume; /* 4 */
|
||||
Uint8 ucSpeakerVolume; /* 5 */
|
||||
Uint8 ucMicrophoneVolume; /* 6 */
|
||||
Uint8 ucAudioEnableBits; /* 7 */
|
||||
Uint8 ucMicLightMode; /* 8 */
|
||||
Uint8 ucAudioMuteBits; /* 9 */
|
||||
Uint8 rgucRightTriggerEffect[11]; /* 10 */
|
||||
Uint8 rgucLeftTriggerEffect[11]; /* 21 */
|
||||
Uint8 rgucUnknown1[6]; /* 32 */
|
||||
Uint8 ucLedFlags; /* 38 */
|
||||
Uint8 rgucUnknown2[2]; /* 39 */
|
||||
Uint8 ucLedAnim; /* 41 */
|
||||
Uint8 ucLedBrightness; /* 42 */
|
||||
Uint8 ucPadLights; /* 43 */
|
||||
Uint8 ucLedRed; /* 44 */
|
||||
Uint8 ucLedGreen; /* 45 */
|
||||
Uint8 ucLedBlue; /* 46 */
|
||||
} DS5EffectsState_t;
|
||||
|
||||
#endif // CHIAKI_CONTROLLERMANAGER_H
|
||||
|
|
|
@ -63,6 +63,9 @@ class Settings : public QObject
|
|||
void SetLogVerbose(bool enabled) { settings.setValue("settings/log_verbose", enabled); }
|
||||
uint32_t GetLogLevelMask();
|
||||
|
||||
bool GetDualSenseEnabled() const { return settings.value("settings/dualsense_enabled", false).toBool(); }
|
||||
void SetDualSenseEnabled(bool enabled) { settings.setValue("settings/dualsense_enabled", enabled); }
|
||||
|
||||
ChiakiVideoResolutionPreset GetResolution() const;
|
||||
void SetResolution(ChiakiVideoResolutionPreset resolution);
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ class SettingsDialog : public QDialog
|
|||
|
||||
QCheckBox *log_verbose_check_box;
|
||||
QComboBox *disconnect_action_combo_box;
|
||||
QCheckBox *dualsense_check_box;
|
||||
|
||||
QComboBox *resolution_combo_box;
|
||||
QComboBox *fps_combo_box;
|
||||
|
@ -37,6 +38,7 @@ class SettingsDialog : public QDialog
|
|||
|
||||
private slots:
|
||||
void LogVerboseChanged();
|
||||
void DualSenseChanged();
|
||||
void DisconnectActionSelected();
|
||||
|
||||
void ResolutionSelected();
|
||||
|
|
|
@ -56,6 +56,7 @@ struct StreamSessionConnectInfo
|
|||
bool fullscreen;
|
||||
TransformMode transform_mode;
|
||||
bool enable_keyboard;
|
||||
bool enable_dualsense;
|
||||
|
||||
StreamSessionConnectInfo(
|
||||
Settings *settings,
|
||||
|
@ -101,17 +102,23 @@ class StreamSession : public QObject
|
|||
unsigned int audio_buffer_size;
|
||||
QAudioOutput *audio_output;
|
||||
QIODevice *audio_io;
|
||||
SDL_AudioDeviceID haptics_output;
|
||||
uint8_t *haptics_resampler_buf;
|
||||
|
||||
QMap<Qt::Key, int> key_map;
|
||||
|
||||
void PushAudioFrame(int16_t *buf, size_t samples_count);
|
||||
void PushHapticsFrame(uint8_t *buf, size_t buf_size);
|
||||
#if CHIAKI_GUI_ENABLE_SETSU
|
||||
void HandleSetsuEvent(SetsuEvent *event);
|
||||
#endif
|
||||
|
||||
private slots:
|
||||
void InitAudio(unsigned int channels, unsigned int rate);
|
||||
void InitHaptics();
|
||||
void Event(ChiakiEvent *event);
|
||||
void DisconnectHaptics();
|
||||
void ConnectHaptics();
|
||||
|
||||
public:
|
||||
explicit StreamSession(const StreamSessionConnectInfo &connect_info, QObject *parent = nullptr);
|
||||
|
|
|
@ -68,6 +68,12 @@ static QSet<QString> chiaki_motion_controller_guids({
|
|||
"030000008f0e00001431000000000000",
|
||||
});
|
||||
|
||||
static QSet<QPair<int16_t, int16_t>> chiaki_dualsense_controller_ids({
|
||||
// in format (vendor id, product id)
|
||||
QPair<int16_t, int16_t>(0x054c, 0x0ce6), // DualSense controller
|
||||
QPair<int16_t, int16_t>(0x054c, 0x0df2), // DualSense Edge controller
|
||||
});
|
||||
|
||||
static ControllerManager *instance = nullptr;
|
||||
|
||||
#define UPDATE_INTERVAL_MS 4
|
||||
|
@ -84,6 +90,15 @@ ControllerManager::ControllerManager(QObject *parent)
|
|||
{
|
||||
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
|
||||
SDL_SetMainReady();
|
||||
#ifdef SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
#endif
|
||||
#ifdef SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
#endif
|
||||
#ifdef SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
#endif
|
||||
if(SDL_Init(SDL_INIT_GAMECONTROLLER) < 0)
|
||||
{
|
||||
const char *err = SDL_GetError();
|
||||
|
@ -225,7 +240,8 @@ void ControllerManager::ControllerClosed(Controller *controller)
|
|||
open_controllers.remove(controller->GetDeviceID());
|
||||
}
|
||||
|
||||
Controller::Controller(int device_id, ControllerManager *manager) : QObject(manager)
|
||||
Controller::Controller(int device_id, ControllerManager *manager)
|
||||
: QObject(manager), is_dualsense(false)
|
||||
{
|
||||
this->id = device_id;
|
||||
this->manager = manager;
|
||||
|
@ -244,8 +260,10 @@ Controller::Controller(int device_id, ControllerManager *manager) : QObject(mana
|
|||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
|
||||
if(SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO))
|
||||
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
|
||||
break;
|
||||
#endif
|
||||
auto controller_id = QPair<int16_t, int16_t>(SDL_GameControllerGetVendor(controller), SDL_GameControllerGetProduct(controller));
|
||||
is_dualsense = chiaki_dualsense_controller_ids.contains(controller_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -255,7 +273,12 @@ Controller::~Controller()
|
|||
{
|
||||
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
|
||||
if(controller)
|
||||
{
|
||||
// Clear trigger effects, SDL doesn't do it automatically
|
||||
const uint8_t clear_effect[10] = { 0 };
|
||||
this->SetTriggerEffects(0x05, clear_effect, 0x05, clear_effect);
|
||||
SDL_GameControllerClose(controller);
|
||||
}
|
||||
#endif
|
||||
manager->ControllerClosed(this);
|
||||
}
|
||||
|
@ -487,3 +510,28 @@ void Controller::SetRumble(uint8_t left, uint8_t right)
|
|||
SDL_GameControllerRumble(controller, (uint16_t)left << 8, (uint16_t)right << 8, 5000);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::SetTriggerEffects(uint8_t type_left, const uint8_t *data_left, uint8_t type_right, const uint8_t *data_right)
|
||||
{
|
||||
#if defined(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) && SDL_VERSION_ATLEAST(2, 0, 16)
|
||||
if(!is_dualsense || !controller)
|
||||
return;
|
||||
DS5EffectsState_t state;
|
||||
SDL_zero(state);
|
||||
state.ucEnableBits1 |= (0x04 /* left trigger */ | 0x08 /* right trigger */);
|
||||
state.rgucLeftTriggerEffect[0] = type_left;
|
||||
SDL_memcpy(state.rgucLeftTriggerEffect + 1, data_left, 10);
|
||||
state.rgucRightTriggerEffect[0] = type_right;
|
||||
SDL_memcpy(state.rgucRightTriggerEffect + 1, data_right, 10);
|
||||
SDL_GameControllerSendEffect(controller, &state, sizeof(state));
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Controller::IsDualSense()
|
||||
{
|
||||
#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER
|
||||
if(!controller)
|
||||
return false;
|
||||
return is_dualsense;
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -69,6 +69,11 @@ SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent) : QDialog(pa
|
|||
log_verbose_check_box->setChecked(settings->GetLogVerbose());
|
||||
connect(log_verbose_check_box, &QCheckBox::stateChanged, this, &SettingsDialog::LogVerboseChanged);
|
||||
|
||||
dualsense_check_box = new QCheckBox(this);
|
||||
general_layout->addRow(tr("Extended DualSense Support:\nEnable haptics and adaptive triggers\nfor attached DualSense controllers.\nThis is currently experimental."), dualsense_check_box);
|
||||
dualsense_check_box->setChecked(settings->GetDualSenseEnabled());
|
||||
connect(dualsense_check_box, &QCheckBox::stateChanged, this, &SettingsDialog::DualSenseChanged);
|
||||
|
||||
auto log_directory_label = new QLineEdit(GetLogBaseDir(), this);
|
||||
log_directory_label->setReadOnly(true);
|
||||
general_layout->addRow(tr("Log Directory:"), log_directory_label);
|
||||
|
@ -322,6 +327,11 @@ void SettingsDialog::LogVerboseChanged()
|
|||
settings->SetLogVerbose(log_verbose_check_box->isChecked());
|
||||
}
|
||||
|
||||
void SettingsDialog::DualSenseChanged()
|
||||
{
|
||||
settings->SetDualSenseEnabled(dualsense_check_box->isChecked());
|
||||
}
|
||||
|
||||
void SettingsDialog::FPSSelected()
|
||||
{
|
||||
settings->SetFPS((ChiakiVideoFPSPreset)fps_combo_box->currentData().toInt());
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
|
||||
#define SETSU_UPDATE_INTERVAL_MS 4
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#define DUALSENSE_AUDIO_DEVICE_NEEDLE "DualSense"
|
||||
#else
|
||||
#define DUALSENSE_AUDIO_DEVICE_NEEDLE "Wireless Controller"
|
||||
#endif
|
||||
|
||||
StreamSessionConnectInfo::StreamSessionConnectInfo(
|
||||
Settings *settings,
|
||||
ChiakiTarget target,
|
||||
|
@ -39,10 +45,12 @@ StreamSessionConnectInfo::StreamSessionConnectInfo(
|
|||
this->fullscreen = fullscreen;
|
||||
this->transform_mode = transform_mode;
|
||||
this->enable_keyboard = false; // TODO: from settings
|
||||
this->enable_dualsense = settings->GetDualSenseEnabled();
|
||||
}
|
||||
|
||||
static void AudioSettingsCb(uint32_t channels, uint32_t rate, void *user);
|
||||
static void AudioFrameCb(int16_t *buf, size_t samples_count, void *user);
|
||||
static void HapticsFrameCb(uint8_t *buf, size_t buf_size, void *user);
|
||||
static void EventCb(ChiakiEvent *event, void *user);
|
||||
#if CHIAKI_GUI_ENABLE_SETSU
|
||||
static void SessionSetsuCb(SetsuEvent *event, void *user);
|
||||
|
@ -57,7 +65,9 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje
|
|||
pi_decoder(nullptr),
|
||||
#endif
|
||||
audio_output(nullptr),
|
||||
audio_io(nullptr)
|
||||
audio_io(nullptr),
|
||||
haptics_output(0),
|
||||
haptics_resampler_buf(nullptr)
|
||||
{
|
||||
connected = false;
|
||||
ChiakiErrorCode err;
|
||||
|
@ -116,6 +126,7 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje
|
|||
chiaki_connect_info.video_profile = connect_info.video_profile;
|
||||
chiaki_connect_info.video_profile_auto_downgrade = true;
|
||||
chiaki_connect_info.enable_keyboard = false;
|
||||
chiaki_connect_info.enable_dualsense = connect_info.enable_dualsense;
|
||||
|
||||
#if CHIAKI_LIB_ENABLE_PI_DECODER
|
||||
if(connect_info.decoder == Decoder::Pi && chiaki_connect_info.video_profile.codec != CHIAKI_CODEC_H264)
|
||||
|
@ -144,6 +155,14 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje
|
|||
chiaki_opus_decoder_get_sink(&opus_decoder, &audio_sink);
|
||||
chiaki_session_set_audio_sink(&session, &audio_sink);
|
||||
|
||||
if(connect_info.enable_dualsense)
|
||||
{
|
||||
ChiakiAudioSink haptics_sink;
|
||||
haptics_sink.user = this;
|
||||
haptics_sink.frame_cb = HapticsFrameCb;
|
||||
chiaki_session_set_haptics_sink(&session, &haptics_sink);
|
||||
}
|
||||
|
||||
#if CHIAKI_LIB_ENABLE_PI_DECODER
|
||||
if(pi_decoder)
|
||||
chiaki_session_set_video_sample_cb(&session, chiaki_pi_decoder_video_sample_cb, pi_decoder);
|
||||
|
@ -181,6 +200,10 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje
|
|||
#endif
|
||||
|
||||
key_map = connect_info.key_map;
|
||||
if(connect_info.enable_dualsense)
|
||||
{
|
||||
InitHaptics();
|
||||
}
|
||||
UpdateGamepads();
|
||||
}
|
||||
|
||||
|
@ -208,6 +231,16 @@ StreamSession::~StreamSession()
|
|||
chiaki_ffmpeg_decoder_fini(ffmpeg_decoder);
|
||||
delete ffmpeg_decoder;
|
||||
}
|
||||
if(haptics_output > 0)
|
||||
{
|
||||
SDL_CloseAudioDevice(haptics_output);
|
||||
haptics_output = 0;
|
||||
}
|
||||
if(haptics_resampler_buf)
|
||||
{
|
||||
free(haptics_resampler_buf);
|
||||
haptics_resampler_buf = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void StreamSession::Start()
|
||||
|
@ -312,6 +345,8 @@ void StreamSession::UpdateGamepads()
|
|||
{
|
||||
CHIAKI_LOGI(log.GetChiakiLog(), "Controller %d disconnected", controller->GetDeviceID());
|
||||
controllers.remove(controller_id);
|
||||
if(controller->IsDualSense())
|
||||
DisconnectHaptics();
|
||||
delete controller;
|
||||
}
|
||||
}
|
||||
|
@ -330,6 +365,11 @@ void StreamSession::UpdateGamepads()
|
|||
CHIAKI_LOGI(log.GetChiakiLog(), "Controller %d opened: \"%s\"", controller_id, controller->GetName().toLocal8Bit().constData());
|
||||
connect(controller, &Controller::StateChanged, this, &StreamSession::SendFeedbackState);
|
||||
controllers[controller_id] = controller;
|
||||
if(controller->IsDualSense())
|
||||
{
|
||||
// Connect haptics audio device with a delay to give the sound system time to set up
|
||||
QTimer::singleShot(1000, this, &StreamSession::ConnectHaptics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,6 +428,82 @@ void StreamSession::InitAudio(unsigned int channels, unsigned int rate)
|
|||
channels, rate, audio_output->bufferSize());
|
||||
}
|
||||
|
||||
void StreamSession::InitHaptics()
|
||||
{
|
||||
haptics_output = 0;
|
||||
haptics_resampler_buf = nullptr;
|
||||
#ifdef Q_OS_LINUX
|
||||
// Haptics work most reliably with Pipewire, so try to use that if available
|
||||
SDL_SetHint("SDL_AUDIODRIVER", "pipewire");
|
||||
#endif
|
||||
|
||||
if(SDL_Init(SDL_INIT_AUDIO) < 0)
|
||||
{
|
||||
CHIAKI_LOGE(log.GetChiakiLog(), "Could not initialize SDL Audio for haptics output: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
if(!strstr(SDL_GetCurrentAudioDriver(), "pipewire"))
|
||||
{
|
||||
CHIAKI_LOGW(
|
||||
log.GetChiakiLog(),
|
||||
"Haptics output is not using Pipewire, this may not work reliably. (was: '%s')",
|
||||
SDL_GetCurrentAudioDriver());
|
||||
}
|
||||
#endif
|
||||
|
||||
SDL_AudioCVT cvt;
|
||||
SDL_BuildAudioCVT(&cvt, AUDIO_S16LSB, 4, 3000, AUDIO_S16LSB, 4, 48000);
|
||||
cvt.len = 240; // 10 16bit stereo samples
|
||||
haptics_resampler_buf = (uint8_t*) calloc(cvt.len * cvt.len_mult, sizeof(uint8_t));
|
||||
}
|
||||
|
||||
void StreamSession::DisconnectHaptics()
|
||||
{
|
||||
if(this->haptics_output > 0)
|
||||
{
|
||||
SDL_CloseAudioDevice(haptics_output);
|
||||
this->haptics_output = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void StreamSession::ConnectHaptics()
|
||||
{
|
||||
if(this->haptics_output > 0)
|
||||
{
|
||||
CHIAKI_LOGW(this->log.GetChiakiLog(), "Haptics already connected to an attached DualSense controller, ignoring additional controllers.");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_AudioSpec want, have;
|
||||
SDL_zero(want);
|
||||
want.freq = 48000;
|
||||
want.format = AUDIO_S16LSB;
|
||||
want.channels = 4;
|
||||
want.samples = 480; // 10ms buffer
|
||||
want.callback = NULL;
|
||||
|
||||
const char *device_name = nullptr;
|
||||
for(int i=0; i < SDL_GetNumAudioDevices(0); i++)
|
||||
{
|
||||
device_name = SDL_GetAudioDeviceName(i, 0);
|
||||
if(!device_name || !strstr(device_name, DUALSENSE_AUDIO_DEVICE_NEEDLE))
|
||||
continue;
|
||||
haptics_output = SDL_OpenAudioDevice(device_name, 0, &want, &have, 0);
|
||||
if(haptics_output == 0)
|
||||
{
|
||||
CHIAKI_LOGE(log.GetChiakiLog(), "Could not open SDL Audio Device %s for haptics output: %s", device_name, SDL_GetError());
|
||||
continue;
|
||||
}
|
||||
SDL_PauseAudioDevice(haptics_output, 0);
|
||||
CHIAKI_LOGI(log.GetChiakiLog(), "Haptics Audio Device '%s' opened with %d channels @ %d Hz, buffer size %u (driver=%s)", device_name, have.channels, have.freq, have.size, SDL_GetCurrentAudioDriver());
|
||||
return;
|
||||
}
|
||||
CHIAKI_LOGW(log.GetChiakiLog(), "DualSense features were enabled and a DualSense is connected, but could not find the DualSense audio device!");
|
||||
return;
|
||||
}
|
||||
|
||||
void StreamSession::PushAudioFrame(int16_t *buf, size_t samples_count)
|
||||
{
|
||||
if(!audio_io)
|
||||
|
@ -395,6 +511,35 @@ void StreamSession::PushAudioFrame(int16_t *buf, size_t samples_count)
|
|||
audio_io->write((const char *)buf, static_cast<qint64>(samples_count * 2 * 2));
|
||||
}
|
||||
|
||||
void StreamSession::PushHapticsFrame(uint8_t *buf, size_t buf_size)
|
||||
{
|
||||
if(haptics_output == 0)
|
||||
return;
|
||||
SDL_AudioCVT cvt;
|
||||
// Haptics samples are coming in at 3KHZ, but the DualSense expects 48KHZ
|
||||
SDL_BuildAudioCVT(&cvt, AUDIO_S16LSB, 4, 3000, AUDIO_S16LSB, 4, 48000);
|
||||
cvt.len = buf_size * 2;
|
||||
cvt.buf = haptics_resampler_buf;
|
||||
// Remix to 4 channels
|
||||
for (int i=0; i < buf_size; i+=4)
|
||||
{
|
||||
SDL_memset(haptics_resampler_buf + i * 2, 0, 4);
|
||||
SDL_memcpy(haptics_resampler_buf + (i * 2) + 4, buf + i, 4);
|
||||
}
|
||||
// Resample to 48kHZ
|
||||
if(SDL_ConvertAudio(&cvt) != 0)
|
||||
{
|
||||
CHIAKI_LOGE(log.GetChiakiLog(), "Failed to resample haptics audio: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
if(SDL_QueueAudio(haptics_output, cvt.buf, cvt.len_cvt) < 0)
|
||||
{
|
||||
CHIAKI_LOGE(log.GetChiakiLog(), "Failed to submit haptics audio to device: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void StreamSession::Event(ChiakiEvent *event)
|
||||
{
|
||||
switch(event->type)
|
||||
|
@ -418,6 +563,19 @@ void StreamSession::Event(ChiakiEvent *event)
|
|||
});
|
||||
break;
|
||||
}
|
||||
case CHIAKI_EVENT_TRIGGER_EFFECTS: {
|
||||
uint8_t type_left = event->trigger_effects.type_left;
|
||||
uint8_t data_left[10];
|
||||
memcpy(data_left, event->trigger_effects.left, 10);
|
||||
uint8_t data_right[10];
|
||||
memcpy(data_right, event->trigger_effects.right, 10);
|
||||
uint8_t type_right = event->trigger_effects.type_right;
|
||||
QMetaObject::invokeMethod(this, [this, type_left, data_left, type_right, data_right]() {
|
||||
for(auto controller : controllers)
|
||||
controller->SetTriggerEffects(type_left, data_left, type_right, data_right);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -544,6 +702,7 @@ class StreamSessionPrivate
|
|||
}
|
||||
|
||||
static void PushAudioFrame(StreamSession *session, int16_t *buf, size_t samples_count) { session->PushAudioFrame(buf, samples_count); }
|
||||
static void PushHapticsFrame(StreamSession *session, uint8_t *buf, size_t buf_size) { session->PushHapticsFrame(buf, buf_size); }
|
||||
static void Event(StreamSession *session, ChiakiEvent *event) { session->Event(event); }
|
||||
#if CHIAKI_GUI_ENABLE_SETSU
|
||||
static void HandleSetsuEvent(StreamSession *session, SetsuEvent *event) { session->HandleSetsuEvent(event); }
|
||||
|
@ -563,6 +722,12 @@ static void AudioFrameCb(int16_t *buf, size_t samples_count, void *user)
|
|||
StreamSessionPrivate::PushAudioFrame(session, buf, samples_count);
|
||||
}
|
||||
|
||||
static void HapticsFrameCb(uint8_t *buf, size_t buf_size, void *user)
|
||||
{
|
||||
auto session = reinterpret_cast<StreamSession *>(user);
|
||||
StreamSessionPrivate::PushHapticsFrame(session, buf, buf_size);
|
||||
}
|
||||
|
||||
static void EventCb(ChiakiEvent *event, void *user)
|
||||
{
|
||||
auto session = reinterpret_cast<StreamSession *>(user);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue