audren: Implement I3dl2Reverb
Most notable fix is the voices in Fire Emblem Three Houses
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include <numbers> | ||||
| #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<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>; | ||||
|  | ||||
| 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<f32, AudioCommon::I3DL2REVERB_TAPS> 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<f32, AudioCommon::I3DL2REVERB_TAPS> 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 <std::size_t N> | ||||
| 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<float>(std::numbers::pi) / 180.0f); | ||||
| } | ||||
|  | ||||
| float CosD(float degrees) { | ||||
|     return std::cosf(degrees * static_cast<float>(std::numbers::pi) / 180.0f); | ||||
| } | ||||
|  | ||||
| float ToFloat(s32 sample) { | ||||
|     return static_cast<float>(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<s32>(rescaled_sample); | ||||
| } | ||||
|  | ||||
| constexpr std::array<std::size_t, 20> 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<std::size_t, 20> 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<std::size_t, 20> 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<std::size_t, 20> 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 <std::size_t CHANNEL_COUNT> | ||||
| void ApplyReverbGeneric(const I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||||
|                         const std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT>& input, | ||||
|                         const std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT>& 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<f32, CHANNEL_COUNT> out_samples{}; | ||||
|         std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{}; | ||||
|         std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{}; | ||||
|         std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> 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<EffectI3dl2Reverb*>(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<EffectI3dl2Reverb*>(info)->GetParams(); | ||||
|     const auto channel_count = params.channel_count; | ||||
|  | ||||
|     std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT> input{}; | ||||
|     std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT> 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<u8>& 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<f32*>(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<s32>(last_volume * 32768.0f); | ||||
|   | ||||
| @@ -21,6 +21,8 @@ class ServerMixInfo; | ||||
| class EffectContext; | ||||
| class EffectBase; | ||||
| struct AuxInfoDSP; | ||||
| struct I3dl2ReverbParams; | ||||
| struct I3dl2ReverbState; | ||||
| using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; | ||||
|  | ||||
| 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<u8>& 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); | ||||
|   | ||||
| @@ -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 <typename T> | ||||
| constexpr Fractional ToFractional(T x) { | ||||
|     return static_cast<Fractional>(x * static_cast<T>(0x4000)); | ||||
| } | ||||
|  | ||||
| constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) { | ||||
|     return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14); | ||||
| } | ||||
|  | ||||
| constexpr s32 FractionalToFixed(Fractional x) { | ||||
|     const auto s = x & (1 << 13); | ||||
|     return static_cast<s32>(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 | ||||
|   | ||||
							
								
								
									
										103
									
								
								src/audio_core/delay_line.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/audio_core/delay_line.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										46
									
								
								src/audio_core/delay_line.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/audio_core/delay_line.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -90,6 +90,14 @@ s32 EffectBase::GetProcessingOrder() const { | ||||
|     return processing_order; | ||||
| } | ||||
|  | ||||
| std::vector<u8>& EffectBase::GetWorkBuffer() { | ||||
|     return work_buffer; | ||||
| } | ||||
|  | ||||
| const std::vector<u8>& 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; | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #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<u8>& GetWorkBuffer(); | ||||
|     [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const; | ||||
|  | ||||
| protected: | ||||
|     UsageState usage{UsageState::Invalid}; | ||||
| @@ -201,6 +204,7 @@ protected: | ||||
|     s32 mix_id{}; | ||||
|     s32 processing_order{}; | ||||
|     bool enabled = false; | ||||
|     std::vector<u8> work_buffer{}; | ||||
| }; | ||||
|  | ||||
| template <typename T> | ||||
| @@ -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<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{}; | ||||
|     f32 early_gain{}; | ||||
|     f32 late_gain{}; | ||||
|  | ||||
|     u32 early_to_late_taps{}; | ||||
|     std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{}; | ||||
|     std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{}; | ||||
|     std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{}; | ||||
|     f32 last_reverb_echo{}; | ||||
|     DelayLineBase center_delay_line{}; | ||||
|     std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{}; | ||||
|     std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{}; | ||||
|     f32 dry_gain{}; | ||||
| }; | ||||
|  | ||||
| class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { | ||||
| 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<BiquadFilterParams> { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user