fix(ngs2): resolve voice patches to rack handles

This commit is contained in:
MarcZX100 committed 2026-10-13 20:02:08 +00:00
1 parent a0f5916a29
commit 8b0da182a0
3 files changed
+27 -1

No files matched your search

@@ -116,6 +116,10 @@ static void Patch(Ngs2Voice& voice, const Ngs2VoicePatchParam& patch) {
return;
}
auto* dest = Ngs2FindVoice(patch.dest_handle);
if (dest == nullptr) {
auto* rack = Ngs2FindRack(patch.dest_handle);
if (rack != nullptr && !rack->voices.empty()) dest = &rack->voices.front();
}
if (dest == nullptr || dest->rack->system != voice.rack->system || dest->rack->rackId == SCE_NGS2_RACK_ID_SAMPLER ||
dest->rack->rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) {
APS5_INVALID_ARG_EX;
@@ -147,6 +147,27 @@ static void TestPcmBlockEnd() {
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
static void TestPatchToRackHandle() {
const auto system = CreateSystem();
const auto masteringRack = CreateRack(system, SCE_NGS2_RACK_ID_MASTERING);
const auto master = Voice(masteringRack);
Control(master, SCE_NGS2_MASTERING_VOICE_PARAM_SETUP, Ngs2MasteringVoiceSetupParam{{}, 1, 0});
Control(master, SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT, Ngs2MasteringVoiceOutputParam{{}, 0, 0});
Event(master, SCE_NGS2_VOICE_EVENT_PLAY);
const std::vector<std::int16_t> pcm(Grain, 16384);
const auto sampler = Sampler(system, pcm, 0);
Control(sampler, SCE_NGS2_VOICE_PARAM_PATCH, Ngs2VoicePatchParam{{}, 0, 0, masteringRack});
Ngs2VoicePortInfo port{};
Require(sceNgs2VoiceGetPortInfo(sampler, 0, &port, sizeof(port)) == SCE_NGS2_OK);
Require(port.dest_handle == master && port.dest_input_id == 0);
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
const auto out = RenderI16(system);
Require(out == pcm);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
static bool Near(float a, float b) {
return std::fabs(a - b) < 1e-4f;
}
@@ -887,6 +908,7 @@ int main() {
Require(std::atexit(RenderAfterStaticTeardown) == 0);
TestErrorsAndInfo();
TestPcmBlockEnd();
TestPatchToRackHandle();
TestPan();
TestPitchAndRepeat();
TestSubmixerMatrix();
+1 -1
View File
@@ -18,7 +18,7 @@
- [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`
- [sceNgs2RackGetInfo](../../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) (libSceNgs2.native) - field order from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `OrbisNgs2RackInfo` with the 64-byte name of the other info structures (216 bytes); `type` is assumed to be the rack id divided by 0x1000, `min_grain_samples` 64 (the system minimum) and `state_flags` 1; `uid` comes from a per-process rack counter, `render_count` is the owning system's render count and `active_voice_count` counts voices that are not empty (paused and stopped voices included). The last process ratio and tick and `active_channel_work_count` are left zero
- [sceNgs2VoiceQueryInfo](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - only info id 0x4001 is known: PPSA22520 passes an 8-byte buffer and branches on the first word being 1 or 2, so it is assumed to be the voice channel count (0 before the voice is set up); the second word is zeroed and other ids throw
- [sceNgs2VoiceGetPortInfo](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - layout from shadPS4; the delay and destination input are zero because ports have neither yet
- [sceNgs2VoiceControl patch](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - a destination rack handle with input id 0 resolves to that rack's first voice; this matches the Dead Cells call in #4334, but selection for racks with multiple voices has not been checked against a console. Nonzero destination input ids still throw. `sceNgs2VoiceGetPortInfo` reports the resolved voice handle and zero delay.
- [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