summaryrefslogtreecommitdiffstats
path: root/src/audio_core/command_generator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio_core/command_generator.cpp')
-rw-r--r--src/audio_core/command_generator.cpp357
1 files changed, 340 insertions, 17 deletions
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
index 5b1065520..437cc5ccd 100644
--- a/src/audio_core/command_generator.cpp
+++ b/src/audio_core/command_generator.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cmath>
+#include <numbers>
#include "audio_core/algorithm/interpolate.h"
#include "audio_core/command_generator.h"
#include "audio_core/effect_context.h"
@@ -13,6 +15,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 +81,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::sin(degrees * std::numbers::pi_v<float> / 180.0f);
+}
+
+float CosD(float degrees) {
+ return std::cos(degrees * std::numbers::pi_v<float> / 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(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 +435,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 +539,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>(state, input, output, worker_params.sample_count);
+ break;
+ case 2:
+ ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count);
+ break;
+ case 4:
+ ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count);
+ break;
+ case 6:
+ ApplyReverbGeneric<6>(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 +724,133 @@ 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 /
+ static_cast<f32>(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 * static_cast<f32>(delay_sample_counts)) /
+ (info.decay_time * static_cast<f32>(info.sample_rate));
+ float b = a / info.hf_decay_ratio;
+ float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)) /
+ SinD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(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);