From 2f6dccb10b69b726cac2e4804aedf30c537da8a4 Mon Sep 17 00:00:00 2001 From: nmzik Date: Fri, 30 Oct 2026 01:19:51 +0100 Subject: [PATCH] ngs2: mix ports without matrix levels with the default matrix Co-authored-by: Phr3d13 --- src/libs/ngs2.cpp | 19 ++++++++++++++++++- tests/Ngs2SamplerTests.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/libs/ngs2.cpp b/src/libs/ngs2.cpp index 58fbfcd93..eb09457d7 100644 --- a/src/libs/ngs2.cpp +++ b/src/libs/ngs2.cpp @@ -1803,6 +1803,18 @@ static void Ngs2ConsumeSamples(Ngs2VoiceInternal& voice, uint32_t grain) { voice.has_samples = output != 0; } +// Mono and stereo convert at -3 dB, mono feeds the surround front center, others map 1:1. +static float Ngs2DefaultMatrixLevel(uint32_t src_channels, uint32_t src, uint32_t dst_channels, + size_t dst) { + if ((src_channels == 1 && dst_channels == 2) || (src_channels == 2 && dst_channels == 1)) { + return std::numbers::sqrt2_v / 2; + } + if (src_channels == 1 && dst_channels > 2) { + return dst == 2 ? 1.0f : 0.0f; + } + return src == dst ? 1.0f : 0.0f; +} + static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector& voices, uint32_t grain) { if (voice.rendered) { @@ -1851,7 +1863,11 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector= voice.rack->NumBuffers()); const auto* input = source->Buffer(source->rack->PortBufferId(p), grain); auto* output = voice.Buffer(port.input, grain); + // A port without a matrix, or whose matrix has no levels, uses the default mix. const auto* matrix = port.matrix < 0 ? nullptr : &source->matrices.at(port.matrix); + if (matrix != nullptr && matrix->empty()) { + matrix = nullptr; + } const size_t output_channels = matrix == nullptr ? voice.channels @@ -1860,7 +1876,8 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vectorchannels; ++src) { const float level = port.volume * (matrix == nullptr - ? (src == dst ? 1.0f : 0.0f) + ? Ngs2DefaultMatrixLevel(source->channels, src, + voice.channels, dst) : (*matrix)[dst * source->channels + src]); for (uint32_t i = 0; i < grain; ++i) { output[dst * grain + i] += input[src * grain + i] * level; diff --git a/tests/Ngs2SamplerTests.cpp b/tests/Ngs2SamplerTests.cpp index c796ee5d7..de0dec4ff 100644 --- a/tests/Ngs2SamplerTests.cpp +++ b/tests/Ngs2SamplerTests.cpp @@ -1096,6 +1096,35 @@ void TestMonoRateAndRouting() { "22.05 kHz effect duration was incorrect"); } +void TestDefaultMatrix() { + struct Case { + uint32_t src_channels; + int32_t matrix; + uint32_t dst_channels; + std::vector expected; + }; + constexpr float minus_3db = std::numbers::sqrt2_v / 2; + const int16_t pcm[] = {8192, -16384}; + for (const auto& c: {Case {1, -1, 2, {0.25f * minus_3db, 0.25f * minus_3db}}, + Case {1, 0, 2, {0.25f * minus_3db, 0.25f * minus_3db}}, + Case {2, -1, 1, {0.25f * minus_3db + -0.5f * minus_3db}}, + Case {1, -1, 6, {0, 0, 0.25f, 0, 0, 0}}}) { + Fixture f(48000, c.src_channels); + Ngs2RackInternal rack {}; + rack.ngs = &f.system; + rack.type = Ngs2RackType::Mastering; + Ngs2VoiceInternal dest; + dest.rack = &rack; + dest.channels = c.dst_channels; + dest.state = Ngs2VoicePlayState::Playing; + f.voice.matrices.resize(1); + f.voice.ports.push_back({&dest, 0, 1.0f, c.matrix}); + f.Queue(pcm, 1, 0); + Ngs2RenderVoice(dest, {&f.voice, &dest}, 1); + Check(dest.samples == c.expected, "default port mix produced the wrong channel levels"); + } +} + void TestMatrixOutputChannels() { Fixture f(48000, 2); Ngs2RackInternal mastering_rack {}; @@ -1933,6 +1962,7 @@ int main() { TestPcmSkipAcrossBlocks(); TestPcmBlocksEndWithTheirData(); TestMonoRateAndRouting(); + TestDefaultMatrix(); TestMatrixOutputChannels(); TestCustomPcmStillPlays(); TestSetupQuery();