fix(libSceNgs2.native): mix through a selected matrix whose levels were never set
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>
This commit is contained in:
parent
d724cad38c
commit
56d8e5c4df
|
|
@ -161,7 +161,7 @@ static void RenderSampler(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t s
|
|||
static void RenderVoice(Ngs2Voice& voice, const std::vector<Ngs2Voice*>& voices, std::uint32_t grain, std::uint32_t systemRate);
|
||||
|
||||
static void MixPort(Ngs2Voice& voice, const Ngs2Voice& source, const Ngs2Port& port, std::uint32_t grain) {
|
||||
const auto* matrix = port.matrix < 0 ? nullptr : &source.matrices[port.matrix];
|
||||
const auto* matrix = port.matrix < 0 || source.matrices[port.matrix].empty() ? nullptr : &source.matrices[port.matrix];
|
||||
const std::size_t outputs = matrix == nullptr ? voice.channels : std::min<std::size_t>(voice.channels, matrix->size() / source.channels);
|
||||
for (std::size_t dst = 0; dst < outputs; dst++) {
|
||||
for (std::uint32_t src = 0; src < source.channels; src++) {
|
||||
|
|
|
|||
|
|
@ -423,6 +423,46 @@ static void TestLock() {
|
|||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
static void TestUnsetMatrix() {
|
||||
const auto system = CreateSystem();
|
||||
const auto master = Mastering(system, 2);
|
||||
const auto submixer = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SUBMIXER));
|
||||
Control(submixer, SCE_NGS2_SUBMIXER_VOICE_PARAM_SETUP, Ngs2SubmixerVoiceSetupParam{{}, 2, 0});
|
||||
Patch(submixer, master);
|
||||
Event(submixer, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
|
||||
const std::vector<std::int16_t> pcm(Grain, 16384);
|
||||
const auto sampler = Sampler(system, pcm, 0);
|
||||
Patch(sampler, submixer);
|
||||
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
||||
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_VOLUME, Ngs2VoicePortVolumeParam{{}, 0, 0.5f});
|
||||
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
|
||||
std::vector<float> out(Grain * 2, -1.0f);
|
||||
const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2};
|
||||
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
||||
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.25f && out[i * 2 + 1] == 0.0f);
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
|
||||
const auto stereoSystem = CreateSystem();
|
||||
const auto stereoMaster = Mastering(stereoSystem, 2);
|
||||
const auto stereo = Voice(CreateRack(stereoSystem, SCE_NGS2_RACK_ID_SAMPLER));
|
||||
Control(stereo, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 48000, 0, 0, 0}});
|
||||
std::vector<std::int16_t> frames;
|
||||
for (std::uint32_t i = 0; i < Grain; i++) {
|
||||
frames.push_back(16384);
|
||||
frames.push_back(8192);
|
||||
}
|
||||
const Ngs2WaveformBlock block{0, frames.size() * sizeof(std::int16_t), 0, 0, Grain, 0, 0};
|
||||
Control(stereo, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, frames.data(), 0, 1, &block});
|
||||
Patch(stereo, stereoMaster);
|
||||
Control(stereo, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
||||
Event(stereo, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
Require(sceNgs2SystemRender(stereoSystem, &info, 1) == SCE_NGS2_OK);
|
||||
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.5f && out[i * 2 + 1] == 0.25f);
|
||||
Require(sceNgs2SystemDestroy(stereoSystem, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
static void TestAllocator() {
|
||||
const Ngs2BufferAllocator allocator{Allocate, Release, 9};
|
||||
uintptr_t system = 0;
|
||||
|
|
@ -455,6 +495,7 @@ int main() {
|
|||
TestMasteringGain();
|
||||
TestStereoIntoSurround();
|
||||
TestLock();
|
||||
TestUnsetMatrix();
|
||||
TestAllocator();
|
||||
exitSystem = CreateSystem();
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceRazorCpuPlotValue, sceRazorCpuWriteBookmark](../../core/libs/prx/libSceRazorCpu/Export.cpp) (libSceRazorCpu) - signatures (series, float value) and (label, optional description) from [SharpProspero](https://github.com/SvenGDK/SharpProspero), matching PPSA06320's calls; both do nothing without a capture and return 0
|
||||
- [sceAjmBatchJobGetInfo](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - writes the result and format sideband of a run job without buffers with the `SIDEBAND_FORMAT` flag (0x400000000000), as [KytyPS5](https://github.com/KytyPS5/KytyPS5) does, not confirmed on a PS5 title; the format reports bitrate 0 for ATRAC9 as in KytyPS5, while [shadPS4](https://github.com/shadps4-emu/shadPS4) derives it from the superframe size; before the instance is initialized it returns the not-initialized result (1) and no format, like the other run jobs here, while KytyPS5 returns result 0 with the format of the uninitialized decoder
|
||||
- [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - signature, result layout and rate tables taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) reimplementation of the PS4 library, not confirmed on a PS5 title: the layer field is not checked and MPEG-2.5 stops at 64 kbps; the original file length (`parse_ofl`) layout (Xing/Info + LAME tag, VBRI, Fraunhofer `0xB4` block with its CRC, encoder delay = samples per frame + LAME delay + 529) also comes from shadPS4 only, and the Fraunhofer block has no other public description
|
||||
- [NGS2 port matrix](../../core/libs/prx/libSceNgs2.native/src/Render.cpp) (libSceNgs2.native) - a port that selects a matrix whose levels were never set mixes as a port without a matrix (source channel n to destination channel n, so a mono source reaches only channel 0). PPSA22520 selects matrix 0 for its title music without setting its levels, and the title is expected to have music on the console, so an unset matrix is assumed not to be silent; the console's initial levels are unknown. A matrix set with fewer levels than the destination needs still mixes only the rows it has
|
||||
- [sceAjmBatchJobGetCodecInfo, sceAjmBatchJobGetGaplessDecode](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `RUN_GET_CODEC_INFO` flag (0x800), the ATRAC9 codec info layout and its next frame size are taken from the [shadPS4](https://github.com/shadps4-emu/shadPS4) and [RPCSX](https://github.com/RPCSX/rpcsx) reimplementations, not confirmed on a PS5 title; a run job without buffers returns result 0, as in shadPS4. The gapless sideband reports the total and skip counts the title set and does not count them down: RPCSX does the same, shadPS4 counts them down and BryKytyPS5 returns its current counts
|
||||
- [AJM Opus decoder](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout (`u32 channels, u32 sample rate, u32`, seen as `2, 48000, 0`) and the little-endian `u16` byte count before each packet come from one title (The Smurfs Dreams); only 48000 Hz and a zero third word are accepted
|
||||
- [sceAjmBatchJobControl](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET (bit 13), INITIALIZE (bit 14) and the sideband input order (gapless decode, then initialize parameters) come from [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4); other flags throw
|
||||
|
|
|
|||
Loading…
Reference in New Issue