From 4a7fd91857a95dd9ba7c838384671b2a83e46e7d Mon Sep 17 00:00:00 2001 From: Chloe Marcec Date: Thu, 11 Feb 2021 18:46:20 +1100 Subject: audren: Implement I3dl2Reverb Most notable fix is the voices in Fire Emblem Three Houses --- src/audio_core/CMakeLists.txt | 2 + src/audio_core/command_generator.cpp | 355 +++++++++++++++++++++++++++++++++-- src/audio_core/command_generator.h | 5 + src/audio_core/common.h | 23 +++ src/audio_core/delay_line.cpp | 103 ++++++++++ src/audio_core/delay_line.h | 46 +++++ src/audio_core/effect_context.cpp | 22 +++ src/audio_core/effect_context.h | 31 ++- 8 files changed, 569 insertions(+), 18 deletions(-) create mode 100644 src/audio_core/delay_line.cpp create mode 100644 src/audio_core/delay_line.h diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index d1d177b51..a0ae07752 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -15,6 +15,8 @@ add_library(audio_core STATIC command_generator.cpp command_generator.h common.h + delay_line.cpp + delay_line.h effect_context.cpp effect_context.h info_updater.cpp diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 5b1065520..90d8f90d3 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "audio_core/algorithm/interpolate.h" #include "audio_core/command_generator.h" #include "audio_core/effect_context.h" @@ -13,6 +14,20 @@ namespace AudioCore { namespace { constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; +using DelayLineTimes = std::array; + +constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f}; +constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f}; +constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f}; +constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f}; +constexpr std::array EARLY_TAP_TIMES{ + 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f, + 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f, + 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f}; +constexpr std::array EARLY_GAIN{ + 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, + 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, + 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; template void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) { @@ -65,6 +80,154 @@ s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) { } } +float Pow10(float x) { + if (x >= 0.0f) { + return 1.0f; + } else if (x <= -5.3f) { + return 0.0f; + } + return std::pow(10.0f, x); +} + +float SinD(float degrees) { + return std::sinf(degrees * static_cast(std::numbers::pi) / 180.0f); +} + +float CosD(float degrees) { + return std::cosf(degrees * static_cast(std::numbers::pi) / 180.0f); +} + +float ToFloat(s32 sample) { + return static_cast(sample) / 65536.f; +} + +s32 ToS32(float sample) { + constexpr auto min = -8388608.0f; + constexpr auto max = 8388607.f; + float rescaled_sample = sample * 65536.0f; + if (rescaled_sample < min) { + rescaled_sample = min; + } + if (rescaled_sample > max) { + rescaled_sample = max; + } + return static_cast(rescaled_sample); +} + +constexpr std::array REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +constexpr std::array REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 1, 1, 1}; + +constexpr std::array REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2, + 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; + +constexpr std::array REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2, + 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; + +template +void ApplyReverbGeneric(const I3dl2ReverbParams& info, I3dl2ReverbState& state, + const std::array& input, + const std::array& output, + s32 sample_count) { + + auto GetTapLookup = []() { + if constexpr (CHANNEL_COUNT == 1) { + return REVERB_TAP_INDEX_1CH; + } else if constexpr (CHANNEL_COUNT == 2) { + return REVERB_TAP_INDEX_2CH; + } else if constexpr (CHANNEL_COUNT == 4) { + return REVERB_TAP_INDEX_4CH; + } else if constexpr (CHANNEL_COUNT == 6) { + return REVERB_TAP_INDEX_6CH; + } + }; + + const auto& tap_index_lut = GetTapLookup(); + for (s32 sample = 0; sample < sample_count; sample++) { + std::array out_samples{}; + std::array fsamp{}; + std::array mixed{}; + std::array osamp{}; + + // Mix everything into a single sample + s32 temp_mixed_sample = 0; + for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { + temp_mixed_sample += input[i][sample]; + } + const auto current_sample = ToFloat(temp_mixed_sample); + const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps); + + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) { + const auto tapped_samp = + state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i]; + out_samples[tap_index_lut[i]] += tapped_samp; + + if constexpr (CHANNEL_COUNT == 6) { + // handle lfe + out_samples[5] += tapped_samp; + } + } + + state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1; + state.early_delay_line.Tick(state.lowpass_0); + + for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { + out_samples[i] *= state.early_gain; + } + + // Two channel seems to apply a latet gain, we require to save this + f32 filter{}; + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + filter = state.fdn_delay_line[i].GetOutputSample(); + const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i]; + state.shelf_filter[i] = + filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i]; + fsamp[i] = computed; + } + + // Mixing matrix + mixed[0] = fsamp[1] + fsamp[2]; + mixed[1] = -fsamp[0] - fsamp[3]; + mixed[2] = fsamp[0] - fsamp[3]; + mixed[3] = fsamp[1] - fsamp[2]; + + if constexpr (CHANNEL_COUNT == 2) { + for (auto& mix : mixed) { + mix *= (filter * state.late_gain); + } + } + + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + const auto late = early_tap * state.late_gain; + osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]); + osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]); + state.fdn_delay_line[i].Tick(osamp[i]); + } + + if constexpr (CHANNEL_COUNT == 1) { + output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) + + (out_samples[0] + osamp[0] + osamp[1])); + } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) { + for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { + output[i][sample] = + ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); + } + } else if constexpr (CHANNEL_COUNT == 6) { + const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3])); + for (std::size_t i = 0; i < 4; i++) { + output[i][sample] = + ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); + } + output[4][sample] = + ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center)); + output[5][sample] = + ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3])); + } + } +} + } // namespace CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, @@ -271,11 +434,10 @@ void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voic } // Generate biquad filter - // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, - // dsp_state.biquad_filter_state, - // mix_buffer_count + channel, mix_buffer_count + - // channel, worker_params.sample_count, - // voice_info.GetInParams().node_id); + // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, + // dsp_state.biquad_filter_state, + // mix_buffer_count + channel, mix_buffer_count + channel, + // worker_params.sample_count, voice_info.GetInParams().node_id); } } @@ -376,21 +538,54 @@ void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) { void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) { - if (!enabled) { + auto* reverb = dynamic_cast(info); + const auto& params = reverb->GetParams(); + auto& state = reverb->GetState(); + const auto channel_count = params.channel_count; + + if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) { return; } - const auto& params = dynamic_cast(info)->GetParams(); - const auto channel_count = params.channel_count; + + std::array input{}; + std::array output{}; + + const auto status = params.status; for (s32 i = 0; i < channel_count; i++) { - // TODO(ogniK): Actually implement reverb - /* - if (params.input[i] != params.output[i]) { - const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]); - auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]); - ApplyMix<1>(output, input, 32768, worker_params.sample_count); - }*/ - auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]); - std::memset(output, 0, worker_params.sample_count * sizeof(s32)); + input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]); + output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]); + } + + if (enabled) { + if (status == ParameterStatus::Initialized) { + InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer()); + } else if (status == ParameterStatus::Updating) { + UpdateI3dl2Reverb(reverb->GetParams(), state, false); + } + } + + if (enabled) { + switch (channel_count) { + case 1: + ApplyReverbGeneric<1>(params, state, input, output, worker_params.sample_count); + break; + case 2: + ApplyReverbGeneric<2>(params, state, input, output, worker_params.sample_count); + break; + case 4: + ApplyReverbGeneric<4>(params, state, input, output, worker_params.sample_count); + break; + case 6: + ApplyReverbGeneric<6>(params, state, input, output, worker_params.sample_count); + break; + } + } else { + for (s32 i = 0; i < channel_count; i++) { + // Only copy if the buffer input and output do not match! + if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) { + std::memcpy(output[i], input[i], worker_params.sample_count * sizeof(s32)); + } + } } } @@ -528,6 +723,132 @@ s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u3 return sample_count; } +void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, + std::vector& work_buffer) { + // Reset state + state.lowpass_0 = 0.0f; + state.lowpass_1 = 0.0f; + state.lowpass_2 = 0.0f; + + state.early_delay_line.Reset(); + state.early_tap_steps.fill(0); + state.early_gain = 0.0f; + state.late_gain = 0.0f; + state.early_to_late_taps = 0; + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + state.fdn_delay_line[i].Reset(); + state.decay_delay_line0[i].Reset(); + state.decay_delay_line1[i].Reset(); + } + state.last_reverb_echo = 0.0f; + state.center_delay_line.Reset(); + for (auto& coef : state.lpf_coefficients) { + coef.fill(0.0f); + } + state.shelf_filter.fill(0.0f); + state.dry_gain = 0.0f; + + const auto sample_rate = info.sample_rate / 1000; + f32* work_buffer_ptr = reinterpret_cast(work_buffer.data()); + + s32 delay_samples{}; + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + delay_samples = + AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]); + state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr); + work_buffer_ptr += delay_samples + 1; + + delay_samples = + AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]); + state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); + work_buffer_ptr += delay_samples + 1; + + delay_samples = + AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]); + state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); + work_buffer_ptr += delay_samples + 1; + } + delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f); + state.center_delay_line.Initialize(delay_samples, work_buffer_ptr); + work_buffer_ptr += delay_samples + 1; + + delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f); + state.early_delay_line.Initialize(delay_samples, work_buffer_ptr); + + UpdateI3dl2Reverb(info, state, true); +} + +void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, + bool should_clear) { + + state.dry_gain = info.dry_gain; + state.shelf_filter.fill(0.0f); + state.lowpass_0 = 0.0f; + state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f); + state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f); + + const auto sample_rate = info.sample_rate / 1000; + const f32 hf_gain = Pow10(info.room_hf / 2000.0f); + if (hf_gain >= 1.0f) { + state.lowpass_2 = 1.0f; + state.lowpass_1 = 0.0f; + } else { + const auto a = 1.0f - hf_gain; + const auto b = + 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference / info.sample_rate)); + const auto c = std::sqrt(b * b - 4.0f * a * a); + + state.lowpass_1 = (b - c) / (2.0f * a); + state.lowpass_2 = 1.0f - state.lowpass_1; + } + state.early_to_late_taps = AudioCommon::CalculateDelaySamples( + sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay)); + + state.last_reverb_echo = 0.6f * info.diffusion * 0.01f; + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + const auto length = + FDN_MIN_DELAY_LINE_TIMES[i] + + (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]); + state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length)); + + const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() + + state.decay_delay_line0[i].GetDelay() + + state.decay_delay_line1[i].GetDelay(); + + float a = (-60.0f * delay_sample_counts) / (info.decay_time * info.sample_rate); + float b = a / info.hf_decay_ratio; + float c = CosD(128.0f * 0.5f * info.hf_reference / info.sample_rate) / + SinD(128.0f * 0.5f * info.hf_reference / info.sample_rate); + float d = Pow10((b - a) / 40.0f); + float e = Pow10((b + a) / 40.0f) * 0.7071f; + + state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d); + state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d); + state.lpf_coefficients[2][i] = (c - d) / (c + d); + + state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo); + state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo); + } + + if (should_clear) { + for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { + state.fdn_delay_line[i].Clear(); + state.decay_delay_line0[i].Clear(); + state.decay_delay_line1[i].Clear(); + } + state.early_delay_line.Clear(); + state.center_delay_line.Clear(); + } + + const auto max_early_delay = state.early_delay_line.GetMaxDelay(); + const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f); + for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { + const auto length = AudioCommon::CalculateDelaySamples( + sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); + state.early_tap_steps[tap] = std::min(length, max_early_delay); + } +} + void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel, s32 node_id) { const auto last = static_cast(last_volume * 32768.0f); diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h index b937350b1..2ebb755b0 100644 --- a/src/audio_core/command_generator.h +++ b/src/audio_core/command_generator.h @@ -21,6 +21,8 @@ class ServerMixInfo; class EffectContext; class EffectBase; struct AuxInfoDSP; +struct I3dl2ReverbParams; +struct I3dl2ReverbState; using MixVolumeBuffer = std::array; class CommandGenerator { @@ -80,6 +82,9 @@ private: s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data, u32 sample_count, u32 read_offset, u32 read_count); + void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, + std::vector& work_buffer); + void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); // DSP Code s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, s32 channel, std::size_t mix_offset); diff --git a/src/audio_core/common.h b/src/audio_core/common.h index ec59a3ba9..fe546c55d 100644 --- a/src/audio_core/common.h +++ b/src/audio_core/common.h @@ -33,6 +33,29 @@ constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this // and our const ends up being 0x3f04, the 4 bytes are most // likely the sample history constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; +constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f; +constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f; +constexpr std::size_t I3DL2REVERB_TAPS = 20; +constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4; +using Fractional = s32; + +template +constexpr Fractional ToFractional(T x) { + return static_cast(x * static_cast(0x4000)); +} + +constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) { + return static_cast(static_cast(lhs) * rhs >> 14); +} + +constexpr s32 FractionalToFixed(Fractional x) { + const auto s = x & (1 << 13); + return static_cast(x >> 14) + s; +} + +constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) { + return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time))); +} static constexpr u32 VersionFromRevision(u32_le rev) { // "REV7" -> 7 diff --git a/src/audio_core/delay_line.cpp b/src/audio_core/delay_line.cpp new file mode 100644 index 000000000..c8bc6e23e --- /dev/null +++ b/src/audio_core/delay_line.cpp @@ -0,0 +1,103 @@ +#include "audio_core/delay_line.h" + +namespace AudioCore { +DelayLineBase::DelayLineBase() = default; +DelayLineBase::~DelayLineBase() = default; + +void DelayLineBase::Initialize(s32 _max_delay, float* src_buffer) { + buffer = src_buffer; + buffer_end = buffer + _max_delay; + max_delay = _max_delay; + output = buffer; + SetDelay(_max_delay); + Clear(); +} + +void DelayLineBase::SetDelay(s32 new_delay) { + if (max_delay < new_delay) { + return; + } + delay = new_delay; + input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1)); +} + +s32 DelayLineBase::GetDelay() const { + return delay; +} + +s32 DelayLineBase::GetMaxDelay() const { + return max_delay; +} + +f32 DelayLineBase::TapOut(s32 last_sample) { + float* ptr = input - (last_sample + 1); + if (ptr < buffer) { + ptr += (max_delay + 1); + } + + return *ptr; +} + +f32 DelayLineBase::Tick(f32 sample) { + *(input++) = sample; + const auto out_sample = *(output++); + + if (buffer_end < input) { + input = buffer; + } + + if (buffer_end < output) { + output = buffer; + } + + return out_sample; +} + +float* DelayLineBase::GetInput() { + return input; +} + +const float* DelayLineBase::GetInput() const { + return input; +} + +f32 DelayLineBase::GetOutputSample() const { + return *output; +} + +void DelayLineBase::Clear() { + std::memset(buffer, 0, sizeof(float) * max_delay); +} + +void DelayLineBase::Reset() { + buffer = nullptr; + buffer_end = nullptr; + max_delay = 0; + input = nullptr; + output = nullptr; + delay = 0; +} + +DelayLineAllPass::DelayLineAllPass() = default; +DelayLineAllPass::~DelayLineAllPass() = default; + +void DelayLineAllPass::Initialize(u32 delay, float _coeffcient, f32* src_buffer) { + DelayLineBase::Initialize(delay, src_buffer); + SetCoefficient(_coeffcient); +} + +void DelayLineAllPass::SetCoefficient(float _coeffcient) { + coefficient = _coeffcient; +} + +f32 DelayLineAllPass::Tick(f32 sample) { + const auto temp = sample - coefficient * *output; + return coefficient * temp + DelayLineBase::Tick(temp); +} + +void DelayLineAllPass::Reset() { + coefficient = 0.0f; + DelayLineBase::Reset(); +} + +} // namespace AudioCore diff --git a/src/audio_core/delay_line.h b/src/audio_core/delay_line.h new file mode 100644 index 000000000..b6a6e0b12 --- /dev/null +++ b/src/audio_core/delay_line.h @@ -0,0 +1,46 @@ +#pragma once + +#include "common/common_types.h" + +namespace AudioCore { + +class DelayLineBase { +public: + DelayLineBase(); + ~DelayLineBase(); + + void Initialize(s32 _max_delay, float* src_buffer); + void SetDelay(s32 new_delay); + s32 GetDelay() const; + s32 GetMaxDelay() const; + f32 TapOut(s32 last_sample); + f32 Tick(f32 sample); + float* GetInput(); + const float* GetInput() const; + f32 GetOutputSample() const; + void Clear(); + void Reset(); + +protected: + float* buffer{nullptr}; + float* buffer_end{nullptr}; + s32 max_delay{}; + float* input{nullptr}; + float* output{nullptr}; + s32 delay{}; +}; + +class DelayLineAllPass final : public DelayLineBase { +public: + DelayLineAllPass(); + ~DelayLineAllPass(); + + void Initialize(u32 delay, float _coeffcient, f32* src_buffer); + void SetCoefficient(float _coeffcient); + f32 Tick(f32 sample); + void Reset(); + +private: + float coefficient{}; +}; +} // namespace AudioCore diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp index f770b9608..eeee8e325 100644 --- a/src/audio_core/effect_context.cpp +++ b/src/audio_core/effect_context.cpp @@ -90,6 +90,14 @@ s32 EffectBase::GetProcessingOrder() const { return processing_order; } +std::vector& EffectBase::GetWorkBuffer() { + return work_buffer; +} + +const std::vector& EffectBase::GetWorkBuffer() const { + return work_buffer; +} + EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {} EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; @@ -117,6 +125,12 @@ void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) { usage = UsageState::Initialized; params.status = ParameterStatus::Initialized; skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; + if (!skipped) { + auto& work_buffer = GetWorkBuffer(); + // Has two buffers internally + work_buffer.resize(in_params.buffer_size * 2); + std::fill(work_buffer.begin(), work_buffer.end(), 0); + } } } @@ -129,6 +143,14 @@ void EffectI3dl2Reverb::UpdateForCommandGeneration() { GetParams().status = ParameterStatus::Updated; } +I3dl2ReverbState& EffectI3dl2Reverb::GetState() { + return state; +} + +const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const { + return state; +} + EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {} EffectBiquadFilter::~EffectBiquadFilter() = default; diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h index c5e0b398c..5e0655dd7 100644 --- a/src/audio_core/effect_context.h +++ b/src/audio_core/effect_context.h @@ -8,6 +8,7 @@ #include #include #include "audio_core/common.h" +#include "audio_core/delay_line.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -194,6 +195,8 @@ public: [[nodiscard]] bool IsEnabled() const; [[nodiscard]] s32 GetMixID() const; [[nodiscard]] s32 GetProcessingOrder() const; + [[nodiscard]] std::vector& GetWorkBuffer(); + [[nodiscard]] const std::vector& GetWorkBuffer() const; protected: UsageState usage{UsageState::Invalid}; @@ -201,6 +204,7 @@ protected: s32 mix_id{}; s32 processing_order{}; bool enabled = false; + std::vector work_buffer{}; }; template @@ -212,7 +216,7 @@ public: return internal_params; } - const I3dl2ReverbParams& GetParams() const { + const T& GetParams() const { return internal_params; } @@ -229,6 +233,27 @@ public: void UpdateForCommandGeneration() override; }; +struct I3dl2ReverbState { + f32 lowpass_0{}; + f32 lowpass_1{}; + f32 lowpass_2{}; + + DelayLineBase early_delay_line{}; + std::array early_tap_steps{}; + f32 early_gain{}; + f32 late_gain{}; + + u32 early_to_late_taps{}; + std::array fdn_delay_line{}; + std::array decay_delay_line0{}; + std::array decay_delay_line1{}; + f32 last_reverb_echo{}; + DelayLineBase center_delay_line{}; + std::array, 3> lpf_coefficients{}; + std::array shelf_filter{}; + f32 dry_gain{}; +}; + class EffectI3dl2Reverb : public EffectGeneric { public: explicit EffectI3dl2Reverb(); @@ -237,8 +262,12 @@ public: void Update(EffectInfo::InParams& in_params) override; void UpdateForCommandGeneration() override; + I3dl2ReverbState& GetState(); + const I3dl2ReverbState& GetState() const; + private: bool skipped = false; + I3dl2ReverbState state{}; }; class EffectBiquadFilter : public EffectGeneric { -- cgit v1.2.3