diff --git a/core/libs/prx/libSceNgs2.native/src/Rack.cpp b/core/libs/prx/libSceNgs2.native/src/Rack.cpp index 09a41304f..1acc40d6b 100644 --- a/core/libs/prx/libSceNgs2.native/src/Rack.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Rack.cpp @@ -94,6 +94,9 @@ static RackOptions CheckedRackOption(std::uint32_t rackId, const Ngs2RackOption* throw std::invalid_argument("NGS2: invalid rack voice or channel count"); } if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) Ngs2CheckCustomRack(options.customSubmixer.custom_rack_option); + if (rackId == SCE_NGS2_RACK_ID_REVERB && options.reverb.reverb_size != 1) { + throw std::runtime_error("NGS2: reverb rack size " + std::to_string(options.reverb.reverb_size) + " is not implemented"); + } return options; } diff --git a/core/libs/prx/libSceNgs2.native/src/Reverb.cpp b/core/libs/prx/libSceNgs2.native/src/Reverb.cpp index 06e2beae0..131fb321f 100644 --- a/core/libs/prx/libSceNgs2.native/src/Reverb.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Reverb.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include "prx/libc/include/General.hpp" @@ -215,7 +217,9 @@ static void ApplyParams(Ngs2ReverbState& reverb, float rate) { for (std::uint32_t channel = 0; channel < 8; channel++) { for (std::uint32_t stage = 0; stage < ALLPASSES; stage++) { auto& allpass = reverb.channels[channel].allpasses[stage]; - const auto length = static_cast(static_cast(Samples(ALLPASS_TIMES[stage], rate)) + ALLPASS_OFFSETS[channel][stage]); + const auto signedLength = static_cast(Samples(ALLPASS_TIMES[stage], rate)) + ALLPASS_OFFSETS[channel][stage]; + if (signedLength < 1) throw std::runtime_error("NGS2: reverb at a sample rate of " + std::to_string(reverb.sampleRate) + " Hz is not implemented"); + const auto length = static_cast(signedLength); if (allpass.buffer.size() != length) allpass.buffer.assign(length, 0.0f); if (allpass.cursor >= length) allpass.cursor = 0; } diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp index d74b09a83..5150057d3 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp @@ -370,7 +370,29 @@ static void TestReverbSetup() { Require(loudest > 0.0f && loudest < 1e-8f); Ngs2VoiceState state{}; Require(sceNgs2VoiceGetState(reverb, &state, sizeof(state)) == SCE_NGS2_OK && (state.state_flags & SCE_NGS2_VOICE_STATE_FLAG_INUSE) != 0); + Require(sceNgs2SystemSetSampleRate(system, 7000) == SCE_NGS2_OK); + bool lowRate = false; + try { sceNgs2SystemRender(system, &info, 1); } catch (const std::runtime_error&) { lowRate = true; } + Require(lowRate); Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + + Ngs2ReverbRackOption option{}; + option.rack_option.size = sizeof(option); + option.rack_option.max_grain_samples = 512; + option.rack_option.max_voices = 1; + option.rack_option.max_input_delay_blocks = 1; + option.rack_option.max_matrices = 1; + option.rack_option.max_ports = 8; + option.max_channels = 8; + option.reverb_size = 1; + Ngs2ContextBufferInfo query{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_REVERB, &option.rack_option, &query) == SCE_NGS2_OK); + for (std::uint32_t size : {0u, 2u}) { + option.reverb_size = size; + bool unsupported = false; + try { sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_REVERB, &option.rack_option, &query); } catch (const std::runtime_error&) { unsupported = true; } + Require(unsupported); + } } static void TestSampleRate() { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index d53bc4289..235872f77 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -178,7 +178,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceAjmBatchJobControl](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - initialize parameters are 8 bytes for ATRAC9, 12 for Opus and none for MP3, as one title passes them; other codecs and sizes, and an output other than the 8-byte result, throw - [sceAjmBatchJobControl](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - SIDEBAND_GAPLESS_DECODE with RESET overwrites both counts as in [RPCSX](https://github.com/RPCSX/rpcsx); shadPS4 ignores a zero count when the other is nonzero. Without RESET they differ, so it throws - [NGS2 custom submixer rack](../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4002) - structure layouts from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_custom.h`; module id 0x1f for UserFx2, the voice parameter id `0x40001f00 | module index`, the process flags (1 on the first process call after setup, 2 after a parameter change, 0 otherwise) and the call order (setup per voice at rack creation, process after the inputs are mixed, cleanup at destruction) come only from [KytyPS5](https://github.com/KytyPS5/KytyPS5). Only UserFx2 modules on a single buffer are accepted: other module ids, more buffers, a module control handler, different input and output channel counts, setup flags and default rack options throw. Each module's state is a separate buffer, not the `state_offset` slice of the voice state, and `sceNgs2VoiceGetState` still throws for this rack. As in KytyPS5, process runs only on grains where an input produced samples, so an effect tail stops with the input and a pending flag 2 waits for the next audible grain -- [NGS2 reverb rack](../../core/libs/prx/libSceNgs2.native/src/Reverb.cpp) (libSceNgs2.native, rack 0x2001) - follows the PS4 firmware 11.00 `libSceNgs2.sprx`, read and checked against its output at 11.025 to 48 kHz, including parameter changes during playback (differences at float rounding level); not yet compared with the PS5 library. Structure layouts come from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_reverb.h` and the param ids 0x20010000 (setup) and 0x20010001 (I3DL2) from PPSA22520. Only the default rack options are handled: their 14400-sample reflection buffer is used whatever `reverb_size` says, and the library's half-rate mode is not implemented. Setup only accepts equal input and output channel counts +- [NGS2 reverb rack](../../core/libs/prx/libSceNgs2.native/src/Reverb.cpp) (libSceNgs2.native, rack 0x2001) - follows the PS4 firmware 11.00 `libSceNgs2.sprx`, read and checked against its output at 11.025 to 48 kHz, including parameter changes during playback (differences at float rounding level); not yet compared with the PS5 library. Structure layouts come from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_reverb.h` and the param ids 0x20010000 (setup) and 0x20010001 (I3DL2) from PPSA22520. Only the default `reverb_size` of 1 is handled, with its 14400-sample reflection buffer; other sizes and the library's half-rate mode throw. System sample rates below about 7.5 kHz, where an allpass delay would have no samples, throw. Setup only accepts equal input and output channel counts - [sceHttpParseResponseHeader](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); parsing and error codes follow [shadPS4's reverse-engineered PS4 library](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp), not confirmed on a PS5 title or hardware. Folded values retain their line breaks and whitespace; an empty first line starts the value scan after its line feed. A consumed byte count above `INT_MAX` throws instead of wrapping. - [sceHttpParseStatusLine](../../core/libs/prx/libSceHttp/src/Parse.cpp) (libSceHttp) - behaviour and error codes follow the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title; unlike it, no byte past `lineLen` is read - [sceHttpUriMerge](../../core/libs/prx/libSceHttp/src/Uri.cpp) (libSceHttp) - signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Net/Http.cs); validation, size formula, absolute/relative split and path join follow the disassembly-like body in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp), not confirmed on a PS5 title or hardware. The base URL goes through our `sceHttpUriParse`, which removes dot segments from the base path, while the relative part is appended unswept; base query and fragment are dropped. The relative reference is only classified by its scheme and a leading `//`, and only references with `//` are validated; as in shadPS4, `//host/x` is returned unchanged instead of taking the base scheme, a reference with a scheme but no `//` (`mailto:x`) is joined onto the base directory, and `?q` and `#f` are appended to the base directory instead of the base path.