diff --git a/src/libs/ngs2.cpp b/src/libs/ngs2.cpp index 5f0be1ef4..58fbfcd93 100644 --- a/src/libs/ngs2.cpp +++ b/src/libs/ngs2.cpp @@ -570,6 +570,7 @@ struct Ngs2VoiceInternal { uint32_t output_id = 0; float fbw_level = 1.0f; float lfe_level = 1.0f; + float dry_level = 0.0f; bool rendering = false; bool rendered = false; bool has_samples = false; @@ -1869,6 +1870,11 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vectortype == Ngs2RackType::Reverb) { + for (auto& sample: voice.samples) { + sample *= voice.dry_level; + } + } if (voice.has_samples) { const auto& custom = voice.rack->option.custom_sampler.custom_rack_option; const auto rate = voice.rack->ngs->option.sample_rate; @@ -2472,7 +2478,31 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH } break; } - case 0x2001: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb); break; + case 0x2001: { + EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb); + const auto* values = reinterpret_cast(param + 1); + switch (param->id & 0xffffu) { + case 0: + // Input and output channels, flags; channel conversion stays silent. + EXIT_NOT_IMPLEMENTED(values[0] == 0 || values[2] != 0); + voice->ResetSetupState(); + voice->channels = values[1] == values[0] ? values[0] : 0; + break; + case 1: { + // The reverb parameters start with the wet and dry levels. + const auto* levels = reinterpret_cast(values); + voice->dry_level = std::clamp(levels[1], 0.0f, 1.0f); + + static std::atomic_bool wet_warned = false; + if (levels[0] != 0.0f && !wet_warned.exchange(true)) { + Log::WriteToConsoleAndLog("warning: NGS2 reverb wet signal is muted\n"); + } + break; + } + default: EXIT("unsupported reverb control: 0x%08" PRIx32 "\n", param->id); + } + break; + } case 0x3000: { EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering); const auto* values = reinterpret_cast(param + 1); diff --git a/tests/Ngs2SamplerTests.cpp b/tests/Ngs2SamplerTests.cpp index 59d72a018..c796ee5d7 100644 --- a/tests/Ngs2SamplerTests.cpp +++ b/tests/Ngs2SamplerTests.cpp @@ -854,6 +854,29 @@ void TestMasteringGain() { "mastering gain was not clamped or applied per full band and LFE channel"); } +void TestReverbDryLevel() { + Fixture f(48000); + Ngs2RackInternal reverb_rack {}; + reverb_rack.ngs = &f.system; + reverb_rack.type = Ngs2RackType::Reverb; + Ngs2VoiceInternal reverb; + reverb.rack = &reverb_rack; + f.voice.ports.push_back({&reverb, 0, 1.0f, -1}); + const int16_t pcm = 8192; + f.Queue(&pcm, 1, 0); + struct { + Ngs2VoiceParamHeader header {24, 24, 0x20010000}; + uint32_t channels[4] = {1, 1, 0, 0}; + Ngs2VoiceParamHeader levels_header {96, 0, 0x20010001}; + float levels[22] = {0.6f, 0.4f}; + } setup; + Ngs2VoiceControl(reinterpret_cast(&reverb), &setup.header); + reverb.SetEvent(1); + Ngs2RenderVoice(reverb, {&f.voice, &reverb}, 1); + Check(reverb.has_samples && reverb.samples[0] == 0.25f * 0.4f, + "reverb did not pass its dry signal"); +} + void TestCustomMasteringChannelMixer() { Ngs2Internal system; system.option = Ngs2DefaultSystemOption(); @@ -1875,6 +1898,7 @@ int main() { TestPcm16RenderOutput(); TestPatchToRackHandle(); TestMasteringGain(); + TestReverbDryLevel(); TestCustomMasteringChannelMixer(); TestCustomVoiceStateCapacity(); TestSubmixerSetupAndRouting();