Skip to content

feat(ngs2): allow the custom UserFx module on custom sampler racks - #14379

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
edubox:feat/ngs2-custom-sampler-userfx
Nov 6, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
edubox:feat/ngs2-custom-sampler-userfx

Conversation

@edubox

@edubox edubox commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

What

Accept the custom UserFx module (0x19) on custom sampler racks (0x4001). The rack check allows it, and the voice parameter follows the module as fma-rc reads the 12.70 module: on a custom sampler rack its index is one higher than the module index, so index 0 and any index past the module count return INVALID_PARAM_ID (0x804A835A). The sampler's single buffer is used; multi-buffer routing remains unimplemented. The existing legacy UserFx callback path is reused, with no option or module state.

The test also picks up two current-main requirements: a custom sampler rack must set max_waveform_blocks, and the legacy UserFx control marks the changed parameter, so the first process call reports flags 3, as the custom submixer case does.

Tested

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Please drop the new CreateSamplerThrows<std::runtime_error>(LegacySamplerRackOption(&header, moduleIndex)) assertion at Ngs2Custom.cpp:833. Custom.cpp:84 explicitly leaves non-null custom UserFx options unimplemented, so the test pins an unsupported path to keep throwing. Keep the production refusal, sampler callback/output checks and implemented invalid-index validation. The retargeted unknown-module check at Ngs2Custom.cpp:654-655 also pins an unimplemented module (now 0x99); please avoid replacing the old sampler-UserFx throw expectation with a new unsupported-module throw pin. The one-based sampler parameter index agrees with the cited 12.70 module reading; that reading is disassembly evidence, and the existing callback model remains unmeasured on a console.

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — changes requested

The PR body says that index 0 and any index past the module count on the custom sampler rack return INVALID_PARAM_ID, but the code throws std::invalid_argument, which sceNgs2VoiceControl does not turn into a return code, and the new test pins that exception.

What I ran

Exact head 6a4b61d6c29e08aa814c6fe688f3533e26a041d5, base main e67cc818696fd6e6c58ac80adc6550c54aed68e8 (merge-base e6f57307fa7f47165d7fc1bee945abdb2f6816e7; merge preview clean). Environment: Debian 12, Python 3.11.2, git 2.39.5. No build, no compiler, no CTest.

python3 tools/pr_precheck.py --repo boykopovar/AnyPS5 --pr 14379
python3 tools/pr_abi_audit.py --repo /marc/AnyPS5 --base e6f57307fa7f47165d7fc1bee945abdb2f6816e7 --head 6a4b61d6c29e08aa814c6fe688f3533e26a041d5 --json
python3 tools/pr_test_quality.py --root <new directory with git archive of the head> --json core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp

The precheck's ABI and test-quality helpers could not find their scripts in its isolated head tree, so I ran both helpers by hand as listed. I also read the head versions of Voice.cpp, Custom.cpp and Ngs2Custom.cpp.

Results

Check Result
Conventions and PR template (precheck) Passed.
git diff --check (precheck) Passed.
Live stack and merge preview against main (precheck) Clean.
pr_abi_audit.py (by hand) Passed with warnings: 8 symbols, lexical public-declaration-not-found on internal names such as Cleanup, ControlHandler and CountAlloc. The diff adds no exported symbol.
pr_test_quality.py (by hand) One heuristic finding, possible-unregistered-cpp-test on Ngs2Custom.cpp. False positive: the file is registered as ngs2_custom_tests by the ngs2Test loop in core/libs/prx/libSceNgs2.native/CMakeLists.txt, which includes Custom.
Source reading: exception path sceNgs2VoiceControl catches only Ngs2ReturnCode. Index 0 and out-of-range indexes on the sampler path throw std::invalid_argument. See Blocking.
Hosted CI (precheck snapshot) Passed: eligibility, intake, library-portability, report. Pending: conventions, labels, windows. Skipped: linux, register-context, relinker, relinker-asan, write-watch.
Build and NGS2 tests Not run.
Unedited command output
$ python3 tools/pr_test_quality.py --root <archive> --json core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp
findings: possible-unregistered-cpp-test (core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp, line 1)

$ python3 tools/pr_abi_audit.py ... --json
status: passed_with_warnings; symbols: 8; findings: public-declaration-not-found (Cleanup, ControlHandler, CountAlloc, ...)

Findings

Blocking:

  1. The PR body and the code disagree on the value the guest receives. The PR body says that on custom sampler rack 0x4001, "index 0 and any index past the module count return INVALID_PARAM_ID (0x804A835A)". The code does not return that code. Index 0 hits if (index == 0) APS5_INVALID_ARG_EX; (Custom.cpp#L384). An index past the module count hits the bounds check in SetCustomUserFxParam (Custom.cpp#L237). Both use APS5_INVALID_ARG_EX, which throws std::invalid_argument (General.hpp#L106). sceNgs2VoiceControl catches only Ngs2ReturnCode (Voice.cpp#L1104), so the exception leaves the API and no return code reaches the guest. The new test pins the exception: Ngs2Custom.cpp#L831-L832 use ControlThrows<std::invalid_argument>. The same file already returns Ngs2ReturnCode{SCE_NGS2_ERROR_INVALID_PARAM_ID} for out-of-range indexes on the filter path (Custom.cpp#L345 and #L350).

    If the firmware reading in the PR is right, the emulator's result differs from the console for these two calls. If the exception is what was intended, the PR body and the debt entry are wrong. Smallest fix in the first case, to match the body and the filter path (not compiled):

        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--;
        }

Then assert the returned code in Ngs2Custom.cpp instead of ControlThrows<std::invalid_argument>. In the second case, correct the sentence in the PR body and the custom UserFx entry in libSceNgs2.native.md.

Non-blocking: The test-scope points in oneandonlydean's comment of 2026-11-01 11:03 (the non-null option assertion at Ngs2Custom.cpp:833 and the retargeted 0x99 module check) are not repeated here; this review covers only the return-code question above.

Sources

Not verified

  • The NGS2 test binaries (ngs2_custom, ngs2_userfx) were not built or run here. The author reports a Linux GCC run; it is not independently verified.
  • The fma-rc 12.70 module reading that the PR cites, including the index offset and the 0x804A835A code, was not checked.
  • Windows and macOS were not run.

To get this to a good state

Choose one of the two options under Blocking. For the code option, rerun ngs2_custom and ngs2_userfx on Linux, and the same no-build checks, on the new head.

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 5548 docs/dev/debt/libSceNgs2.native.md
PR 9149 docs/dev/debt/libSceNgs2.native.md
PR 10249 core/libs/prx/libSceNgs2.native/src/Custom.cpp
PR 11388 docs/dev/debt/libSceNgs2.native.md
PR 12107 core/libs/prx/libSceNgs2.native/src/Custom.cpp
core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp
docs/dev/debt/libSceNgs2.native.md
PR 14212 docs/dev/debt/libSceNgs2.native.md

@edubox

edubox commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

Addressed both reviews on 4e5c7a5d75.

  • The sampler UserFx index path now returns the guest code: index 0 and an index past the module count throw Ngs2ReturnCode{SCE_NGS2_ERROR_INVALID_PARAM_ID}, and TestCustomSamplerLegacyUserFx asserts that return value instead of a host exception. The debt entry now matches.
  • Dropped the LegacySamplerRackOption(&header, moduleIndex) throw assertion for a non-null option and the retargeted 0x99 unsupported-module pin. The rack-check loop now creates and destroys a sampler rack with a UserFx module at the end for 1, 2 and 24 modules.

Verified on this machine (Linux, GCC 16.2.1):

  • ngs2_* in build/core/libs/prx/libSceNgs2.native: 35/35 passed.
  • ngs2_* in build/core/libs/prx/libSceNgs2: 10/10 passed (including the five ngs2_core_custom_safety_*).
  • python3 tools/check_conventions.py --base origin/main: all checks passed.
  • git diff --check: clean.

AI-assisted: yes

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — no blocking findings

The custom UserFx module is now accepted on a custom sampler rack, and the voice-parameter index is shifted by one only on that rack, with index 0 and out-of-range indices rejected with INVALID_PARAM_ID; the code paths are consistent with each other and the test is updated to match, but the index rule itself rests on an external reading of firmware 12.70, which I could not check here.

What I ran

  • Exact head 4e5c7a5d759e6435ce633b52bc06a0941d55b5c7, base e67cc81869.
  • Environment: Linux, /usr/bin/python3 3.11.2, no build.
  • Precheck: python3 tools/pr_precheck.py --repo boykopovar/AnyPS5 --pr 14379 --output-dir /dev/shm/reviewer-run/precheck/14379
  • ABI audit by hand (helper absent from the head tree): python3 tools/pr_abi_audit.py --repo /marc/AnyPS5 --base e67cc81869 --head 4e5c7a5d759e6435ce633b52bc06a0941d55b5c7 --json (exit 0; passed_with_warnings; 8 findings, all public-declaration-not-found for callbacks defined in the test and module source, not in a header; 8 symbols).
  • Test-quality helper on core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp from a head extract: one literal-path registration heuristic.
  • Source read at head: Ngs2CheckCustomRack and Ngs2ApplyCustomParam in core/libs/prx/libSceNgs2.native/src/Custom.cpp (around lines 78-88 and 376-392) and the rewritten test cases in Ngs2Custom.cpp.

Results

Precheck report, checks table, unedited (`/dev/shm/reviewer-run/precheck/14379/report.md`):

Check Status Command / reason
build_and_compile not_applicable The precheck deliberately does not configure, build, or compile AnyPS5.
diff_check passed git diff --check e67cc818696fd6e6c58ac80adc6550c54aed68e8...4e5c7a5d759e6435ce633b52bc06a0941d55b5c7
conventions_and_pr_template passed /usr/bin/python3 tools/check_conventions.py --base e67cc818696fd6e6c58ac80adc6550c54aed68e8 --head 4e5c7a5d759e6435ce633b52bc06a0941d55b5c7 --title feat(ngs2): allow the custom UserFx module on custom sampler racks --body /dev/shm/reviewer-run/precheck/14379/pr-body.md
changed_export_abi_audit failed /usr/bin/python3 tools/pr_abi_audit.py --base e67cc818696fd6e6c58ac80adc6550c54aed68e8 --head 4e5c7a5d759e6435ce633b52bc06a0941d55b5c7 --json
python_syntax not_applicable No changed Python implementation files.
related_python_unit_tests not_applicable No changed tools/ module has an existing sibling tools/tests/test_<module>.py suite.
python_test_registration not_applicable No changed test_*.py files.
live_dependency_and_mergeability_check passed /usr/bin/python3 /marc/AnyPS5/tools/pr_stack_check.py 14379 --repo boykopovar/AnyPS5 --output-dir /dev/shm/reviewer-run/precheck/14379/stack
build_dependent_cpp_tests not_applicable Not run: C++ tests need exact-head binaries, which require a build; the precheck does not build.
test_quality_heuristics failed /usr/bin/python3 /dev/shm/reviewer-run/precheck/14379/head-4e5c7a5d759e-4372fcc2/tools/pr_test_quality.py --root /dev/shm/reviewer-run/precheck/14379/head-4e5c7a5d759e-4372fcc2 --json core/libs/prx/libSceNgs2.native/src/Custom.cpp core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp docs/dev/debt/libSceNgs2.native.md
negative_controls not_applicable No changed Python test module in tools/ can be run as a no-build base-source control.

Negative control

Not run here. The C++ test needs a build; on base the sampler-rack UserFx cases throw, which is what the updated test expects to change.

Findings

Blocking: none.

Non-blocking:

  • The off-by-one index for the sampler rack (the voice parameter index is one higher than the module index) is the PR's reading of the 12.70 module. It cannot be checked here, and the debt entry should name the source.
  • The ABI audit's eight findings are the lexical class seen before (callbacks marked APS5_VABI in test or module source without a header declaration); they are not exports.

Sources

  • Rack check at head:
    } 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 && 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);
    } else if (BuiltInModuleName(info.module_id) == nullptr) {
    throw std::runtime_error("NGS2: custom module id " + Ngs2Hex(info.module_id) + " is not implemented");
  • Parameter routing at head:
    return;
    }
    if ((param.id & ~0x1fu) == SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX) {
    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");
    throw std::runtime_error("NGS2: voice param " + Ngs2Hex(param.id) + " is not implemented");
    }

Not verified

  • The firmware 12.70 index semantics cited in the PR were not checked.
  • The C++ test was not built or run here.
  • Hosted: at this head conventions, eligibility, labels, library-portability and report succeeded; linux, register-context, relinker, relinker-asan and write-watch were skipped.

@edubox

edubox commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

Re-verified on 4e5c7a5d75: the non-null UserFx option CreateSamplerThrows<std::runtime_error> assertion and the retargeted 0x99 unsupported-module pin are gone, while the production refusal for non-null options remains. Index 0 and out-of-range indexes now return INVALID_PARAM_ID via Ngs2ReturnCode, and the test pins that return value; the sampler callback/output and rack-check coverage remains.

Re-ran locally on this head: libSceNgs2.native 35/35, libSceNgs2 10/10 (including the five ngs2_core_custom_safety_*), and python3 tools/check_conventions.py --base origin/main passed. No further changes.

@edubox

edubox commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

@MarcZX10 the return-code and test-scope items are addressed on 4e5c7a5d75; can you re-review that head?

@maskym

maskym commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Windows result for head 4e5c7a5d75, merged alone into main e0caf13600 (the PR's Tested section names no Windows run).

Host: Windows 11 26300, Intel i7-12700K, WinLibs GCC 15.2.0 (x86_64-ucrt-posix-seh, r6), Release, BUILD_TESTING=ON, Ninja. Build clean.

ngs2_custom, the test this PR extends, passes 3 of 3 runs. Not run on a PS5.

AI-assisted: yes (Claude Code), the runs and the numbers above are from this machine.

@adriadam10 adriadam10 self-assigned this Nov 6, 2026
@adriadam10
adriadam10 merged commit 3e06949 into boykopovar:main Nov 6, 2026
11 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the sampler-rack UserFx index mapping (+1, index 0 and past-the-count return INVALID_PARAM_ID through Ngs2ReturnCode), the reused legacy callback path, the rewritten test (flags 3 then 0, both invalid-index returns) and the updated debt entry; earlier review points are addressed, and maskym's Windows run covers ngs2_custom; merged.

@edubox
edubox deleted the feat/ngs2-custom-sampler-userfx branch November 6, 2026 08:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants