audren: Implement I3dl2Reverb
Most notable fix is the voices in Fire Emblem Three Houses
This commit is contained in:
parent
c86d770af9
commit
4a7fd91857
|
@ -15,6 +15,8 @@ add_library(audio_core STATIC
|
||||||
command_generator.cpp
|
command_generator.cpp
|
||||||
command_generator.h
|
command_generator.h
|
||||||
common.h
|
common.h
|
||||||
|
delay_line.cpp
|
||||||
|
delay_line.h
|
||||||
effect_context.cpp
|
effect_context.cpp
|
||||||
effect_context.h
|
effect_context.h
|
||||||
info_updater.cpp
|
info_updater.cpp
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <numbers>
|
||||||
#include "audio_core/algorithm/interpolate.h"
|
#include "audio_core/algorithm/interpolate.h"
|
||||||
#include "audio_core/command_generator.h"
|
#include "audio_core/command_generator.h"
|
||||||
#include "audio_core/effect_context.h"
|
#include "audio_core/effect_context.h"
|
||||||
|
@ -13,6 +14,20 @@ namespace AudioCore {
|
||||||
namespace {
|
namespace {
|
||||||
constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
|
constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
|
||||||
constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
|
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>
|
template <std::size_t N>
|
||||||
void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) {
|
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
|
} // namespace
|
||||||
|
|
||||||
CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_,
|
CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_,
|
||||||
|
@ -273,9 +436,8 @@ void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voic
|
||||||
// Generate biquad filter
|
// Generate biquad filter
|
||||||
// GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
|
// GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
|
||||||
// dsp_state.biquad_filter_state,
|
// dsp_state.biquad_filter_state,
|
||||||
// mix_buffer_count + channel, mix_buffer_count +
|
// mix_buffer_count + channel, mix_buffer_count + channel,
|
||||||
// channel, worker_params.sample_count,
|
// worker_params.sample_count, voice_info.GetInParams().node_id);
|
||||||
// voice_info.GetInParams().node_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,21 +538,54 @@ void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
|
||||||
|
|
||||||
void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
|
void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
|
||||||
bool enabled) {
|
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;
|
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++) {
|
for (s32 i = 0; i < channel_count; i++) {
|
||||||
// TODO(ogniK): Actually implement reverb
|
input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]);
|
||||||
/*
|
output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]);
|
||||||
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]);
|
if (enabled) {
|
||||||
ApplyMix<1>(output, input, 32768, worker_params.sample_count);
|
if (status == ParameterStatus::Initialized) {
|
||||||
}*/
|
InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer());
|
||||||
auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
|
} else if (status == ParameterStatus::Updating) {
|
||||||
std::memset(output, 0, worker_params.sample_count * sizeof(s32));
|
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;
|
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,
|
void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
|
||||||
s32 channel, s32 node_id) {
|
s32 channel, s32 node_id) {
|
||||||
const auto last = static_cast<s32>(last_volume * 32768.0f);
|
const auto last = static_cast<s32>(last_volume * 32768.0f);
|
||||||
|
|
|
@ -21,6 +21,8 @@ class ServerMixInfo;
|
||||||
class EffectContext;
|
class EffectContext;
|
||||||
class EffectBase;
|
class EffectBase;
|
||||||
struct AuxInfoDSP;
|
struct AuxInfoDSP;
|
||||||
|
struct I3dl2ReverbParams;
|
||||||
|
struct I3dl2ReverbState;
|
||||||
using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
|
using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
|
||||||
|
|
||||||
class CommandGenerator {
|
class CommandGenerator {
|
||||||
|
@ -80,6 +82,9 @@ private:
|
||||||
s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
|
s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
|
||||||
u32 sample_count, u32 read_offset, u32 read_count);
|
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
|
// DSP Code
|
||||||
s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
|
s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
|
||||||
s32 channel, std::size_t mix_offset);
|
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
|
// and our const ends up being 0x3f04, the 4 bytes are most
|
||||||
// likely the sample history
|
// likely the sample history
|
||||||
constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_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) {
|
static constexpr u32 VersionFromRevision(u32_le rev) {
|
||||||
// "REV7" -> 7
|
// "REV7" -> 7
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
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() : EffectGeneric(EffectType::I3dl2Reverb) {}
|
||||||
EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
|
EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
|
||||||
|
|
||||||
|
@ -117,6 +125,12 @@ void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
|
||||||
usage = UsageState::Initialized;
|
usage = UsageState::Initialized;
|
||||||
params.status = ParameterStatus::Initialized;
|
params.status = ParameterStatus::Initialized;
|
||||||
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
|
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;
|
GetParams().status = ParameterStatus::Updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
I3dl2ReverbState& EffectI3dl2Reverb::GetState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {}
|
EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {}
|
||||||
EffectBiquadFilter::~EffectBiquadFilter() = default;
|
EffectBiquadFilter::~EffectBiquadFilter() = default;
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "audio_core/common.h"
|
#include "audio_core/common.h"
|
||||||
|
#include "audio_core/delay_line.h"
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
|
@ -194,6 +195,8 @@ public:
|
||||||
[[nodiscard]] bool IsEnabled() const;
|
[[nodiscard]] bool IsEnabled() const;
|
||||||
[[nodiscard]] s32 GetMixID() const;
|
[[nodiscard]] s32 GetMixID() const;
|
||||||
[[nodiscard]] s32 GetProcessingOrder() const;
|
[[nodiscard]] s32 GetProcessingOrder() const;
|
||||||
|
[[nodiscard]] std::vector<u8>& GetWorkBuffer();
|
||||||
|
[[nodiscard]] const std::vector<u8>& GetWorkBuffer() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
UsageState usage{UsageState::Invalid};
|
UsageState usage{UsageState::Invalid};
|
||||||
|
@ -201,6 +204,7 @@ protected:
|
||||||
s32 mix_id{};
|
s32 mix_id{};
|
||||||
s32 processing_order{};
|
s32 processing_order{};
|
||||||
bool enabled = false;
|
bool enabled = false;
|
||||||
|
std::vector<u8> work_buffer{};
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -212,7 +216,7 @@ public:
|
||||||
return internal_params;
|
return internal_params;
|
||||||
}
|
}
|
||||||
|
|
||||||
const I3dl2ReverbParams& GetParams() const {
|
const T& GetParams() const {
|
||||||
return internal_params;
|
return internal_params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,6 +233,27 @@ public:
|
||||||
void UpdateForCommandGeneration() override;
|
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> {
|
class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
|
||||||
public:
|
public:
|
||||||
explicit EffectI3dl2Reverb();
|
explicit EffectI3dl2Reverb();
|
||||||
|
@ -237,8 +262,12 @@ public:
|
||||||
void Update(EffectInfo::InParams& in_params) override;
|
void Update(EffectInfo::InParams& in_params) override;
|
||||||
void UpdateForCommandGeneration() override;
|
void UpdateForCommandGeneration() override;
|
||||||
|
|
||||||
|
I3dl2ReverbState& GetState();
|
||||||
|
const I3dl2ReverbState& GetState() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool skipped = false;
|
bool skipped = false;
|
||||||
|
I3dl2ReverbState state{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
|
class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
|
||||||
|
|
Reference in New Issue