Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions core/libs/prx/libSceNgs2.native/src/Custom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ int Ngs2CheckCustomRack(std::uint32_t rackId, const Ngs2CustomRackOption& option
} else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_FILTER) {
if (info.option != nullptr || info.state_size != 0 || info.state_offset != 0) APS5_INVALID_ARG_EX;
} else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX) {
if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::runtime_error("NGS2: the custom UserFx module on a custom sampler rack is not implemented");
if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER && rackId != SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) {
throw std::runtime_error("NGS2: the custom UserFx module on this rack is not implemented");
}
if (info.option != nullptr) throw std::runtime_error("NGS2: a custom UserFx module option is not implemented");
} else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_DELAY) {
CheckDelayModule(rackId, info);
Expand Down Expand Up @@ -374,8 +376,15 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) {
return;
}
if ((param.id & ~0x1fu) == SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX) {
if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack");
SetCustomUserFxParam(voice, param.id & 0x1f, ParamAs<Ngs2CustomVoiceUserFxParam>(param));
if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER && rackId != SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) {
throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack");
}
auto index = param.id & 0x1f;
if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) {
if (index == 0 || index - 1 >= voice.customUserFx.size()) throw Ngs2ReturnCode{SCE_NGS2_ERROR_INVALID_PARAM_ID};
index--;
}
SetCustomUserFxParam(voice, index, ParamAs<Ngs2CustomVoiceUserFxParam>(param));
return;
}
if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack");
Expand Down
84 changes: 66 additions & 18 deletions core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -651,24 +651,13 @@ static void TestCustomRackChecks() {
for (std::uint32_t index = 0; index < count; ++index) {
sampler.custom_rack_option.module[index].module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER;
}
sampler.custom_rack_option.module[count - 1].module_id = 0x19;
Require(CreateSamplerThrows<std::runtime_error>(sampler));
for (const bool useAllocator : {false, true}) {
rack = 0;
rejected = false;
try {
if (useAllocator) {
sceNgs2RackCreateWithAllocator(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &allocator, &rack);

} else {
sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &info, &rack);

}
} catch (const std::runtime_error&) {
rejected = true;
}
Require(rejected && rack == 0 && allocCalls == 0);
}
sampler.custom_rack_option.module[count - 1].module_id = SCE_NGS2_CUSTOM_MODULE_ID_USER_FX;
Ngs2ContextBufferInfo counted{};
Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &counted) == SCE_NGS2_OK);
const auto countedInfo = Buffer(counted);
rack = 0;
Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &countedInfo, &rack) == SCE_NGS2_OK);
Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK);
}
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
Expand Down Expand Up @@ -778,6 +767,64 @@ static void TestCustomLegacyUserFx(std::uint32_t moduleIndex) {
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}

static Ngs2CustomSamplerRackOption LegacySamplerRackOption(const Ngs2CustomModuleOption* option, std::uint32_t moduleIndex) {
Ngs2CustomSamplerRackOption rack{};
auto& common = rack.custom_rack_option.rack_option;
common.size = sizeof(rack);
common.max_grain_samples = 512;
common.max_voices = 1;
common.max_matrices = 1;
common.max_ports = 1;
rack.custom_rack_option.num_buffers = 1;
rack.custom_rack_option.num_modules = moduleIndex + 1;
for (std::uint32_t index = 0; index <= moduleIndex; ++index) {
rack.custom_rack_option.module[index].option = option;
rack.custom_rack_option.module[index].module_id = SCE_NGS2_CUSTOM_MODULE_ID_USER_FX;
}
rack.max_channel_works = 1;
rack.max_waveform_blocks = 1;
return rack;
}

static void TestCustomSamplerLegacyUserFx(std::uint32_t moduleIndex) {
const auto system = CreateSystem();
const auto master = Mastering(system, 2);
auto option = LegacySamplerRackOption(nullptr, moduleIndex);
Ngs2ContextBufferInfo query{};
Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &query) == SCE_NGS2_OK);
const auto info = Buffer(query);
uintptr_t rack = 0;
Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_OK);

const auto voice = Voice(rack);
Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_SETUP,
Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 48000, 0, 0, 0}});
std::vector<std::int16_t> pcm(Grain * 2 * 8, 16384);
const Ngs2WaveformBlock block{0, pcm.size() * sizeof(std::int16_t), 1, 0, static_cast<std::uint32_t>(pcm.size() / 2), 0, 0};
Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS,
Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block});
Patch(voice, master);
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);

LegacyUserFxState state{};
const auto param = Ngs2CustomVoiceUserFxParam{{}, ProcessLegacy, reinterpret_cast<std::uintptr_t>(&state), 123, 456};
Control(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | (moduleIndex + 1), param);
auto out = Render(system);
Require(state.calls == 1 && state.flags == 3);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.25f && out[i * 2 + 1] == 0.5f);
out = Render(system);
Require(state.calls == 2 && state.flags == 0);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.25f && out[i * 2 + 1] == 0.5f);

Ngs2CustomVoiceUserFxParam invalid{};
invalid.header = {sizeof(invalid), 0, SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | 0};
Require(sceNgs2VoiceControl(voice, &invalid.header) == SCE_NGS2_ERROR_INVALID_PARAM_ID);
invalid.header.id = SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX | (moduleIndex + 2);
Require(sceNgs2VoiceControl(voice, &invalid.header) == SCE_NGS2_ERROR_INVALID_PARAM_ID);
Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}

struct DelayOption {
std::uint32_t size = 20;
std::uint32_t type = 0;
Expand Down Expand Up @@ -1153,6 +1200,7 @@ int main() {
TestCustomSampler();
TestCustomFilter();
for (const auto moduleIndex : {0u, 1u, 23u}) TestCustomLegacyUserFx(moduleIndex);
for (const auto moduleIndex : {0u, 1u}) TestCustomSamplerLegacyUserFx(moduleIndex);
TestCustomSamplerGain();
TestCustomGain();
TestCustomModuleInfo();
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/debt/libSceNgs2.native.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
- [NGS2 reverb channel conversion](../../../core/libs/prx/libSceNgs2.native/src/Reverb.cpp) (libSceNgs2.native) - a reverb voice whose `num_input_channels` differs from `num_output_channels` (PPSA03747 sets up 2 inputs and 8 outputs) mixes its ports in the input layout and folds that to mono with the input count's gains, as an equal-count voice does, and runs the comb and allpass stages per output channel. The PS4 library's handling of the dry signal in this case is not checked: here it reaches the output layout through the PS4 default rows, not the PS5 port map, so a stereo input keeps its dry signal on the front left and right and the other outputs carry only the reverb
- [NGS2 port matrix](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) (libSceNgs2.native) - gaps: a mastering voice into a buffer of another count throws when either count is 3, 4, 5 or 7, though PS5 firmware 3.20 and 4.03 render them; the PS4 library has no rows into 3, 4, 5 or 7 and throws, its mastering output is unmeasured, and its reverb conversion and Pan keep the PS4 rows; a port into more than 8 channels without a matrix throws, which is unreachable (`0x804A8052` for a custom submixer voice with over 8 channels, 13.42 module). Not run on PS5: non-sampler sources into 4 or 7 channels and from 1, 2, 6 or 8 channels into 3, 4, 5 or 7, an active reverb (I3DL2), custom submixers with modules and custom mastering 0x4003; they use the sampler rows here. The PS5 rows were measured on that module run on a PC, not a console. A matrix with too few levels mixes only its whole rows
- [NGS2 custom submixer rack](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4002) - UserFx2 process flags and setup/process/cleanup order follow only [KytyPS5](https://github.com/KytyPS5/KytyPS5), not a console measurement; layouts follow [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `ngs2_custom.h`. A module control handler, unequal input and output channels, setup flags and default rack options throw. Module state is a separate buffer, not the `state_offset` slice of voice state; `sceNgs2VoiceGetState` throws for this rack. UserFx2 runs only on grains with input samples, so an effect tail stops with the input. Setup changes from custom process callbacks throw before later modules can use the changed layout; inconsistent input-mixing layouts after callbacks also throw. Console reentry behavior is unknown
- [NGS2 custom UserFx](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - the 48-byte process context, process flags, callback error handling and null-handler behavior retain the existing submixer callback model and are not console-verified. Non-null module options throw because their console error is unknown. UserFx on custom sampler rack 0x4001 throws: its parameter indexing and routing are not implemented. Module id 0x19, the 40-byte voice parameter (handler and three user-data words), parameter id `0x40001900 | index`, and lack of option and module state follow [fma-rc's PS5 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943), including parameter dispatch at 0x25ec4.
- [NGS2 custom UserFx](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - the 48-byte process context, process flags, callback error handling and null-handler behavior retain the existing submixer callback model and are not console-verified. Non-null module options throw because their console error is unknown. On custom sampler rack 0x4001 the parameter index is one higher than the module index, as fma-rc reads; index 0 and an index past the module count return `INVALID_PARAM_ID`, and the sampler's single buffer is used because multi-buffer routing is not implemented. Module id 0x19, the 40-byte voice parameter (handler and three user-data words), parameter id `0x40001900 | index`, and lack of option and module state follow [fma-rc's PS5 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943), including parameter dispatch at 0x25ec4.
- [NGS2 custom Delay](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, 0x1d) - processing and defaults remain unverified on PS5. The [agreed compatibility path](https://github.com/boykopovar/AnyPS5/pull/9337#issuecomment-6351205140) preserves main's PCM pass-through for Void Terrarium (PPSA03061) until any Delay parameter is set; every `0x40001dxx` parameter throws. Only one-buffer custom submixers with no module state and a 20-byte option are accepted. Option type 0/1, taps 1..8, length 1..20000 and reserved 0 follow [fma-rc's 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943). Other shapes and values throw because their console errors are unknown. This preserves host behavior, not console-equivalent Delay processing
- [NGS2 custom module ids](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - Reverb (0x1b) and Convolution (0x23) are not processed: a rack that passes their creation checks throws. Per [fma-rc's 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6294323150), creation returns `0x804A8302` for a wrong non-zero state size, and `0x804A8013`, `0x804A8099` and `0x804A8098` for a bad Reverb option size or word and PitchShift word. Unknown: check order, errors for a Gain option, missing Reverb options, Convolution option sizes, and Convolution's state (64 bytes from SDK 3.0). PPSA04540 uses 0x15, 0x19, 0x1b and 0x23 (#8039), PPSA35169 0x20 (#4246) and PPSA33655 0x15, 0x19 and 0x1b

Expand Down
Loading