Merge pull request #812 from adriadam10/feat/ngs2-system-state

feat(ngs2): system sample rate, user data and lock
This commit is contained in:
Dean 2026-10-06 22:33:34 +13:00 committed by GitHub
commit 2f7c9e5914
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 147 additions and 1 deletions

View File

@ -130,6 +130,7 @@ struct Ngs2System {
Ngs2BufferAllocator allocator{};
std::uint32_t uid = 0;
std::int64_t renderCount = 0;
std::uintptr_t userData = 0;
std::vector<Ngs2Rack*> racks;
};

View File

@ -188,6 +188,55 @@ int APS5_VABI sceNgs2SystemSetGrainSamples(uintptr_t system_handle, uint32_t num
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemSetSampleRate(uintptr_t system_handle, uint32_t sample_rate) {
std::lock_guard lock(Ngs2Mutex());
auto* system = Ngs2FindSystem(system_handle);
if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
if (sample_rate == 0) APS5_INVALID_ARG_EX;
for (const auto* rack : system->racks) {
for (const auto& voice : rack->voices) {
for (const auto& filter : voice.filters) {
if (filter.enabled && sample_rate != system->option.sample_rate) {
throw std::runtime_error("NGS2: changing the sample rate under an enabled sampler filter is not implemented");
}
}
}
}
system->option.sample_rate = sample_rate;
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemSetUserData(uintptr_t system_handle, uintptr_t user_data) {
std::lock_guard lock(Ngs2Mutex());
auto* system = Ngs2FindSystem(system_handle);
if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
system->userData = user_data;
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemGetUserData(uintptr_t system_handle, uintptr_t* user_data) {
std::lock_guard lock(Ngs2Mutex());
const auto* system = Ngs2FindSystem(system_handle);
if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
if (user_data == nullptr) APS5_INVALID_ARG_EX;
*user_data = system->userData;
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemLock(uintptr_t system_handle) {
Ngs2Mutex().lock();
if (Ngs2FindSystem(system_handle) != nullptr) return SCE_NGS2_OK;
Ngs2Mutex().unlock();
return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
}
int APS5_VABI sceNgs2SystemUnlock(uintptr_t system_handle) {
std::lock_guard lock(Ngs2Mutex());
if (Ngs2FindSystem(system_handle) == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE;
Ngs2Mutex().unlock();
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info, uint32_t num_buffer_info) {
std::lock_guard lock(Ngs2Mutex());
auto* system = Ngs2FindSystem(system_handle);

View File

@ -173,10 +173,28 @@ static void TestRejectedParams() {
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
static void TestRateChangeUnderFilter() {
const auto system = CreateSystem();
const auto sampler = Sampler(system, std::vector<std::int16_t>(Grain, 0), 1, 48000);
SetFilter(sampler, Filter(0, 1, 1, 0, 1000.0f, 1.0f, 1.0f));
Require(sceNgs2SystemSetSampleRate(system, 48000) == SCE_NGS2_OK);
bool rejected = false;
try {
sceNgs2SystemSetSampleRate(system, 96000);
} catch (const std::runtime_error&) {
rejected = true;
}
Require(rejected);
SetFilter(sampler, Filter(0, 1, 0, 0, 0.0f, 1.0f, 1.0f));
Require(sceNgs2SystemSetSampleRate(system, 96000) == SCE_NGS2_OK);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
int main() {
TestLowPassAndTail();
TestBypassMaskAndGain();
TestSystemRateAndZeroCutoff();
TestRejectedParams();
TestRateChangeUnderFilter();
return 0;
}

View File

@ -1,7 +1,11 @@
#include "Ngs2Test.hpp"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <thread>
#include <vector>
static uintptr_t Sampler(uintptr_t system, const std::vector<std::int16_t>& pcm, std::uint32_t repeats) {
@ -152,6 +156,71 @@ static std::int32_t APS5_VABI Release(Ngs2ContextBufferInfo* info) {
return SCE_NGS2_OK;
}
static void TestSampleRate() {
const auto system = CreateSystem();
Require(sceNgs2SystemSetSampleRate(0x1234, 96000) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE);
bool threw = false;
try {
sceNgs2SystemSetSampleRate(system, 0);
} catch (const std::invalid_argument&) {
threw = true;
}
Require(threw);
Require(sceNgs2SystemSetSampleRate(system, 96000) == SCE_NGS2_OK);
Ngs2SystemInfo info{};
Require(sceNgs2SystemGetInfo(system, &info, sizeof(info)) == SCE_NGS2_OK && info.sample_rate == 96000);
const auto master = Mastering(system, 1);
const std::vector<std::int16_t> pcm{0, 1000, 2000, 3000};
const auto sampler = Sampler(system, pcm, 1);
Patch(sampler, master);
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
const std::int16_t expected[Grain] = {0, 500, 1000, 1500, 2000, 2500, 3000, 1500};
const auto out = RenderI16(system);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == expected[i]);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
static void TestUserData() {
uintptr_t value = 1;
Require(sceNgs2SystemSetUserData(0x1234, 5) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE);
Require(sceNgs2SystemGetUserData(0x1234, &value) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE && value == 1);
const auto system = CreateSystem();
Require(sceNgs2SystemGetUserData(system, &value) == SCE_NGS2_OK && value == 0);
Require(sceNgs2SystemSetUserData(system, 0xfedcba9876543210) == SCE_NGS2_OK);
Require(sceNgs2SystemGetUserData(system, &value) == SCE_NGS2_OK && value == 0xfedcba9876543210);
bool threw = false;
try {
sceNgs2SystemGetUserData(system, nullptr);
} catch (const std::invalid_argument&) {
threw = true;
}
Require(threw);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
static void TestLock() {
const auto system = CreateSystem();
Require(sceNgs2SystemLock(0x1234) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE);
Require(sceNgs2SystemUnlock(0x1234) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE);
Ngs2SystemInfo info{};
std::thread free([&] { Require(sceNgs2SystemGetInfo(system, &info, sizeof(info)) == SCE_NGS2_OK); });
free.join();
Require(sceNgs2SystemLock(system) == SCE_NGS2_OK);
std::atomic<bool> done = false;
std::thread blocked([&] {
Require(sceNgs2SystemGetInfo(system, &info, sizeof(info)) == SCE_NGS2_OK);
done = true;
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Require(!done);
Require(sceNgs2SystemUnlock(system) == SCE_NGS2_OK);
blocked.join();
Require(done);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
static void TestAllocator() {
const Ngs2BufferAllocator allocator{Allocate, Release, 9};
uintptr_t system = 0;
@ -171,6 +240,9 @@ int main() {
TestPcmBlockEnd();
TestPitchAndRepeat();
TestSubmixerMatrix();
TestSampleRate();
TestUserData();
TestLock();
TestAllocator();
return 0;
}

View File

@ -18,6 +18,11 @@ int APS5_VABI sceNgs2SystemCreateWithAllocator(const Ngs2SystemOption*, const Ng
int APS5_VABI sceNgs2SystemDestroy(uintptr_t, Ngs2ContextBufferInfo*);
int APS5_VABI sceNgs2SystemGetInfo(uintptr_t, Ngs2SystemInfo*, size_t);
int APS5_VABI sceNgs2SystemSetGrainSamples(uintptr_t, uint32_t);
int APS5_VABI sceNgs2SystemSetSampleRate(uintptr_t, uint32_t);
int APS5_VABI sceNgs2SystemSetUserData(uintptr_t, uintptr_t);
int APS5_VABI sceNgs2SystemGetUserData(uintptr_t, uintptr_t*);
int APS5_VABI sceNgs2SystemLock(uintptr_t);
int APS5_VABI sceNgs2SystemUnlock(uintptr_t);
int APS5_VABI sceNgs2SystemRender(uintptr_t, const Ngs2RenderBufferInfo*, uint32_t);
int APS5_VABI sceNgs2RackQueryBufferSize(uint32_t, const Ngs2RackOption*, Ngs2ContextBufferInfo*);
int APS5_VABI sceNgs2RackCreate(uintptr_t, uint32_t, const Ngs2RackOption*, const Ngs2ContextBufferInfo*, uintptr_t*);
@ -40,7 +45,7 @@ inline void Check(bool value, int line) {
inline constexpr std::uint32_t Grain = 8;
inline std::vector<std::uint64_t> buffers[16];
inline std::vector<std::uint64_t> buffers[32];
inline std::size_t usedBuffers = 0;
inline Ngs2ContextBufferInfo Buffer(const Ngs2ContextBufferInfo& query) {

View File

@ -118,6 +118,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceSystemServiceDisableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature
- [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature
- [sceSystemServiceParamGetString](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the system name (parameter 6) is the fixed `PS5`, not the console's name; other parameters and buffers of 1 to 64 bytes throw
- [sceNgs2SystemSetSampleRate, sceNgs2SystemSetUserData, sceNgs2SystemGetUserData, sceNgs2SystemLock, sceNgs2SystemUnlock](../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2.native) - shadPS4 only checks the system handle (`INVALID_SYSTEM_HANDLE`) and SharpProspero names what each call does; neither gives the other limits. A sample rate of 0 and a null user data pointer throw, any other rate is accepted, and changing the rate while a sampler filter is enabled throws because the filter coefficients were computed for the old rate. Lock and unlock take the same lock as `sceNgs2RackLock`
- [sceVoiceSetMuteFlag](../../core/libs/prx/libSceVoice/Export.cpp) (libSceVoice) - unknown signature
- [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - only shadPS4 (PS4) has bodies. A null tick returns `INVALID_POINTER` like the existing `sceRtcFormatRFC3339`, where shadPS4 formats the current time instead; offsets outside ±1439 minutes return `INVALID_VALUE` like `sceRtcFormatRFC3339`; the local-time variants use the host's offset at the given instant (shadPS4 uses `sceKernelGettimezone`, the current offset)
- [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf`