mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 09:03:22 -05:00
A title can open AudioOut2 ports of type 0x3 (the controller's speaker) and 0x6 (its two voice-coil actuators) next to its bed and object ports. Both were folded into the TV mix, so the haptics came out of the PC speakers as low thumps and the controller got nothing. A USB DualSense is also a 4-channel 48 kHz sound card: channels 1-2 feed the speaker and headset jack, channels 3-4 the left and right actuators. When SDL lists such a card, a context with these ports opens it and queues a grain on it with every push: the speaker port on channels 1-2 (a mono port duplicated) and the vibration port's left and right channels on 3-4, at the port volumes and clamped. Both ports then leave the main mix. The card's queue gets the main queue's silence cushion when it runs dry and drops grains that run more than two grains beyond the main queue, so the controller keeps pace with the TV. An unplugged card is released (its ports return to the main mix) and looked for again every 2 s. Without a card nothing changes, and other port types and channel counts keep their routing. SDL2's PulseAudio backend labels a 4-channel stream FL FR FC LFE instead of quad, and the sound server then folds channels 3-4 into the speaker. Under that backend the card is opened as 5.1 with FC and LFE silent and the actuators on the back pair, which lands on the card's RL/RR.
117 lines
6.3 KiB
C++
117 lines
6.3 KiB
C++
#include "prx/libSceAudioOut/src/AudioOut2PadMix.hpp"
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <stdexcept>
|
|
|
|
static void Require(bool value, const char*) {
|
|
if (!value) std::abort();
|
|
}
|
|
|
|
template<typename TAction>
|
|
static bool Throws(TAction action) {
|
|
try {
|
|
action();
|
|
} catch (const std::invalid_argument&) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template<std::uint32_t Frames>
|
|
static std::array<float, Frames * AUDIO_OUT2_PAD_CHANNELS> Silence() {
|
|
return {};
|
|
}
|
|
|
|
static void Accumulate(AudioOut2Route route, const float* data, std::uint32_t channels, const float* volume, float* out, std::uint32_t frames) {
|
|
for (std::uint32_t frame = 0; frame < frames; frame++) AudioOut2AccumulatePadFrame(route, data + static_cast<std::size_t>(frame) * channels, channels, volume, out + static_cast<std::size_t>(frame) * AUDIO_OUT2_PAD_CHANNELS);
|
|
}
|
|
|
|
int main() {
|
|
Require(AudioOut2RouteForPort(0x3, 1) == AudioOut2Route::PadSpeaker, "mono type 0x3 is the pad speaker");
|
|
Require(AudioOut2RouteForPort(0x3, 2) == AudioOut2Route::PadSpeaker, "stereo type 0x3 is the pad speaker");
|
|
Require(AudioOut2RouteForPort(0x6, 2) == AudioOut2Route::PadVibration, "stereo type 0x6 is the vibration");
|
|
Require(AudioOut2RouteForPort(0x6, 1) == AudioOut2Route::PadVibration, "mono type 0x6 is the vibration");
|
|
Require(AudioOut2RouteForPort(0x6, 8) == AudioOut2Route::Main, "an 8-channel type 0x6 stays in the main mix");
|
|
Require(AudioOut2RouteForPort(0x3, 0) == AudioOut2Route::Main, "an undecoded format stays in the main mix");
|
|
for (std::uint16_t type : {0x0, 0x1, 0x2, 0x4, 0x5, 0x7, 0x100}) {
|
|
Require(AudioOut2RouteForPort(type, 1) == AudioOut2Route::Main, "other port types stay in the main mix");
|
|
Require(AudioOut2RouteForPort(type, 2) == AudioOut2Route::Main, "other port types stay in the main mix");
|
|
}
|
|
|
|
Require(AudioOut2IsPadAudioDevice("DualSense wireless controller (PS5) Direct DualSense Wireless Controller"), "pulse name");
|
|
Require(AudioOut2IsPadAudioDevice("DualSense Edge Wireless Controller, USB Audio"), "edge alsa name");
|
|
Require(!AudioOut2IsPadAudioDevice("AD102 High Definition Audio Controller Digital Stereo (HDMI)"), "hdmi name");
|
|
Require(!AudioOut2IsPadAudioDevice(nullptr), "null name");
|
|
|
|
constexpr std::uint32_t frames = 3;
|
|
const float unity[2] = {1.0f, 1.0f};
|
|
|
|
{
|
|
auto out = Silence<frames>();
|
|
const float speaker[frames] = {0.5f, -0.25f, 0.125f};
|
|
const float half[1] = {0.5f};
|
|
Accumulate(AudioOut2Route::PadSpeaker, speaker, 1, half, out.data(), frames);
|
|
for (std::uint32_t frame = 0; frame < frames; frame++) {
|
|
const float* pad = &out[frame * AUDIO_OUT2_PAD_CHANNELS];
|
|
Require(pad[0] == speaker[frame] * 0.5f && pad[1] == speaker[frame] * 0.5f, "mono speaker on channels 1-2");
|
|
Require(pad[2] == 0.0f && pad[3] == 0.0f, "speaker leaves the actuators alone");
|
|
}
|
|
}
|
|
|
|
{
|
|
auto out = Silence<frames>();
|
|
const float vibration[frames * 2] = {0.1f, 0.2f, 0.3f, 0.4f, -0.5f, -0.6f};
|
|
const float gains[2] = {1.0f, 0.5f};
|
|
Accumulate(AudioOut2Route::PadVibration, vibration, 2, gains, out.data(), frames);
|
|
for (std::uint32_t frame = 0; frame < frames; frame++) {
|
|
const float* pad = &out[frame * AUDIO_OUT2_PAD_CHANNELS];
|
|
Require(pad[0] == 0.0f && pad[1] == 0.0f, "vibration leaves the speaker alone");
|
|
Require(pad[2] == vibration[frame * 2] && pad[3] == vibration[frame * 2 + 1] * 0.5f, "vibration on channels 3-4");
|
|
}
|
|
}
|
|
|
|
{
|
|
auto out = Silence<frames>();
|
|
const float speaker[frames] = {0.75f, 0.75f, 0.0f};
|
|
const float vibration[frames * 2] = {0.9f, -0.9f, 0.0f, 0.0f, 0.0f, 0.0f};
|
|
Accumulate(AudioOut2Route::PadSpeaker, speaker, 1, unity, out.data(), frames);
|
|
Accumulate(AudioOut2Route::PadSpeaker, speaker, 1, unity, out.data(), frames);
|
|
Accumulate(AudioOut2Route::PadVibration, vibration, 2, unity, out.data(), frames);
|
|
Accumulate(AudioOut2Route::PadVibration, vibration, 2, unity, out.data(), frames);
|
|
AudioOut2FinishPadMix(out.data(), frames);
|
|
Require(out[0] == 1.0f && out[1] == 1.0f, "speaker sum clamps at full scale");
|
|
Require(out[2] == 1.0f && out[3] == -1.0f, "vibration sum clamps at full scale");
|
|
Require(out[4] == 1.0f && out[6] == 0.0f, "second frame");
|
|
for (std::uint32_t index = 8; index < frames * AUDIO_OUT2_PAD_CHANNELS; index++) Require(out[index] == 0.0f, "silent frame stays silent");
|
|
}
|
|
|
|
{
|
|
auto out = Silence<1>();
|
|
const float data[8] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
|
Require(Throws([&] { AudioOut2AccumulatePadFrame(AudioOut2Route::Main, data, 2, unity, out.data()); }), "a main-mix port is refused");
|
|
Require(Throws([&] { AudioOut2AccumulatePadFrame(AudioOut2Route::PadVibration, data, 8, unity, out.data()); }), "an 8-channel pad port is refused");
|
|
for (float sample : out) Require(sample == 0.0f, "a refused port mixes nothing");
|
|
}
|
|
|
|
{
|
|
const auto quad = AudioOut2PadLayoutForDriver("pipewire");
|
|
Require(quad.channels == 4 && quad.position[0] == 0 && quad.position[1] == 1 && quad.position[2] == 2 && quad.position[3] == 3, "quad layout");
|
|
Require(AudioOut2PadLayoutForDriver("alsa").channels == 4 && AudioOut2PadLayoutForDriver(nullptr).channels == 4, "alsa and unknown use quad");
|
|
const auto pulse = AudioOut2PadLayoutForDriver("pulseaudio");
|
|
Require(pulse.channels == 6 && pulse.position[0] == 0 && pulse.position[1] == 1 && pulse.position[2] == 4 && pulse.position[3] == 5, "pulse layout");
|
|
|
|
const float pad[2 * AUDIO_OUT2_PAD_CHANNELS] = {0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f};
|
|
float out[2 * AUDIO_OUT2_PAD_DEVICE_CHANNELS_MAX];
|
|
for (float& sample : out) sample = 9.0f;
|
|
AudioOut2WritePadFrames(pad, pulse, out, 2);
|
|
const float expected[2 * 6] = {0.1f, 0.2f, 0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 0.6f, 0.0f, 0.0f, 0.7f, 0.8f};
|
|
for (int index = 0; index < 12; index++) Require(out[index] == expected[index], "pulse frames: speaker front, centre and LFE silent, actuators back");
|
|
AudioOut2WritePadFrames(pad, quad, out, 2);
|
|
for (int index = 0; index < 8; index++) Require(out[index] == pad[index], "quad frames are the pad mix");
|
|
}
|
|
|
|
return 0;
|
|
}
|