Merge pull request #4598 from Lorkydey/feat/ngs2-listener-transform
feat(libSceNgs2): support default listener calculation
This commit is contained in:
commit
e55b881f0f
|
|
@ -1,9 +1,30 @@
|
|||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "Ngs2Internal.hpp"
|
||||
|
||||
static constexpr float DEFAULT_SOUND_SPEED = 343.0f;
|
||||
|
||||
int Ngs2GeomCalcListener(const Ngs2GeomListenerParam* param, Ngs2GeomListenerWork* outWork, std::uint32_t flags) {
|
||||
if (param == nullptr) APS5_INVALID_ARG_EX;
|
||||
if (outWork == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
|
||||
if (flags != 0) throw std::runtime_error("NGS2: listener coordinate flags are unsupported");
|
||||
if (param->position.x != 0.0f || param->position.y != 0.0f || param->position.z != 0.0f)
|
||||
throw std::runtime_error("NGS2: listener translation is unsupported");
|
||||
if (param->orient_front.x != 0.0f || param->orient_front.y != 0.0f || param->orient_front.z != 1.0f ||
|
||||
param->orient_up.x != 0.0f || param->orient_up.y != 1.0f || param->orient_up.z != 0.0f)
|
||||
throw std::runtime_error("NGS2: non-default listener orientation is unsupported");
|
||||
if (!std::isfinite(param->velocity.x) || !std::isfinite(param->velocity.y) || !std::isfinite(param->velocity.z)) APS5_INVALID_ARG_EX;
|
||||
if (!std::isfinite(param->sound_speed) || param->sound_speed <= 0.0f) APS5_INVALID_ARG_EX;
|
||||
Ngs2GeomListenerWork work{};
|
||||
for (std::size_t i = 0; i < 4; i++) work.matrix[i][i] = 1.0f;
|
||||
work.velocity = param->velocity;
|
||||
work.sound_speed = param->sound_speed;
|
||||
*outWork = work;
|
||||
return SCE_NGS2_OK;
|
||||
}
|
||||
|
||||
int Ngs2GeomResetListenerParam(Ngs2GeomListenerParam* out_listener_param) {
|
||||
if (out_listener_param == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
|
||||
*out_listener_param = {};
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ float Ngs2DefaultLevel(std::uint32_t sourceChannels, std::uint32_t source, std::
|
|||
int Ngs2PanInit(Ngs2PanWork* work, const float* speaker_angles, float unit_angle, uint32_t num_speakers);
|
||||
int Ngs2PanGetVolumeMatrix(Ngs2PanWork* work, const Ngs2PanParam* params, uint32_t num_params, uint32_t matrix_format, float* out_volume_matrix);
|
||||
int Ngs2GeomResetListenerParam(Ngs2GeomListenerParam* out_listener_param);
|
||||
int Ngs2GeomCalcListener(const Ngs2GeomListenerParam* param, Ngs2GeomListenerWork* outWork, std::uint32_t flags);
|
||||
int Ngs2GeomResetSourceParam(Ngs2GeomSourceParam* out_source_param);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -17,14 +17,6 @@ int APS5_VABI sceNgs2GeomApply(const Ngs2GeomListenerWork* listener, const Ngs2G
|
|||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNgs2GeomCalcListener(const Ngs2GeomListenerParam* param, Ngs2GeomListenerWork* out_work, uint32_t flags) {
|
||||
(void)param;
|
||||
(void)out_work;
|
||||
(void)flags;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNgs2ReportRegisterHandler(uint32_t report_type, void* handler, uintptr_t user_data, Ngs2Handle* out_handle) {
|
||||
(void)report_type;
|
||||
(void)handler;
|
||||
|
|
|
|||
|
|
@ -490,6 +490,10 @@ int APS5_VABI sceNgs2GeomResetListenerParam(Ngs2GeomListenerParam* out_listener_
|
|||
return Ngs2GeomResetListenerParam(out_listener_param);
|
||||
}
|
||||
|
||||
int APS5_VABI sceNgs2GeomCalcListener(const Ngs2GeomListenerParam* param, Ngs2GeomListenerWork* outWork, std::uint32_t flags) {
|
||||
return Ngs2GeomCalcListener(param, outWork, flags);
|
||||
}
|
||||
|
||||
int APS5_VABI sceNgs2GeomResetSourceParam(Ngs2GeomSourceParam* out_source_param) {
|
||||
return Ngs2GeomResetSourceParam(out_source_param);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
static constexpr float SoundSpeed = 343.0f;
|
||||
|
||||
|
|
@ -49,9 +52,114 @@ static void TestResetSourceParam() {
|
|||
Require(sceNgs2GeomResetSourceParam(nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
|
||||
}
|
||||
|
||||
static Ngs2GeomListenerParam Listener() {
|
||||
Ngs2GeomListenerParam param{};
|
||||
Require(sceNgs2GeomResetListenerParam(¶m) == SCE_NGS2_OK);
|
||||
return param;
|
||||
}
|
||||
|
||||
static void RequireMatrix(const Ngs2GeomListenerWork& work, const float (&expected)[4][4]) {
|
||||
for (std::size_t row = 0; row < 4; row++) {
|
||||
for (std::size_t column = 0; column < 4; column++) Require(work.matrix[row][column] == expected[row][column]);
|
||||
}
|
||||
}
|
||||
|
||||
static void TestDefaultListener() {
|
||||
auto param = Listener();
|
||||
Ngs2GeomListenerWork work;
|
||||
std::memset(&work, 0x5a, sizeof(work));
|
||||
Require(sceNgs2GeomCalcListener(¶m, &work, 0) == SCE_NGS2_OK);
|
||||
RequireMatrix(work, {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}});
|
||||
Require(work.velocity.x == 0.0f && work.velocity.y == 0.0f && work.velocity.z == 0.0f);
|
||||
Require(work.sound_speed == SoundSpeed && work.coordinate == 0);
|
||||
Require(work.reserved[0] == 0 && work.reserved[1] == 0 && work.reserved[2] == 0);
|
||||
|
||||
param.velocity = {2.0f, -3.0f, 4.0f};
|
||||
param.sound_speed = 1500.0f;
|
||||
std::memset(&work, 0x5a, sizeof(work));
|
||||
Require(sceNgs2GeomCalcListener(¶m, &work, 0) == SCE_NGS2_OK);
|
||||
RequireMatrix(work, {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}});
|
||||
Require(work.velocity.x == 2.0f && work.velocity.y == -3.0f && work.velocity.z == 4.0f);
|
||||
Require(work.sound_speed == 1500.0f && work.coordinate == 0);
|
||||
Require(work.reserved[0] == 0 && work.reserved[1] == 0 && work.reserved[2] == 0);
|
||||
|
||||
param.position = {-0.0f, -0.0f, -0.0f};
|
||||
param.orient_front = {-0.0f, -0.0f, 1.0f};
|
||||
param.orient_up = {-0.0f, 1.0f, -0.0f};
|
||||
Require(sceNgs2GeomCalcListener(¶m, &work, 0) == SCE_NGS2_OK);
|
||||
RequireMatrix(work, {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}});
|
||||
}
|
||||
|
||||
static void RejectListener(const Ngs2GeomListenerParam* param, std::uint32_t flags) {
|
||||
Ngs2GeomListenerWork work;
|
||||
std::memset(&work, 0x5a, sizeof(work));
|
||||
unsigned char before[sizeof(work)];
|
||||
std::memcpy(before, &work, sizeof(work));
|
||||
bool rejected = false;
|
||||
try {
|
||||
sceNgs2GeomCalcListener(param, &work, flags);
|
||||
} catch (const std::exception&) {
|
||||
rejected = true;
|
||||
}
|
||||
Require(rejected);
|
||||
Require(std::memcmp(before, &work, sizeof(work)) == 0);
|
||||
}
|
||||
|
||||
static void TestUnsupportedListener() {
|
||||
auto param = Listener();
|
||||
for (const auto flags : {1u, 2u, 0xffffffffu}) RejectListener(¶m, flags);
|
||||
for (const auto vector : {&Ngs2GeomListenerParam::position, &Ngs2GeomListenerParam::orient_front, &Ngs2GeomListenerParam::orient_up}) {
|
||||
for (const auto component : {&Ngs2GeomVector::x, &Ngs2GeomVector::y, &Ngs2GeomVector::z}) {
|
||||
for (const float value : {-1.0f, 0.0f, 1.0f, 2.0f, std::numeric_limits<float>::min(), std::numeric_limits<float>::max()}) {
|
||||
param = Listener();
|
||||
auto& field = (param.*vector).*component;
|
||||
if (field == value) continue;
|
||||
field = value;
|
||||
RejectListener(¶m, 0);
|
||||
}
|
||||
param = Listener();
|
||||
auto& field = (param.*vector).*component;
|
||||
field = std::nextafter(field, 2.0f);
|
||||
RejectListener(¶m, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void TestInvalidListener() {
|
||||
auto param = Listener();
|
||||
Require(sceNgs2GeomCalcListener(¶m, nullptr, 0) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
|
||||
RejectListener(nullptr, 0);
|
||||
for (const float value : {0.0f, -1.0f, std::numeric_limits<float>::infinity(), std::numeric_limits<float>::quiet_NaN()}) {
|
||||
param.sound_speed = value;
|
||||
RejectListener(¶m, 0);
|
||||
}
|
||||
param = Listener();
|
||||
param.orient_front = {};
|
||||
RejectListener(¶m, 0);
|
||||
param = Listener();
|
||||
param.orient_up = param.orient_front;
|
||||
RejectListener(¶m, 0);
|
||||
param = Listener();
|
||||
param.orient_up = {};
|
||||
RejectListener(¶m, 0);
|
||||
for (const auto vector : {&Ngs2GeomListenerParam::position, &Ngs2GeomListenerParam::orient_front,
|
||||
&Ngs2GeomListenerParam::orient_up, &Ngs2GeomListenerParam::velocity}) {
|
||||
for (const auto component : {&Ngs2GeomVector::x, &Ngs2GeomVector::y, &Ngs2GeomVector::z}) {
|
||||
for (const float value : {std::numeric_limits<float>::infinity(), -std::numeric_limits<float>::infinity(), std::numeric_limits<float>::quiet_NaN()}) {
|
||||
param = Listener();
|
||||
(param.*vector).*component = value;
|
||||
RejectListener(¶m, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
TestLayouts();
|
||||
TestResetListenerParam();
|
||||
TestResetSourceParam();
|
||||
TestDefaultListener();
|
||||
TestUnsupportedListener();
|
||||
TestInvalidListener();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ int APS5_VABI sceNgs2VoiceGetStateFlags(uintptr_t, uint32_t*);
|
|||
int APS5_VABI sceNgs2VoiceGetPortInfo(uintptr_t, uint32_t, Ngs2VoicePortInfo*, size_t);
|
||||
int APS5_VABI sceNgs2VoiceQueryInfo(uintptr_t, uint32_t, void*, size_t);
|
||||
int APS5_VABI sceNgs2GeomResetListenerParam(Ngs2GeomListenerParam*);
|
||||
int APS5_VABI sceNgs2GeomCalcListener(const Ngs2GeomListenerParam*, Ngs2GeomListenerWork*, std::uint32_t);
|
||||
int APS5_VABI sceNgs2GeomResetSourceParam(Ngs2GeomSourceParam*);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,4 +22,5 @@
|
|||
- [sceNgs2ParseWaveformFile](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - the signature `(path, offset, info)` is taken from the call site of PPSA21402, which sets only three argument registers; the file is read from `offset` to its end, the data and block offsets are taken to be relative to the start of the file, not of `offset`, and an unreadable file throws. The audio unit fields of a 16-bit PCM waveform are assumed to be one sample frame per unit and per frame; `INVALID_WAVEFORM_DATA` for a `data` chunk that runs past the buffer or ends inside a frame is assumed, and looped PCM and other PCM formats throw
|
||||
- [sceNgs2VoiceControl sampler filter](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - param `0x1000000a`, its layout and meaning follow KytyPS5 only: location 1 type 1 is an RBJ low-pass biquad at the system sample rate after resampling, scaled by `level`, a set `channel_mask` bit bypasses that channel, type 0 turns the filter off. Other locations and types throw. The voice keeps playing a filter tail after its last block until the history is zero, with outputs below 1e-20 flushed to zero (a KytyPS5 choice); the console's tail length is unknown
|
||||
- [sceNgs2PanInit / sceNgs2PanGetVolumeMatrix](../../../core/libs/prx/libSceNgs2.native/src/Pan.cpp) (libSceNgs2.native) - follow the PS4 firmware 11.00 `libSceNgs2.sprx`, read and checked against its output for 2, 4, 5 and 7 speakers into 1, 2, 6 and 8 channels; not yet confirmed on the PS5 library. Speakers are taken in the order L R, L R Ls Rs, L R C Ls Rs or L R C Ls Rs Lb Rb and kept in a fixed circular order, not sorted; without angles the defaults are 90/270, 40/110/250/320, 0/40/110/250/320 and 0/40/110/150/210/250/320 degrees. As in that library, defaults converted to radians keep the wrap-around entry in degrees, so angles past the last speaker pan almost fully to it. Error codes are not known for the PS5, so invalid arguments throw, including a speaker angle equal to the unit angle, on which that library loops forever. Angles so large that adding or subtracting a turn leaves them unchanged throw for the same reason
|
||||
- [sceNgs2GeomResetListenerParam, sceNgs2GeomResetSourceParam](../../../core/libs/prx/libSceNgs2.native/src/Geom.cpp) (libSceNgs2.native) - the structures (12, 16, 16, 60, 96, 108, 32 and 308 bytes) come from [KytyPS5](https://github.com/KytyPS5/KytyPS5)'s `ngs2.cpp` and match [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_geom.h` field for field, but only KytyPS5 gives the behaviour - shadPS4 logs and returns success without writing the output. The defaults (orient front +Z, orient up +Y, sound speed 343, cone 1/360/1/360, rolloff 0/1000000/1/1, doppler factor, full-band, LFE and maximum level 1, two speakers and matrix format 2) are unverified on a PS5 title; KytyPS5 and iStark/PS5PCEM agree on them. PPSA15552 (Dead Cells) calls `sceNgs2GeomResetSourceParam` after creating its racks. A null output address returns `INVALID_OUT_ADDRESS` as elsewhere in the library rather than throwing as KytyPS5 does. `sceNgs2GeomCalcListener` and `sceNgs2GeomApply` stay unimplemented
|
||||
- [sceNgs2GeomResetListenerParam, sceNgs2GeomResetSourceParam](../../../core/libs/prx/libSceNgs2.native/src/Geom.cpp) (libSceNgs2.native) - the structures (12, 16, 16, 60, 96, 108, 32 and 308 bytes) come from [KytyPS5](https://github.com/KytyPS5/KytyPS5)'s `ngs2.cpp` and match [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_geom.h` field for field, but only KytyPS5 gives the behaviour - shadPS4 logs and returns success without writing the output. The defaults (orient front +Z, orient up +Y, sound speed 343, cone 1/360/1/360, rolloff 0/1000000/1/1, doppler factor, full-band, LFE and maximum level 1, two speakers and matrix format 2) are unverified on a PS5 title; KytyPS5 and iStark/PS5PCEM agree on them. PPSA15552 (Dead Cells) calls `sceNgs2GeomResetSourceParam` after creating its racks. A null output address returns `INVALID_OUT_ADDRESS` as elsewhere in the library rather than throwing as KytyPS5 does. `sceNgs2GeomApply` stays unimplemented
|
||||
- [sceNgs2GeomCalcListener](../../../core/libs/prx/libSceNgs2.native/src/Geom.cpp) supports only a listener at the origin with front +Z, up +Y and flags 0. The identity matrix and preserved finite velocity and positive finite sound speed follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/df5b9b92be7dfd463b47756b707d6d356a9f0550/src/libs/ngs2.cpp) and [PS5PCEM](https://github.com/iStark/PS5PCEM/blob/d2534bac6325a2c61c0fa88977e5321074304484/src/hle/ngs2_geom.zig) for that case, not a PS5 measurement. Other orientations (including non-unit vectors), translations and flags throw without changing the output; the matrix convention, handedness flags and console error codes remain unverified. Nonfinite input and nonpositive sound speed also throw. Dead Cells (PPSA15552) reaches this call after the custom sampler rack in #4334, but its arguments have not been captured here. Source attenuation, panning and Doppler in `sceNgs2GeomApply` remain unimplemented
|
||||
|
|
|
|||
Loading…
Reference in New Issue