feat(libSceNgs2.native): mastering voice gain parameter
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>
This commit is contained in:
parent
5b359092d8
commit
81a332c148
|
|
@ -50,6 +50,7 @@ static constexpr std::uint32_t SCE_NGS2_SAMPLER_VOICE_PARAM_PITCH = 0x10000005;
|
|||
static constexpr std::uint32_t SCE_NGS2_SAMPLER_VOICE_PARAM_FILTER = 0x1000000a;
|
||||
static constexpr std::uint32_t SCE_NGS2_SUBMIXER_VOICE_PARAM_SETUP = 0x20000000;
|
||||
static constexpr std::uint32_t SCE_NGS2_MASTERING_VOICE_PARAM_SETUP = 0x30000000;
|
||||
static constexpr std::uint32_t SCE_NGS2_MASTERING_VOICE_PARAM_GAIN = 0x30000004;
|
||||
static constexpr std::uint32_t SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT = 0x30000005;
|
||||
static constexpr std::uint32_t SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP = 0x40020000;
|
||||
static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX2 = 0x40001f00;
|
||||
|
|
@ -402,6 +403,13 @@ struct Ngs2MasteringVoiceSetupParam {
|
|||
std::uint32_t flags;
|
||||
};
|
||||
|
||||
struct Ngs2MasteringVoiceGainParam {
|
||||
Ngs2VoiceParamHeader header;
|
||||
float fbw_level;
|
||||
float lfe_level;
|
||||
};
|
||||
static_assert(sizeof(Ngs2MasteringVoiceGainParam) == 16);
|
||||
|
||||
struct Ngs2MasteringVoiceOutputParam {
|
||||
Ngs2VoiceParamHeader header;
|
||||
std::uint32_t output_id;
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ struct Ngs2Voice {
|
|||
std::vector<std::vector<float>> matrices;
|
||||
std::vector<Ngs2Filter> filters;
|
||||
std::uint32_t outputId = 0;
|
||||
float fbwLevel = 1.0f;
|
||||
float lfeLevel = 1.0f;
|
||||
std::vector<Ngs2UserFx2> userFx;
|
||||
std::vector<float> samples;
|
||||
bool rendering = false;
|
||||
|
|
|
|||
|
|
@ -228,7 +228,9 @@ void Ngs2RenderSystem(Ngs2System& system, const Ngs2RenderBufferInfo* bufferInfo
|
|||
if (voice->rack->rackId != SCE_NGS2_RACK_ID_MASTERING || !voice->hasSamples) continue;
|
||||
auto& mix = OutputMix(mixes, bufferInfo, numBufferInfo, *voice, grain);
|
||||
for (std::uint32_t channel = 0; channel < voice->channels; channel++) {
|
||||
for (std::uint32_t i = 0; i < grain; i++) mix[i * voice->channels + channel] += voice->samples[channel * grain + i];
|
||||
const bool lfe = channel == 3 && (voice->channels == 6 || voice->channels == 8);
|
||||
const float level = lfe ? voice->lfeLevel : voice->fbwLevel;
|
||||
for (std::uint32_t i = 0; i < grain; i++) mix[i * voice->channels + channel] += voice->samples[channel * grain + i] * level;
|
||||
}
|
||||
}
|
||||
for (std::uint32_t i = 0; i < numBufferInfo; i++) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ void Ngs2Voice::ResetSetup() {
|
|||
std::fill(ports.begin(), ports.end(), Ngs2Port{});
|
||||
for (auto& matrix : matrices) matrix.clear();
|
||||
filters.clear();
|
||||
fbwLevel = 1.0f;
|
||||
lfeLevel = 1.0f;
|
||||
channels = 0;
|
||||
sampleRate = 0;
|
||||
waveformType = 0;
|
||||
|
|
@ -257,6 +259,13 @@ static void ApplyParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) {
|
|||
SetupMixer(voice, ParamAs<Ngs2MasteringVoiceSetupParam>(param).num_io_channels);
|
||||
return;
|
||||
}
|
||||
if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_GAIN) {
|
||||
const auto& gain = ParamAs<Ngs2MasteringVoiceGainParam>(param);
|
||||
if (!std::isfinite(gain.fbw_level) || !std::isfinite(gain.lfe_level)) APS5_INVALID_ARG_EX;
|
||||
voice.fbwLevel = gain.fbw_level;
|
||||
voice.lfeLevel = gain.lfe_level;
|
||||
return;
|
||||
}
|
||||
if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT) {
|
||||
voice.outputId = ParamAs<Ngs2MasteringVoiceOutputParam>(param).output_id;
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
|
|
@ -199,6 +200,70 @@ static void TestUserData() {
|
|||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
static void TestMasteringGain() {
|
||||
const auto system = CreateSystem();
|
||||
const auto stereo = Mastering(system, 2);
|
||||
const auto surround = Mastering(system, 6);
|
||||
const auto quad = Mastering(system, 4);
|
||||
Control(stereo, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, 0.5f, 1.0f});
|
||||
Control(surround, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, 0.5f, 0.25f});
|
||||
Control(surround, SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT, Ngs2MasteringVoiceOutputParam{{}, 1});
|
||||
Control(quad, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, 0.5f, 0.25f});
|
||||
Control(quad, SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT, Ngs2MasteringVoiceOutputParam{{}, 2});
|
||||
const std::vector<std::int16_t> pcm(Grain, 16384);
|
||||
const auto toStereo = Sampler(system, pcm, 0);
|
||||
const float stereoLevels[2] = {1.0f, 0.5f};
|
||||
Control(toStereo, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 2, stereoLevels});
|
||||
Control(toStereo, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
||||
Patch(toStereo, stereo);
|
||||
Event(toStereo, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
const auto toSurround = Sampler(system, pcm, 0);
|
||||
const float surroundLevels[6] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||
Control(toSurround, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 6, surroundLevels});
|
||||
Control(toSurround, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
||||
Patch(toSurround, surround);
|
||||
Event(toSurround, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
const auto toQuad = Sampler(system, pcm, 0);
|
||||
Control(toQuad, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 4, surroundLevels});
|
||||
Control(toQuad, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
||||
Patch(toQuad, quad);
|
||||
Event(toQuad, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
bool rejected = false;
|
||||
try { Control(stereo, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, NAN, 1.0f}); } catch (const std::exception&) { rejected = true; }
|
||||
Require(rejected);
|
||||
|
||||
std::vector<float> outStereo(Grain * 2, -1.0f);
|
||||
std::vector<float> outSurround(Grain * 6, -1.0f);
|
||||
std::vector<float> outQuad(Grain * 4, -1.0f);
|
||||
const Ngs2RenderBufferInfo info[3] = {
|
||||
{outStereo.data(), outStereo.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2},
|
||||
{outSurround.data(), outSurround.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 6},
|
||||
{outQuad.data(), outQuad.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 4},
|
||||
};
|
||||
Require(sceNgs2SystemRender(system, info, 3) == SCE_NGS2_OK);
|
||||
for (std::uint32_t i = 0; i < Grain; i++) {
|
||||
Require(outStereo[i * 2] == 0.25f && outStereo[i * 2 + 1] == 0.125f);
|
||||
for (std::uint32_t channel = 0; channel < 6; channel++) Require(outSurround[i * 6 + channel] == (channel == 3 ? 0.125f : 0.25f));
|
||||
for (std::uint32_t channel = 0; channel < 4; channel++) Require(outQuad[i * 4 + channel] == 0.25f);
|
||||
}
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
|
||||
const auto loudSystem = CreateSystem();
|
||||
const auto loud = Mastering(loudSystem, 1);
|
||||
Control(loud, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, 2.0f, 2.0f});
|
||||
const std::vector<std::int16_t> loudPcm(Grain * 2, 24576);
|
||||
const auto toLoud = Sampler(loudSystem, loudPcm, 0);
|
||||
Patch(toLoud, loud);
|
||||
Event(toLoud, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
auto clipped = RenderI16(loudSystem);
|
||||
for (std::int16_t sample : clipped) Require(sample == 32767);
|
||||
Control(loud, SCE_NGS2_MASTERING_VOICE_PARAM_SETUP, Ngs2MasteringVoiceSetupParam{{}, 1, 0});
|
||||
Event(loud, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
auto reset = RenderI16(loudSystem);
|
||||
for (std::int16_t sample : reset) Require(sample == 24576);
|
||||
Require(sceNgs2SystemDestroy(loudSystem, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
static void TestLock() {
|
||||
const auto system = CreateSystem();
|
||||
Require(sceNgs2SystemLock(0x1234) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE);
|
||||
|
|
@ -242,6 +307,7 @@ int main() {
|
|||
TestSubmixerMatrix();
|
||||
TestSampleRate();
|
||||
TestUserData();
|
||||
TestMasteringGain();
|
||||
TestLock();
|
||||
TestAllocator();
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceKernelGetOperationMode](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the signature `(int* mode, int* submode)` comes from PPSA12544, which logs both values after the call; the values are unknown and 0 is reported for both, as in the [prosper](https://github.com/mattias800/prosper/pull/4153) reimplementation. Null outputs throw
|
||||
- Font, Http2, Net, Ssl, SystemService and libkernel exports imported by PPSA12544 and added without an implementation have unknown signatures
|
||||
- [libSceAudioPropagation](../../core/libs/prx/libSceAudioPropagation/Export.cpp) (libSceAudioPropagation) - signatures and struct descriptors recovered from PPSA21567's calls (#481), not from documentation. The system options layout is unknown (only checked for non-null); the fourth argument of `sceAudioPropagationSourceCalculateAudioPaths` is unknown; `sceAudioPropagationSourceGetAudioPath` is assumed to output an 8-byte path handle. The meaning of the `RenderInfo` word at +0x28 is unknown (PPSA21567 and PPSA21564 pass 2); only 2 is accepted. Both titles pass one `RenderInfo` per call: a count of 0 throws, and larger counts are handled element by element without proof. No error code is known, so invalid input throws instead of returning one
|
||||
- [NGS2 mastering voice gain](../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - the param id 0x30000004 is taken from PPSA22520 and the layout from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `OrbisNgs2MasteringVoiceGainParam`; no reference gives the units or range. The levels are assumed to be linear factors applied when the mastering voice is mixed into the render buffer, so the voice's own samples stay unscaled; the LFE level applies to channel 3 of 6- and 8-channel voices, assuming the L R C LFE order of the AudioOut2 entries above, and the full-band level to the rest. Negative levels are accepted, and a new setup resets both levels to 1
|
||||
- [sceAvPlayerStartEx](../../core/libs/prx/libSceAvPlayer/Export.cpp) (libSceAvPlayer) - start info layout unknown; it is ignored and playback starts as with `sceAvPlayerStart`
|
||||
- [sceAvPlayerInit / sceAvPlayerInitEx](../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - behaviour without a memory replacement unknown; frame and sample buffers then come from the guest heap
|
||||
- [SceAvPlayerVideoEx](../../core/libs/SceTypes.hpp) (libSceAvPlayer) - frame rate field and encoding unknown; it is left zero in frame and stream info
|
||||
|
|
|
|||
Loading…
Reference in New Issue