Pan follows the PS4 firmware 11.00 libSceNgs2.sprx: the speaker layout
from the init angles, then each param's angle and distance into a
volume matrix for the voice. Every param is checked before anything is
written, and angles too large to wrap throw. The pan functions are
exported from the existing Voice.cpp export block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A port without a matrix sent source channel n to destination channel n, so a
mono voice reached only the left channel of a stereo mix. The PS4 firmware
11.00 libSceNgs2.sprx mixes such ports, and ports whose matrix levels were
never set, through a fixed channel map instead: mono reaches both stereo
channels at -3 dB and the centre of 5.1 and 7.1, stereo folds to mono at
-3 dB per side, and wider sources fold down with 0.5 and -3 dB gains. The map
was measured for every source width into 1, 2, 6 and 8 channels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PPSA22520 routes its title music through port matrix 0 without setting the
matrix levels, so the music rendered silent. A matrix without levels now
mixes like a port without a matrix, source channel n to destination channel
n. The console's initial levels are unknown; the assumption is recorded in
TechnicalDebt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use INFINITY and NAN from <cmath> instead of a new <limits> include, and
list the TechnicalDebt entry beside the other NGS2 voice entries, so the
change merges cleanly with the pan, channel map and reverb changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reverb_size other than the default was accepted and ignored, and below
about 7.5 kHz an allpass length went negative and wrapped to a 16 GB buffer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PS4 firmware 11.00 libSceNgs2.sprx stores matrix levels clamped to
-4..4, keeps NaN, and rejects a level count of 0. Levels were stored
unclamped, so a level of 8 played twice as loud as on the console, and a
count of 0 cleared the matrix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sceNgs2SystemRender and the other entry points look up their handles in a
function-local std::vector guarded by a function-local std::recursive_mutex.
Both were destroyed with the other statics when the process exits, while a
title's audio thread can still be rendering: std::exit runs the destructors
on the exiting thread and other threads keep running until the process
ends. The freed vector then no longer holds the system, so rendering
returns SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE, and a lookup that walks the
racks reads freed memory and crashes.
Both are now allocated once and never destroyed, as other driver state that
must outlive static teardown already is.
PPSA21402 (Balatro) printed "Error: sceNgs2SystemRender() 804A0230" and
segfaulted in Ngs2FindVoice (from sceNgs2VoiceGetState) when it was closed.
ngs2_render now renders from an atexit handler registered before the first
NGS2 call, which runs after the library's statics are destroyed; without
this change it fails on that render.
The mixing stage threw when a mastering voice and its render buffer had different channel counts. A 2-channel voice is now mixed into the first two channels of a 6- or 8-channel buffer; other mismatches still throw.
The reverb was a Freeverb-style approximation. It now follows the PS4
firmware 11.00 libSceNgs2.sprx, read and checked against its output: the
input mixed to mono with the library's fixed gains, a 14400-sample buffer
with four reflection taps from the reflection pattern tables and a late
feed, sixteen damped comb filters with a pre-emphasis stage, a signed
selection of combs and four allpasses per output channel, a two-pole room
HF filter, and the library's parameter clamps, crossfades and setup
defaults. The test values are the PS4 library's own output.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RackGetInfo reports the rack's handles, buffer, id, uid, option limits,
render count and active voices. VoiceGetPortInfo reports a port's matrix,
volume and destination. VoiceQueryInfo answers info id 0x4001 with the
voice channel count and throws for other ids. Assumptions are recorded in
TechnicalDebt.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Rack 0x2001 is created with its option layout and KytyPS5's defaults, takes
the setup (0x20010000) and I3DL2 (0x20010001) voice params and reports the
common voice state. The voice mixes its inputs and runs a Freeverb-style
approximation driven by the I3DL2 parameters: room and room HF, early
reflections and their delay, late reverb and its pre-delay, decay time and
HF ratio, and diffusion. Out-of-range params throw, param changes keep the
tail, a stop or kill clears it, and the tail ends once it decays below
1e-20. The console's algorithm is unknown; the approximation is recorded in
TechnicalDebt. The test context buffer pool grows to 64.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mastering voice param 0x30000004 sets a full-band and an LFE level. They
scale the mastering voice when it is mixed into the render buffer, the LFE
level on channel 3 of 6- and 8-channel voices. Non-finite levels are
invalid arguments.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>