mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 06:16:55 +01:00
ngs2: apply custom channel matrices before mastering output
Co-authored-by: Frenzied <frenziedsaint@gmail.com>
This commit is contained in:
+43
-2
@@ -546,6 +546,7 @@ struct Ngs2VoiceInternal {
|
||||
};
|
||||
struct Module {
|
||||
std::vector<uint8_t> param, work, state;
|
||||
std::vector<float> channel_matrix;
|
||||
void KYTY_SYSV_ABI (*handler)(Ngs2UserFxProcessContext*) = nullptr;
|
||||
std::array<uintptr_t, 3> user_data {};
|
||||
uint32_t flags = 1;
|
||||
@@ -563,6 +564,7 @@ struct Ngs2VoiceInternal {
|
||||
std::vector<std::vector<float>> matrices;
|
||||
std::vector<Module> modules;
|
||||
std::vector<float> samples;
|
||||
std::vector<float> mix_samples;
|
||||
std::vector<float> output_matrix;
|
||||
uint32_t channels = 0;
|
||||
uint32_t output_id = 0;
|
||||
@@ -614,6 +616,27 @@ struct Ngs2VoiceInternal {
|
||||
return samples.data() + size_t(id) * grain * channels;
|
||||
}
|
||||
|
||||
void MixChannels(const std::vector<float>& matrix, uint32_t buffer, uint32_t grain) {
|
||||
if (matrix.empty()) {
|
||||
return;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(channels > 37);
|
||||
auto* input = Buffer(buffer, grain);
|
||||
mix_samples.assign(size_t(channels) * grain, 0.0f);
|
||||
for (uint32_t dst = 0; dst < channels; ++dst) {
|
||||
for (uint32_t src = 0; src < channels; ++src) {
|
||||
const float level = matrix[dst * 37 + src];
|
||||
if (level == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t i = 0; i < grain; ++i) {
|
||||
mix_samples[dst * grain + i] += input[src * grain + i] * level;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::copy(mix_samples.begin(), mix_samples.end(), input);
|
||||
}
|
||||
|
||||
void ResetSetupState() {
|
||||
SetEvent(4);
|
||||
std::ranges::fill(ports, Port {});
|
||||
@@ -1850,6 +1873,10 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
|
||||
for (size_t m = 0; m < voice.modules.size(); ++m) {
|
||||
const auto& info = custom.module[m];
|
||||
auto& module = voice.modules[m];
|
||||
if (info.module_id == 0x17) {
|
||||
voice.MixChannels(module.channel_matrix, info.source_buffer_id, grain);
|
||||
continue;
|
||||
}
|
||||
if (info.module_id != 0x19 && info.module_id != 0x1f) {
|
||||
if (!module.render_warned) {
|
||||
Log::WriteToConsoleAndLog(fmt::sprintf(
|
||||
@@ -1934,7 +1961,8 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
|
||||
EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info);
|
||||
const auto& output = buffer_info[voice->output_id];
|
||||
EXIT_NOT_IMPLEMENTED(output.buffer == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED((output.waveform_type != 0x12 && output.waveform_type != 0x18) ||
|
||||
EXIT_NOT_IMPLEMENTED(output.waveform_type != 0x12 && output.waveform_type != 0x18);
|
||||
EXIT_NOT_IMPLEMENTED(voice->rack->type == Ngs2RackType::Mastering &&
|
||||
output.num_channels != voice->channels);
|
||||
EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels *
|
||||
(output.waveform_type == 0x12 ? sizeof(int16_t)
|
||||
@@ -1954,6 +1982,9 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
|
||||
const float level = voice->output_matrix.empty()
|
||||
? (src == dst ? 1.0f : 0.0f)
|
||||
: voice->output_matrix.at(src * output.num_channels + dst);
|
||||
if (level == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t i = 0; i < grain; ++i) {
|
||||
pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level;
|
||||
}
|
||||
@@ -2452,7 +2483,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
||||
voice->rack->option.custom_sampler.custom_rack_option.module[index].module_id;
|
||||
auto& module = voice->modules[index];
|
||||
if ((param->id & 0xffffffe0u) != (0x40000000u | module_id << 8u) ||
|
||||
(module_id != 0x19 && module_id != 0x1f)) {
|
||||
(module_id != 0x17 && module_id != 0x19 && module_id != 0x1f)) {
|
||||
if (!module.control_warned) {
|
||||
Log::WriteToConsoleAndLog(fmt::sprintf(
|
||||
"warning: NGS2 custom control 0x%08" PRIx32 " is ignored\n",
|
||||
@@ -2461,6 +2492,16 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (module_id == 0x17) {
|
||||
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceParamHeader) +
|
||||
37 * 37 * sizeof(float));
|
||||
const auto* levels = reinterpret_cast<const float*>(param + 1);
|
||||
module.channel_matrix.resize(37 * 37);
|
||||
std::transform(levels, levels + module.channel_matrix.size(),
|
||||
module.channel_matrix.begin(),
|
||||
[](float level) { return std::clamp(level, -32.0f, 32.0f); });
|
||||
break;
|
||||
}
|
||||
if (module_id == 0x19) {
|
||||
struct FxParam {
|
||||
Ngs2VoiceParamHeader header;
|
||||
|
||||
+85
-19
@@ -751,6 +751,26 @@ void TestCustomSideChainUserFx() {
|
||||
"user FX did not receive the side-chain buffer after the source channels");
|
||||
}
|
||||
|
||||
struct RenderRack {
|
||||
std::unique_ptr<std::byte[]> storage;
|
||||
uintptr_t handle = 0;
|
||||
|
||||
RenderRack(Ngs2Internal& system, uint32_t id, const Ngs2RackOption& option) {
|
||||
Ngs2ContextBufferInfo buffer {};
|
||||
Check(Ngs2RackQueryBufferSize(id, &option, &buffer) == OK, "rack size query failed");
|
||||
storage = std::make_unique<std::byte[]>(buffer.host_buffer_size);
|
||||
buffer.host_buffer = storage.get();
|
||||
Check(Ngs2RackCreate(reinterpret_cast<uintptr_t>(&system), id, &option, &buffer,
|
||||
&handle) == OK, "render rack creation failed");
|
||||
}
|
||||
|
||||
~RenderRack() { Check(Ngs2RackDestroy(handle, nullptr) == OK, "render rack cleanup failed"); }
|
||||
|
||||
Ngs2VoiceInternal& Voice() {
|
||||
return *reinterpret_cast<Ngs2VoiceInternal*>(reinterpret_cast<Ngs2RackInternal*>(handle) + 1);
|
||||
}
|
||||
};
|
||||
|
||||
void TestPcm16RenderOutput() {
|
||||
Ngs2Internal system;
|
||||
system.option = Ngs2DefaultSystemOption();
|
||||
@@ -760,23 +780,11 @@ void TestPcm16RenderOutput() {
|
||||
Ngs2FillDefaultRackOption(0x3000, &mastering_option);
|
||||
sampler_option.common.max_voices = 2;
|
||||
sampler_option.common.max_ports = 1;
|
||||
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal)
|
||||
std::byte sampler_storage[sizeof(Ngs2RackInternal) + 2 * sizeof(Ngs2VoiceInternal)];
|
||||
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal)
|
||||
std::byte mastering_storage[sizeof(Ngs2RackInternal) + sizeof(Ngs2VoiceInternal)];
|
||||
Ngs2ContextBufferInfo sampler_buffer {sampler_storage, sizeof(sampler_storage)};
|
||||
Ngs2ContextBufferInfo mastering_buffer {mastering_storage, sizeof(mastering_storage)};
|
||||
uintptr_t sampler_handle = 0, mastering_handle = 0;
|
||||
RenderRack sampler(system, 0x1000, sampler_option.common);
|
||||
RenderRack master(system, 0x3000, mastering_option.common);
|
||||
const auto system_handle = reinterpret_cast<uintptr_t>(&system);
|
||||
Check(Ngs2RackCreate(system_handle, 0x1000, &sampler_option.common, &sampler_buffer,
|
||||
&sampler_handle) == OK &&
|
||||
Ngs2RackCreate(system_handle, 0x3000, &mastering_option.common,
|
||||
&mastering_buffer, &mastering_handle) == OK,
|
||||
"PCM16 render rack creation failed");
|
||||
auto* samplers = reinterpret_cast<Ngs2VoiceInternal*>(
|
||||
reinterpret_cast<Ngs2RackInternal*>(sampler_handle) + 1);
|
||||
auto& mastering = *reinterpret_cast<Ngs2VoiceInternal*>(
|
||||
reinterpret_cast<Ngs2RackInternal*>(mastering_handle) + 1);
|
||||
auto* samplers = &sampler.Voice();
|
||||
auto& mastering = master.Voice();
|
||||
mastering.channels = 1;
|
||||
mastering.state = Ngs2VoicePlayState::Playing;
|
||||
const int16_t source[2][4] = {{16384, -16384, 32767, -32768},
|
||||
@@ -797,9 +805,66 @@ void TestPcm16RenderOutput() {
|
||||
Check(Ngs2SystemRender(system_handle, &output, 1) == OK &&
|
||||
std::ranges::all_of(output_samples, [](int16_t sample) { return sample == 0; }),
|
||||
"PCM16 output retained samples from the previous render");
|
||||
Check(Ngs2RackDestroy(mastering_handle, nullptr) == OK &&
|
||||
Ngs2RackDestroy(sampler_handle, nullptr) == OK,
|
||||
"PCM16 render rack cleanup failed");
|
||||
}
|
||||
|
||||
void TestCustomMasteringChannelMixer() {
|
||||
Ngs2Internal system;
|
||||
system.option = Ngs2DefaultSystemOption();
|
||||
system.option.num_grain_samples = 6;
|
||||
Ngs2RackOptionUnion sampler_option {};
|
||||
Ngs2FillDefaultRackOption(0x1000, &sampler_option);
|
||||
sampler_option.common.max_voices = 1;
|
||||
sampler_option.common.max_ports = 1;
|
||||
Ngs2CustomMasteringRackOption master_option {};
|
||||
auto& custom = master_option.custom_rack_option;
|
||||
custom.rack_option.size = sizeof(master_option);
|
||||
custom.rack_option.max_voices = 1;
|
||||
custom.rack_option.max_voice_channels = 6;
|
||||
custom.num_buffers = 1;
|
||||
custom.num_modules = 1;
|
||||
custom.module[0].module_id = 0x17;
|
||||
RenderRack sampler(system, 0x1000, sampler_option.common);
|
||||
RenderRack master(system, 0x4003, custom.rack_option);
|
||||
auto& source = sampler.Voice();
|
||||
auto& output = master.Voice();
|
||||
output.channels = 6;
|
||||
output.SetEvent(1);
|
||||
struct {
|
||||
Ngs2VoiceParamHeader header {5484, 0, 0x40001700};
|
||||
float levels[37][37] {};
|
||||
} matrix;
|
||||
constexpr float center = 0.70710677f;
|
||||
matrix.levels[0][0] = matrix.levels[1][1] = 1.0f;
|
||||
matrix.levels[0][2] = matrix.levels[1][2] = center;
|
||||
matrix.levels[0][4] = matrix.levels[1][5] = center;
|
||||
int16_t impulses[6][6] {};
|
||||
for (uint32_t c = 0; c < 6; ++c) impulses[c][c] = 8192;
|
||||
for (unsigned pass = 0; pass < 2; ++pass) {
|
||||
if (pass == 1) {
|
||||
std::memset(matrix.levels, 0, sizeof(matrix.levels));
|
||||
matrix.levels[0][1] = 64.0f;
|
||||
matrix.levels[1][0] = -64.0f;
|
||||
}
|
||||
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&output), &matrix.header) == OK,
|
||||
"custom channel mixer control failed");
|
||||
source.SetupSampler({0x12, 6, 48000});
|
||||
source.blocks.push_back({reinterpret_cast<const uint8_t*>(impulses),
|
||||
{0, sizeof(impulses), 0, 0, 6}});
|
||||
source.ports[0].dest = &output;
|
||||
source.SetEvent(1);
|
||||
std::array<float, 14> pcm;
|
||||
pcm.fill(99.0f);
|
||||
Ngs2RenderBufferInfo buffer {pcm.data() + 1, 12 * sizeof(float), 0x18, 2};
|
||||
Check(Ngs2SystemRender(reinterpret_cast<uintptr_t>(&system), &buffer, 1) == OK,
|
||||
"custom mastering rejected stereo output");
|
||||
const std::array<float, 12> expected = pass == 0
|
||||
? std::array<float, 12> {0.25f, 0, 0, 0.25f, center / 4, center / 4,
|
||||
0, 0, center / 4, 0, 0, center / 4}
|
||||
: std::array<float, 12> {0, -8, 8, 0};
|
||||
Check(pcm.front() == 99 && pcm.back() == 99 &&
|
||||
std::equal(expected.begin(), expected.end(), pcm.begin() + 1),
|
||||
"custom mixer lost channel routing, in-place input, clamping or output bounds");
|
||||
}
|
||||
}
|
||||
|
||||
void TestPitchLoopAndSkip() {
|
||||
@@ -1700,6 +1765,7 @@ int main() {
|
||||
TestAtrac9Vibration();
|
||||
TestAtrac9Discrete();
|
||||
TestPcm16RenderOutput();
|
||||
TestCustomMasteringChannelMixer();
|
||||
TestSubmixerSetupAndRouting();
|
||||
TestCustomSideChainUserFx();
|
||||
TestWaveformBlockGeometry();
|
||||
|
||||
Reference in New Issue
Block a user