diff options
| author | 2021-02-11 18:46:20 +1100 | |
|---|---|---|
| committer | 2021-02-12 18:48:10 -0800 | |
| commit | 4a7fd91857a95dd9ba7c838384671b2a83e46e7d (patch) | |
| tree | 77dc77e963c2ce5e386d747b76ba19bec89a92b6 | |
| parent | Merge pull request #5877 from ameerj/res-limit-usage (diff) | |
| download | yuzu-4a7fd91857a95dd9ba7c838384671b2a83e46e7d.tar.gz yuzu-4a7fd91857a95dd9ba7c838384671b2a83e46e7d.tar.xz yuzu-4a7fd91857a95dd9ba7c838384671b2a83e46e7d.zip | |
audren: Implement I3dl2Reverb
Most notable fix is the voices in Fire Emblem Three Houses
| -rw-r--r-- | src/audio_core/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/audio_core/command_generator.cpp | 355 | ||||
| -rw-r--r-- | src/audio_core/command_generator.h | 5 | ||||
| -rw-r--r-- | src/audio_core/common.h | 23 | ||||
| -rw-r--r-- | src/audio_core/delay_line.cpp | 103 | ||||
| -rw-r--r-- | src/audio_core/delay_line.h | 46 | ||||
| -rw-r--r-- | src/audio_core/effect_context.cpp | 22 | ||||
| -rw-r--r-- | src/audio_core/effect_context.h | 31 |
8 files changed, 569 insertions, 18 deletions
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 | |||
| 15 | command_generator.cpp | 15 | command_generator.cpp |
| 16 | command_generator.h | 16 | command_generator.h |
| 17 | common.h | 17 | common.h |
| 18 | delay_line.cpp | ||
| 19 | delay_line.h | ||
| 18 | effect_context.cpp | 20 | effect_context.cpp |
| 19 | effect_context.h | 21 | effect_context.h |
| 20 | info_updater.cpp | 22 | 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 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <numbers> | ||
| 5 | #include "audio_core/algorithm/interpolate.h" | 6 | #include "audio_core/algorithm/interpolate.h" |
| 6 | #include "audio_core/command_generator.h" | 7 | #include "audio_core/command_generator.h" |
| 7 | #include "audio_core/effect_context.h" | 8 | #include "audio_core/effect_context.h" |
| @@ -13,6 +14,20 @@ namespace AudioCore { | |||
| 13 | namespace { | 14 | namespace { |
| 14 | constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; | 15 | constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; |
| 15 | constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; | 16 | constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; |
| 17 | using DelayLineTimes = std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>; | ||
| 18 | |||
| 19 | constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f}; | ||
| 20 | constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f}; | ||
| 21 | constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f}; | ||
| 22 | constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f}; | ||
| 23 | constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_TAP_TIMES{ | ||
| 24 | 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f, | ||
| 25 | 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f, | ||
| 26 | 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f}; | ||
| 27 | constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{ | ||
| 28 | 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, | ||
| 29 | 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, | ||
| 30 | 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; | ||
| 16 | 31 | ||
| 17 | template <std::size_t N> | 32 | template <std::size_t N> |
| 18 | void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) { | 33 | 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) { | |||
| 65 | } | 80 | } |
| 66 | } | 81 | } |
| 67 | 82 | ||
| 83 | float Pow10(float x) { | ||
| 84 | if (x >= 0.0f) { | ||
| 85 | return 1.0f; | ||
| 86 | } else if (x <= -5.3f) { | ||
| 87 | return 0.0f; | ||
| 88 | } | ||
| 89 | return std::pow(10.0f, x); | ||
| 90 | } | ||
| 91 | |||
| 92 | float SinD(float degrees) { | ||
| 93 | return std::sinf(degrees * static_cast<float>(std::numbers::pi) / 180.0f); | ||
| 94 | } | ||
| 95 | |||
| 96 | float CosD(float degrees) { | ||
| 97 | return std::cosf(degrees * static_cast<float>(std::numbers::pi) / 180.0f); | ||
| 98 | } | ||
| 99 | |||
| 100 | float ToFloat(s32 sample) { | ||
| 101 | return static_cast<float>(sample) / 65536.f; | ||
| 102 | } | ||
| 103 | |||
| 104 | s32 ToS32(float sample) { | ||
| 105 | constexpr auto min = -8388608.0f; | ||
| 106 | constexpr auto max = 8388607.f; | ||
| 107 | float rescaled_sample = sample * 65536.0f; | ||
| 108 | if (rescaled_sample < min) { | ||
| 109 | rescaled_sample = min; | ||
| 110 | } | ||
| 111 | if (rescaled_sample > max) { | ||
| 112 | rescaled_sample = max; | ||
| 113 | } | ||
| 114 | return static_cast<s32>(rescaled_sample); | ||
| 115 | } | ||
| 116 | |||
| 117 | constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 118 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | ||
| 119 | |||
| 120 | constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0, | ||
| 121 | 1, 1, 1, 0, 0, 0, 0, 1, 1, 1}; | ||
| 122 | |||
| 123 | constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2, | ||
| 124 | 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; | ||
| 125 | |||
| 126 | constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2, | ||
| 127 | 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; | ||
| 128 | |||
| 129 | template <std::size_t CHANNEL_COUNT> | ||
| 130 | void ApplyReverbGeneric(const I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||
| 131 | const std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT>& input, | ||
| 132 | const std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT>& output, | ||
| 133 | s32 sample_count) { | ||
| 134 | |||
| 135 | auto GetTapLookup = []() { | ||
| 136 | if constexpr (CHANNEL_COUNT == 1) { | ||
| 137 | return REVERB_TAP_INDEX_1CH; | ||
| 138 | } else if constexpr (CHANNEL_COUNT == 2) { | ||
| 139 | return REVERB_TAP_INDEX_2CH; | ||
| 140 | } else if constexpr (CHANNEL_COUNT == 4) { | ||
| 141 | return REVERB_TAP_INDEX_4CH; | ||
| 142 | } else if constexpr (CHANNEL_COUNT == 6) { | ||
| 143 | return REVERB_TAP_INDEX_6CH; | ||
| 144 | } | ||
| 145 | }; | ||
| 146 | |||
| 147 | const auto& tap_index_lut = GetTapLookup(); | ||
| 148 | for (s32 sample = 0; sample < sample_count; sample++) { | ||
| 149 | std::array<f32, CHANNEL_COUNT> out_samples{}; | ||
| 150 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{}; | ||
| 151 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{}; | ||
| 152 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> osamp{}; | ||
| 153 | |||
| 154 | // Mix everything into a single sample | ||
| 155 | s32 temp_mixed_sample = 0; | ||
| 156 | for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { | ||
| 157 | temp_mixed_sample += input[i][sample]; | ||
| 158 | } | ||
| 159 | const auto current_sample = ToFloat(temp_mixed_sample); | ||
| 160 | const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps); | ||
| 161 | |||
| 162 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) { | ||
| 163 | const auto tapped_samp = | ||
| 164 | state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i]; | ||
| 165 | out_samples[tap_index_lut[i]] += tapped_samp; | ||
| 166 | |||
| 167 | if constexpr (CHANNEL_COUNT == 6) { | ||
| 168 | // handle lfe | ||
| 169 | out_samples[5] += tapped_samp; | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1; | ||
| 174 | state.early_delay_line.Tick(state.lowpass_0); | ||
| 175 | |||
| 176 | for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { | ||
| 177 | out_samples[i] *= state.early_gain; | ||
| 178 | } | ||
| 179 | |||
| 180 | // Two channel seems to apply a latet gain, we require to save this | ||
| 181 | f32 filter{}; | ||
| 182 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 183 | filter = state.fdn_delay_line[i].GetOutputSample(); | ||
| 184 | const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i]; | ||
| 185 | state.shelf_filter[i] = | ||
| 186 | filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i]; | ||
| 187 | fsamp[i] = computed; | ||
| 188 | } | ||
| 189 | |||
| 190 | // Mixing matrix | ||
| 191 | mixed[0] = fsamp[1] + fsamp[2]; | ||
| 192 | mixed[1] = -fsamp[0] - fsamp[3]; | ||
| 193 | mixed[2] = fsamp[0] - fsamp[3]; | ||
| 194 | mixed[3] = fsamp[1] - fsamp[2]; | ||
| 195 | |||
| 196 | if constexpr (CHANNEL_COUNT == 2) { | ||
| 197 | for (auto& mix : mixed) { | ||
| 198 | mix *= (filter * state.late_gain); | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 203 | const auto late = early_tap * state.late_gain; | ||
| 204 | osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]); | ||
| 205 | osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]); | ||
| 206 | state.fdn_delay_line[i].Tick(osamp[i]); | ||
| 207 | } | ||
| 208 | |||
| 209 | if constexpr (CHANNEL_COUNT == 1) { | ||
| 210 | output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) + | ||
| 211 | (out_samples[0] + osamp[0] + osamp[1])); | ||
| 212 | } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) { | ||
| 213 | for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { | ||
| 214 | output[i][sample] = | ||
| 215 | ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); | ||
| 216 | } | ||
| 217 | } else if constexpr (CHANNEL_COUNT == 6) { | ||
| 218 | const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3])); | ||
| 219 | for (std::size_t i = 0; i < 4; i++) { | ||
| 220 | output[i][sample] = | ||
| 221 | ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); | ||
| 222 | } | ||
| 223 | output[4][sample] = | ||
| 224 | ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center)); | ||
| 225 | output[5][sample] = | ||
| 226 | ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3])); | ||
| 227 | } | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 68 | } // namespace | 231 | } // namespace |
| 69 | 232 | ||
| 70 | CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, | 233 | CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, |
| @@ -271,11 +434,10 @@ void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voic | |||
| 271 | } | 434 | } |
| 272 | 435 | ||
| 273 | // Generate biquad filter | 436 | // Generate biquad filter |
| 274 | // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, | 437 | // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, |
| 275 | // dsp_state.biquad_filter_state, | 438 | // dsp_state.biquad_filter_state, |
| 276 | // mix_buffer_count + channel, mix_buffer_count + | 439 | // mix_buffer_count + channel, mix_buffer_count + channel, |
| 277 | // channel, worker_params.sample_count, | 440 | // worker_params.sample_count, voice_info.GetInParams().node_id); |
| 278 | // voice_info.GetInParams().node_id); | ||
| 279 | } | 441 | } |
| 280 | } | 442 | } |
| 281 | 443 | ||
| @@ -376,21 +538,54 @@ void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) { | |||
| 376 | 538 | ||
| 377 | void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, | 539 | void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, |
| 378 | bool enabled) { | 540 | bool enabled) { |
| 379 | if (!enabled) { | 541 | auto* reverb = dynamic_cast<EffectI3dl2Reverb*>(info); |
| 542 | const auto& params = reverb->GetParams(); | ||
| 543 | auto& state = reverb->GetState(); | ||
| 544 | const auto channel_count = params.channel_count; | ||
| 545 | |||
| 546 | if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) { | ||
| 380 | return; | 547 | return; |
| 381 | } | 548 | } |
| 382 | const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams(); | 549 | |
| 383 | const auto channel_count = params.channel_count; | 550 | std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT> input{}; |
| 551 | std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT> output{}; | ||
| 552 | |||
| 553 | const auto status = params.status; | ||
| 384 | for (s32 i = 0; i < channel_count; i++) { | 554 | for (s32 i = 0; i < channel_count; i++) { |
| 385 | // TODO(ogniK): Actually implement reverb | 555 | input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]); |
| 386 | /* | 556 | output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]); |
| 387 | if (params.input[i] != params.output[i]) { | 557 | } |
| 388 | const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]); | 558 | |
| 389 | auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]); | 559 | if (enabled) { |
| 390 | ApplyMix<1>(output, input, 32768, worker_params.sample_count); | 560 | if (status == ParameterStatus::Initialized) { |
| 391 | }*/ | 561 | InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer()); |
| 392 | auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]); | 562 | } else if (status == ParameterStatus::Updating) { |
| 393 | std::memset(output, 0, worker_params.sample_count * sizeof(s32)); | 563 | UpdateI3dl2Reverb(reverb->GetParams(), state, false); |
| 564 | } | ||
| 565 | } | ||
| 566 | |||
| 567 | if (enabled) { | ||
| 568 | switch (channel_count) { | ||
| 569 | case 1: | ||
| 570 | ApplyReverbGeneric<1>(params, state, input, output, worker_params.sample_count); | ||
| 571 | break; | ||
| 572 | case 2: | ||
| 573 | ApplyReverbGeneric<2>(params, state, input, output, worker_params.sample_count); | ||
| 574 | break; | ||
| 575 | case 4: | ||
| 576 | ApplyReverbGeneric<4>(params, state, input, output, worker_params.sample_count); | ||
| 577 | break; | ||
| 578 | case 6: | ||
| 579 | ApplyReverbGeneric<6>(params, state, input, output, worker_params.sample_count); | ||
| 580 | break; | ||
| 581 | } | ||
| 582 | } else { | ||
| 583 | for (s32 i = 0; i < channel_count; i++) { | ||
| 584 | // Only copy if the buffer input and output do not match! | ||
| 585 | if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) { | ||
| 586 | std::memcpy(output[i], input[i], worker_params.sample_count * sizeof(s32)); | ||
| 587 | } | ||
| 588 | } | ||
| 394 | } | 589 | } |
| 395 | } | 590 | } |
| 396 | 591 | ||
| @@ -528,6 +723,132 @@ s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u3 | |||
| 528 | return sample_count; | 723 | return sample_count; |
| 529 | } | 724 | } |
| 530 | 725 | ||
| 726 | void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||
| 727 | std::vector<u8>& work_buffer) { | ||
| 728 | // Reset state | ||
| 729 | state.lowpass_0 = 0.0f; | ||
| 730 | state.lowpass_1 = 0.0f; | ||
| 731 | state.lowpass_2 = 0.0f; | ||
| 732 | |||
| 733 | state.early_delay_line.Reset(); | ||
| 734 | state.early_tap_steps.fill(0); | ||
| 735 | state.early_gain = 0.0f; | ||
| 736 | state.late_gain = 0.0f; | ||
| 737 | state.early_to_late_taps = 0; | ||
| 738 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 739 | state.fdn_delay_line[i].Reset(); | ||
| 740 | state.decay_delay_line0[i].Reset(); | ||
| 741 | state.decay_delay_line1[i].Reset(); | ||
| 742 | } | ||
| 743 | state.last_reverb_echo = 0.0f; | ||
| 744 | state.center_delay_line.Reset(); | ||
| 745 | for (auto& coef : state.lpf_coefficients) { | ||
| 746 | coef.fill(0.0f); | ||
| 747 | } | ||
| 748 | state.shelf_filter.fill(0.0f); | ||
| 749 | state.dry_gain = 0.0f; | ||
| 750 | |||
| 751 | const auto sample_rate = info.sample_rate / 1000; | ||
| 752 | f32* work_buffer_ptr = reinterpret_cast<f32*>(work_buffer.data()); | ||
| 753 | |||
| 754 | s32 delay_samples{}; | ||
| 755 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 756 | delay_samples = | ||
| 757 | AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]); | ||
| 758 | state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr); | ||
| 759 | work_buffer_ptr += delay_samples + 1; | ||
| 760 | |||
| 761 | delay_samples = | ||
| 762 | AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]); | ||
| 763 | state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); | ||
| 764 | work_buffer_ptr += delay_samples + 1; | ||
| 765 | |||
| 766 | delay_samples = | ||
| 767 | AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]); | ||
| 768 | state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); | ||
| 769 | work_buffer_ptr += delay_samples + 1; | ||
| 770 | } | ||
| 771 | delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f); | ||
| 772 | state.center_delay_line.Initialize(delay_samples, work_buffer_ptr); | ||
| 773 | work_buffer_ptr += delay_samples + 1; | ||
| 774 | |||
| 775 | delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f); | ||
| 776 | state.early_delay_line.Initialize(delay_samples, work_buffer_ptr); | ||
| 777 | |||
| 778 | UpdateI3dl2Reverb(info, state, true); | ||
| 779 | } | ||
| 780 | |||
| 781 | void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||
| 782 | bool should_clear) { | ||
| 783 | |||
| 784 | state.dry_gain = info.dry_gain; | ||
| 785 | state.shelf_filter.fill(0.0f); | ||
| 786 | state.lowpass_0 = 0.0f; | ||
| 787 | state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f); | ||
| 788 | state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f); | ||
| 789 | |||
| 790 | const auto sample_rate = info.sample_rate / 1000; | ||
| 791 | const f32 hf_gain = Pow10(info.room_hf / 2000.0f); | ||
| 792 | if (hf_gain >= 1.0f) { | ||
| 793 | state.lowpass_2 = 1.0f; | ||
| 794 | state.lowpass_1 = 0.0f; | ||
| 795 | } else { | ||
| 796 | const auto a = 1.0f - hf_gain; | ||
| 797 | const auto b = | ||
| 798 | 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference / info.sample_rate)); | ||
| 799 | const auto c = std::sqrt(b * b - 4.0f * a * a); | ||
| 800 | |||
| 801 | state.lowpass_1 = (b - c) / (2.0f * a); | ||
| 802 | state.lowpass_2 = 1.0f - state.lowpass_1; | ||
| 803 | } | ||
| 804 | state.early_to_late_taps = AudioCommon::CalculateDelaySamples( | ||
| 805 | sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay)); | ||
| 806 | |||
| 807 | state.last_reverb_echo = 0.6f * info.diffusion * 0.01f; | ||
| 808 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 809 | const auto length = | ||
| 810 | FDN_MIN_DELAY_LINE_TIMES[i] + | ||
| 811 | (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]); | ||
| 812 | state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length)); | ||
| 813 | |||
| 814 | const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() + | ||
| 815 | state.decay_delay_line0[i].GetDelay() + | ||
| 816 | state.decay_delay_line1[i].GetDelay(); | ||
| 817 | |||
| 818 | float a = (-60.0f * delay_sample_counts) / (info.decay_time * info.sample_rate); | ||
| 819 | float b = a / info.hf_decay_ratio; | ||
| 820 | float c = CosD(128.0f * 0.5f * info.hf_reference / info.sample_rate) / | ||
| 821 | SinD(128.0f * 0.5f * info.hf_reference / info.sample_rate); | ||
| 822 | float d = Pow10((b - a) / 40.0f); | ||
| 823 | float e = Pow10((b + a) / 40.0f) * 0.7071f; | ||
| 824 | |||
| 825 | state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d); | ||
| 826 | state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d); | ||
| 827 | state.lpf_coefficients[2][i] = (c - d) / (c + d); | ||
| 828 | |||
| 829 | state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo); | ||
| 830 | state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo); | ||
| 831 | } | ||
| 832 | |||
| 833 | if (should_clear) { | ||
| 834 | for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { | ||
| 835 | state.fdn_delay_line[i].Clear(); | ||
| 836 | state.decay_delay_line0[i].Clear(); | ||
| 837 | state.decay_delay_line1[i].Clear(); | ||
| 838 | } | ||
| 839 | state.early_delay_line.Clear(); | ||
| 840 | state.center_delay_line.Clear(); | ||
| 841 | } | ||
| 842 | |||
| 843 | const auto max_early_delay = state.early_delay_line.GetMaxDelay(); | ||
| 844 | const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f); | ||
| 845 | for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { | ||
| 846 | const auto length = AudioCommon::CalculateDelaySamples( | ||
| 847 | sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); | ||
| 848 | state.early_tap_steps[tap] = std::min(length, max_early_delay); | ||
| 849 | } | ||
| 850 | } | ||
| 851 | |||
| 531 | void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, | 852 | void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, |
| 532 | s32 channel, s32 node_id) { | 853 | s32 channel, s32 node_id) { |
| 533 | const auto last = static_cast<s32>(last_volume * 32768.0f); | 854 | const auto last = static_cast<s32>(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; | |||
| 21 | class EffectContext; | 21 | class EffectContext; |
| 22 | class EffectBase; | 22 | class EffectBase; |
| 23 | struct AuxInfoDSP; | 23 | struct AuxInfoDSP; |
| 24 | struct I3dl2ReverbParams; | ||
| 25 | struct I3dl2ReverbState; | ||
| 24 | using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; | 26 | using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; |
| 25 | 27 | ||
| 26 | class CommandGenerator { | 28 | class CommandGenerator { |
| @@ -80,6 +82,9 @@ private: | |||
| 80 | s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data, | 82 | s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data, |
| 81 | u32 sample_count, u32 read_offset, u32 read_count); | 83 | u32 sample_count, u32 read_offset, u32 read_count); |
| 82 | 84 | ||
| 85 | void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, | ||
| 86 | std::vector<u8>& work_buffer); | ||
| 87 | void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); | ||
| 83 | // DSP Code | 88 | // DSP Code |
| 84 | s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, | 89 | s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, |
| 85 | s32 channel, std::size_t mix_offset); | 90 | 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 | |||
| 33 | // and our const ends up being 0x3f04, the 4 bytes are most | 33 | // and our const ends up being 0x3f04, the 4 bytes are most |
| 34 | // likely the sample history | 34 | // likely the sample history |
| 35 | constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; | 35 | constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; |
| 36 | constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f; | ||
| 37 | constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f; | ||
| 38 | constexpr std::size_t I3DL2REVERB_TAPS = 20; | ||
| 39 | constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4; | ||
| 40 | using Fractional = s32; | ||
| 41 | |||
| 42 | template <typename T> | ||
| 43 | constexpr Fractional ToFractional(T x) { | ||
| 44 | return static_cast<Fractional>(x * static_cast<T>(0x4000)); | ||
| 45 | } | ||
| 46 | |||
| 47 | constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) { | ||
| 48 | return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14); | ||
| 49 | } | ||
| 50 | |||
| 51 | constexpr s32 FractionalToFixed(Fractional x) { | ||
| 52 | const auto s = x & (1 << 13); | ||
| 53 | return static_cast<s32>(x >> 14) + s; | ||
| 54 | } | ||
| 55 | |||
| 56 | constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) { | ||
| 57 | return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time))); | ||
| 58 | } | ||
| 36 | 59 | ||
| 37 | static constexpr u32 VersionFromRevision(u32_le rev) { | 60 | static constexpr u32 VersionFromRevision(u32_le rev) { |
| 38 | // "REV7" -> 7 | 61 | // "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 @@ | |||
| 1 | #include "audio_core/delay_line.h" | ||
| 2 | |||
| 3 | namespace AudioCore { | ||
| 4 | DelayLineBase::DelayLineBase() = default; | ||
| 5 | DelayLineBase::~DelayLineBase() = default; | ||
| 6 | |||
| 7 | void DelayLineBase::Initialize(s32 _max_delay, float* src_buffer) { | ||
| 8 | buffer = src_buffer; | ||
| 9 | buffer_end = buffer + _max_delay; | ||
| 10 | max_delay = _max_delay; | ||
| 11 | output = buffer; | ||
| 12 | SetDelay(_max_delay); | ||
| 13 | Clear(); | ||
| 14 | } | ||
| 15 | |||
| 16 | void DelayLineBase::SetDelay(s32 new_delay) { | ||
| 17 | if (max_delay < new_delay) { | ||
| 18 | return; | ||
| 19 | } | ||
| 20 | delay = new_delay; | ||
| 21 | input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1)); | ||
| 22 | } | ||
| 23 | |||
| 24 | s32 DelayLineBase::GetDelay() const { | ||
| 25 | return delay; | ||
| 26 | } | ||
| 27 | |||
| 28 | s32 DelayLineBase::GetMaxDelay() const { | ||
| 29 | return max_delay; | ||
| 30 | } | ||
| 31 | |||
| 32 | f32 DelayLineBase::TapOut(s32 last_sample) { | ||
| 33 | float* ptr = input - (last_sample + 1); | ||
| 34 | if (ptr < buffer) { | ||
| 35 | ptr += (max_delay + 1); | ||
| 36 | } | ||
| 37 | |||
| 38 | return *ptr; | ||
| 39 | } | ||
| 40 | |||
| 41 | f32 DelayLineBase::Tick(f32 sample) { | ||
| 42 | *(input++) = sample; | ||
| 43 | const auto out_sample = *(output++); | ||
| 44 | |||
| 45 | if (buffer_end < input) { | ||
| 46 | input = buffer; | ||
| 47 | } | ||
| 48 | |||
| 49 | if (buffer_end < output) { | ||
| 50 | output = buffer; | ||
| 51 | } | ||
| 52 | |||
| 53 | return out_sample; | ||
| 54 | } | ||
| 55 | |||
| 56 | float* DelayLineBase::GetInput() { | ||
| 57 | return input; | ||
| 58 | } | ||
| 59 | |||
| 60 | const float* DelayLineBase::GetInput() const { | ||
| 61 | return input; | ||
| 62 | } | ||
| 63 | |||
| 64 | f32 DelayLineBase::GetOutputSample() const { | ||
| 65 | return *output; | ||
| 66 | } | ||
| 67 | |||
| 68 | void DelayLineBase::Clear() { | ||
| 69 | std::memset(buffer, 0, sizeof(float) * max_delay); | ||
| 70 | } | ||
| 71 | |||
| 72 | void DelayLineBase::Reset() { | ||
| 73 | buffer = nullptr; | ||
| 74 | buffer_end = nullptr; | ||
| 75 | max_delay = 0; | ||
| 76 | input = nullptr; | ||
| 77 | output = nullptr; | ||
| 78 | delay = 0; | ||
| 79 | } | ||
| 80 | |||
| 81 | DelayLineAllPass::DelayLineAllPass() = default; | ||
| 82 | DelayLineAllPass::~DelayLineAllPass() = default; | ||
| 83 | |||
| 84 | void DelayLineAllPass::Initialize(u32 delay, float _coeffcient, f32* src_buffer) { | ||
| 85 | DelayLineBase::Initialize(delay, src_buffer); | ||
| 86 | SetCoefficient(_coeffcient); | ||
| 87 | } | ||
| 88 | |||
| 89 | void DelayLineAllPass::SetCoefficient(float _coeffcient) { | ||
| 90 | coefficient = _coeffcient; | ||
| 91 | } | ||
| 92 | |||
| 93 | f32 DelayLineAllPass::Tick(f32 sample) { | ||
| 94 | const auto temp = sample - coefficient * *output; | ||
| 95 | return coefficient * temp + DelayLineBase::Tick(temp); | ||
| 96 | } | ||
| 97 | |||
| 98 | void DelayLineAllPass::Reset() { | ||
| 99 | coefficient = 0.0f; | ||
| 100 | DelayLineBase::Reset(); | ||
| 101 | } | ||
| 102 | |||
| 103 | } // 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 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "common/common_types.h" | ||
| 4 | |||
| 5 | namespace AudioCore { | ||
| 6 | |||
| 7 | class DelayLineBase { | ||
| 8 | public: | ||
| 9 | DelayLineBase(); | ||
| 10 | ~DelayLineBase(); | ||
| 11 | |||
| 12 | void Initialize(s32 _max_delay, float* src_buffer); | ||
| 13 | void SetDelay(s32 new_delay); | ||
| 14 | s32 GetDelay() const; | ||
| 15 | s32 GetMaxDelay() const; | ||
| 16 | f32 TapOut(s32 last_sample); | ||
| 17 | f32 Tick(f32 sample); | ||
| 18 | float* GetInput(); | ||
| 19 | const float* GetInput() const; | ||
| 20 | f32 GetOutputSample() const; | ||
| 21 | void Clear(); | ||
| 22 | void Reset(); | ||
| 23 | |||
| 24 | protected: | ||
| 25 | float* buffer{nullptr}; | ||
| 26 | float* buffer_end{nullptr}; | ||
| 27 | s32 max_delay{}; | ||
| 28 | float* input{nullptr}; | ||
| 29 | float* output{nullptr}; | ||
| 30 | s32 delay{}; | ||
| 31 | }; | ||
| 32 | |||
| 33 | class DelayLineAllPass final : public DelayLineBase { | ||
| 34 | public: | ||
| 35 | DelayLineAllPass(); | ||
| 36 | ~DelayLineAllPass(); | ||
| 37 | |||
| 38 | void Initialize(u32 delay, float _coeffcient, f32* src_buffer); | ||
| 39 | void SetCoefficient(float _coeffcient); | ||
| 40 | f32 Tick(f32 sample); | ||
| 41 | void Reset(); | ||
| 42 | |||
| 43 | private: | ||
| 44 | float coefficient{}; | ||
| 45 | }; | ||
| 46 | } // 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 { | |||
| 90 | return processing_order; | 90 | return processing_order; |
| 91 | } | 91 | } |
| 92 | 92 | ||
| 93 | std::vector<u8>& EffectBase::GetWorkBuffer() { | ||
| 94 | return work_buffer; | ||
| 95 | } | ||
| 96 | |||
| 97 | const std::vector<u8>& EffectBase::GetWorkBuffer() const { | ||
| 98 | return work_buffer; | ||
| 99 | } | ||
| 100 | |||
| 93 | EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {} | 101 | EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {} |
| 94 | EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; | 102 | EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; |
| 95 | 103 | ||
| @@ -117,6 +125,12 @@ void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) { | |||
| 117 | usage = UsageState::Initialized; | 125 | usage = UsageState::Initialized; |
| 118 | params.status = ParameterStatus::Initialized; | 126 | params.status = ParameterStatus::Initialized; |
| 119 | skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; | 127 | skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; |
| 128 | if (!skipped) { | ||
| 129 | auto& work_buffer = GetWorkBuffer(); | ||
| 130 | // Has two buffers internally | ||
| 131 | work_buffer.resize(in_params.buffer_size * 2); | ||
| 132 | std::fill(work_buffer.begin(), work_buffer.end(), 0); | ||
| 133 | } | ||
| 120 | } | 134 | } |
| 121 | } | 135 | } |
| 122 | 136 | ||
| @@ -129,6 +143,14 @@ void EffectI3dl2Reverb::UpdateForCommandGeneration() { | |||
| 129 | GetParams().status = ParameterStatus::Updated; | 143 | GetParams().status = ParameterStatus::Updated; |
| 130 | } | 144 | } |
| 131 | 145 | ||
| 146 | I3dl2ReverbState& EffectI3dl2Reverb::GetState() { | ||
| 147 | return state; | ||
| 148 | } | ||
| 149 | |||
| 150 | const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const { | ||
| 151 | return state; | ||
| 152 | } | ||
| 153 | |||
| 132 | EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {} | 154 | EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {} |
| 133 | EffectBiquadFilter::~EffectBiquadFilter() = default; | 155 | EffectBiquadFilter::~EffectBiquadFilter() = default; |
| 134 | 156 | ||
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 @@ | |||
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <vector> | 9 | #include <vector> |
| 10 | #include "audio_core/common.h" | 10 | #include "audio_core/common.h" |
| 11 | #include "audio_core/delay_line.h" | ||
| 11 | #include "common/common_funcs.h" | 12 | #include "common/common_funcs.h" |
| 12 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 13 | #include "common/swap.h" | 14 | #include "common/swap.h" |
| @@ -194,6 +195,8 @@ public: | |||
| 194 | [[nodiscard]] bool IsEnabled() const; | 195 | [[nodiscard]] bool IsEnabled() const; |
| 195 | [[nodiscard]] s32 GetMixID() const; | 196 | [[nodiscard]] s32 GetMixID() const; |
| 196 | [[nodiscard]] s32 GetProcessingOrder() const; | 197 | [[nodiscard]] s32 GetProcessingOrder() const; |
| 198 | [[nodiscard]] std::vector<u8>& GetWorkBuffer(); | ||
| 199 | [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const; | ||
| 197 | 200 | ||
| 198 | protected: | 201 | protected: |
| 199 | UsageState usage{UsageState::Invalid}; | 202 | UsageState usage{UsageState::Invalid}; |
| @@ -201,6 +204,7 @@ protected: | |||
| 201 | s32 mix_id{}; | 204 | s32 mix_id{}; |
| 202 | s32 processing_order{}; | 205 | s32 processing_order{}; |
| 203 | bool enabled = false; | 206 | bool enabled = false; |
| 207 | std::vector<u8> work_buffer{}; | ||
| 204 | }; | 208 | }; |
| 205 | 209 | ||
| 206 | template <typename T> | 210 | template <typename T> |
| @@ -212,7 +216,7 @@ public: | |||
| 212 | return internal_params; | 216 | return internal_params; |
| 213 | } | 217 | } |
| 214 | 218 | ||
| 215 | const I3dl2ReverbParams& GetParams() const { | 219 | const T& GetParams() const { |
| 216 | return internal_params; | 220 | return internal_params; |
| 217 | } | 221 | } |
| 218 | 222 | ||
| @@ -229,6 +233,27 @@ public: | |||
| 229 | void UpdateForCommandGeneration() override; | 233 | void UpdateForCommandGeneration() override; |
| 230 | }; | 234 | }; |
| 231 | 235 | ||
| 236 | struct I3dl2ReverbState { | ||
| 237 | f32 lowpass_0{}; | ||
| 238 | f32 lowpass_1{}; | ||
| 239 | f32 lowpass_2{}; | ||
| 240 | |||
| 241 | DelayLineBase early_delay_line{}; | ||
| 242 | std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{}; | ||
| 243 | f32 early_gain{}; | ||
| 244 | f32 late_gain{}; | ||
| 245 | |||
| 246 | u32 early_to_late_taps{}; | ||
| 247 | std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{}; | ||
| 248 | std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{}; | ||
| 249 | std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{}; | ||
| 250 | f32 last_reverb_echo{}; | ||
| 251 | DelayLineBase center_delay_line{}; | ||
| 252 | std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{}; | ||
| 253 | std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{}; | ||
| 254 | f32 dry_gain{}; | ||
| 255 | }; | ||
| 256 | |||
| 232 | class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { | 257 | class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { |
| 233 | public: | 258 | public: |
| 234 | explicit EffectI3dl2Reverb(); | 259 | explicit EffectI3dl2Reverb(); |
| @@ -237,8 +262,12 @@ public: | |||
| 237 | void Update(EffectInfo::InParams& in_params) override; | 262 | void Update(EffectInfo::InParams& in_params) override; |
| 238 | void UpdateForCommandGeneration() override; | 263 | void UpdateForCommandGeneration() override; |
| 239 | 264 | ||
| 265 | I3dl2ReverbState& GetState(); | ||
| 266 | const I3dl2ReverbState& GetState() const; | ||
| 267 | |||
| 240 | private: | 268 | private: |
| 241 | bool skipped = false; | 269 | bool skipped = false; |
| 270 | I3dl2ReverbState state{}; | ||
| 242 | }; | 271 | }; |
| 243 | 272 | ||
| 244 | class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> { | 273 | class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> { |