From 80ac1331b545d993aa7c205dc24f8b20a4d6d44e Mon Sep 17 00:00:00 2001
From: David Marcec <dmarcecguzman@gmail.com>
Date: Mon, 17 Aug 2020 01:23:55 +1000
Subject: [PATCH] Preliminary effects

---
 src/audio_core/audio_renderer.cpp    |   9 +-
 src/audio_core/command_generator.cpp | 187 +++++++++++++++++-
 src/audio_core/command_generator.h   |  17 +-
 src/audio_core/common.h              |   1 +
 src/audio_core/effect_context.cpp    | 271 ++++++++++++++++++++++++++-
 src/audio_core/effect_context.h      | 215 ++++++++++++++++++++-
 src/audio_core/info_updater.cpp      |  18 +-
 src/audio_core/info_updater.h        |   2 +-
 src/audio_core/mix_context.cpp       |  35 +++-
 src/audio_core/mix_context.h         |  11 +-
 10 files changed, 731 insertions(+), 35 deletions(-)

diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index a3ff819e11..56dc892b14 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -27,12 +27,13 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
       voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
       sink_context(params.sink_count), splitter_context(),
       voices(params.voice_count), memory{memory_},
-      command_generator(worker_params, voice_context, mix_context, splitter_context, memory),
+      command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
+                        memory),
       temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) {
     behavior_info.SetUserRevision(params.revision);
     splitter_context.Initialize(behavior_info, params.splitter_count,
                                 params.num_splitter_send_channels);
-    mix_context.Initialize(behavior_info, params.submix_count + 1);
+    mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
     audio_out = std::make_unique<AudioCore::AudioOut>();
     stream =
         audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
@@ -106,8 +107,8 @@ ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_param
         }
     }
 
-    auto mix_result =
-        info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, splitter_context);
+    auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count,
+                                               splitter_context, effect_context);
 
     if (mix_result.IsError()) {
         LOG_ERROR(Audio, "Failed to update mix parameters");
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
index 73608c9ed7..84782cde6e 100644
--- a/src/audio_core/command_generator.cpp
+++ b/src/audio_core/command_generator.cpp
@@ -4,6 +4,7 @@
 
 #include "audio_core/algorithm/interpolate.h"
 #include "audio_core/command_generator.h"
+#include "audio_core/effect_context.h"
 #include "audio_core/mix_context.h"
 #include "audio_core/voice_context.h"
 #include "core/memory.h"
@@ -68,9 +69,10 @@ s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) {
 
 CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
                                    VoiceContext& voice_context, MixContext& mix_context,
-                                   SplitterContext& splitter_context, Core::Memory::Memory& memory)
+                                   SplitterContext& splitter_context, EffectContext& effect_context,
+                                   Core::Memory::Memory& memory)
     : worker_params(worker_params), voice_context(voice_context), mix_context(mix_context),
-      splitter_context(splitter_context), memory(memory),
+      splitter_context(splitter_context), effect_context(effect_context), memory(memory),
       mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
                  worker_params.sample_count),
       sample_buffer(MIX_BUFFER_SIZE),
@@ -338,6 +340,120 @@ void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_
     }
 }
 
+void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
+    const std::size_t effect_count = effect_context.GetCount();
+    const auto buffer_offset = mix_info.GetInParams().buffer_offset;
+    for (std::size_t i = 0; i < effect_count; i++) {
+        const auto index = mix_info.GetEffectOrder(i);
+        if (index == AudioCommon::NO_EFFECT_ORDER) {
+            break;
+        }
+        auto* info = effect_context.GetInfo(index);
+        const auto type = info->GetType();
+
+        // TODO(ogniK): Finish remaining effects
+        switch (type) {
+        case EffectType::Aux:
+            GenerateAuxCommand(buffer_offset, info, info->IsEnabled());
+            break;
+        case EffectType::I3dl2Reverb:
+            GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled());
+            break;
+        case EffectType::BiquadFilter:
+            GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled());
+            break;
+        default:
+            break;
+        }
+
+        info->UpdateForCommandGeneration();
+    }
+}
+
+void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
+                                                        bool enabled) {
+    if (!enabled) {
+        return;
+    }
+    const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams();
+    const auto channel_count = params.channel_count;
+    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);
+        }
+    }
+}
+
+void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info,
+                                                         bool enabled) {
+    if (!enabled) {
+        return;
+    }
+    const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams();
+    const auto channel_count = params.channel_count;
+    for (s32 i = 0; i < channel_count; i++) {
+        // TODO(ogniK): Actually implement biquad filter
+        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);
+        }
+    }
+}
+
+void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
+    auto aux = dynamic_cast<EffectAuxInfo*>(info);
+    const auto& params = aux->GetParams();
+    if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
+        const auto max_channels = params.count;
+        u32 offset{};
+        for (u32 channel = 0; channel < max_channels; channel++) {
+            u32 write_count = 0;
+            if (channel == (max_channels - 1)) {
+                write_count = offset + worker_params.sample_count;
+            }
+
+            const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset;
+            const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset;
+
+            if (enabled) {
+                AuxInfoDSP send_info{};
+                AuxInfoDSP recv_info{};
+                memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
+                memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
+
+                WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count,
+                               GetMixBuffer(input_index), worker_params.sample_count, offset,
+                               write_count);
+                memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
+
+                const auto samples_read = ReadAuxBuffer(
+                    recv_info, aux->GetRecvBuffer(), params.sample_count,
+                    GetMixBuffer(output_index), worker_params.sample_count, offset, write_count);
+                memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
+
+                if (samples_read != worker_params.sample_count &&
+                    samples_read <= params.sample_count) {
+                    std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read);
+                }
+            } else {
+                AuxInfoDSP empty{};
+                memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP));
+                memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP));
+                if (output_index != input_index) {
+                    std::memcpy(GetMixBuffer(output_index), GetMixBuffer(input_index),
+                                worker_params.sample_count * sizeof(s32));
+                }
+            }
+
+            offset += worker_params.sample_count;
+        }
+    }
+}
+
 ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
     if (splitter_id == AudioCommon::NO_SPLITTER) {
         return nullptr;
@@ -345,6 +461,66 @@ ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter
     return splitter_context.GetDestinationData(splitter_id, index);
 }
 
+s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
+                                     const s32* data, u32 sample_count, u32 write_offset,
+                                     u32 write_count) {
+    if (max_samples == 0) {
+        return 0;
+    }
+    u32 offset = dsp_info.write_offset + write_offset;
+    if (send_buffer == 0 || offset > max_samples) {
+        return 0;
+    }
+
+    std::size_t data_offset{};
+    u32 remaining = sample_count;
+    while (remaining > 0) {
+        // Get position in buffer
+        const auto base = send_buffer + (offset * sizeof(u32));
+        const auto samples_to_grab = std::min(max_samples - offset, remaining);
+        // Write to output
+        memory.WriteBlock(base, (data + data_offset), samples_to_grab * sizeof(u32));
+        offset = (offset + samples_to_grab) % max_samples;
+        remaining -= samples_to_grab;
+        data_offset += samples_to_grab;
+    }
+
+    if (write_count != 0) {
+        dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples;
+    }
+    return sample_count;
+}
+
+s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
+                                    s32* out_data, u32 sample_count, u32 read_offset,
+                                    u32 read_count) {
+    if (max_samples == 0) {
+        return 0;
+    }
+
+    u32 offset = recv_info.read_offset + read_offset;
+    if (recv_buffer == 0 || offset > max_samples) {
+        return 0;
+    }
+
+    u32 remaining = sample_count;
+    while (remaining > 0) {
+        const auto base = recv_buffer + (offset * sizeof(u32));
+        const auto samples_to_grab = std::min(max_samples - offset, remaining);
+        std::vector<s32> buffer(samples_to_grab);
+        memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32));
+        std::memcpy(out_data, buffer.data(), buffer.size() * sizeof(u32));
+        out_data += samples_to_grab;
+        offset = (offset + samples_to_grab) % max_samples;
+        remaining -= samples_to_grab;
+    }
+
+    if (read_count != 0) {
+        recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples;
+    }
+    return sample_count;
+}
+
 void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
                                                  s32 channel, s32 node_id) {
     const auto last = static_cast<s32>(last_volume * 32768.0f);
@@ -398,7 +574,9 @@ void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
     auto& in_params = mix_info.GetInParams();
     GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
                                       in_params.sample_rate);
-    // TODO(ogniK): Effects
+
+    GenerateEffectCommand(mix_info);
+
     GenerateMixCommands(mix_info);
 }
 
@@ -476,7 +654,8 @@ void CommandGenerator::GenerateFinalMixCommand() {
 
     GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
                                       in_params.sample_rate);
-    // TODO(ogniK): Effects
+
+    GenerateEffectCommand(mix_info);
 
     for (s32 i = 0; i < in_params.buffer_count; i++) {
         const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h
index 656ad81438..967d24078e 100644
--- a/src/audio_core/command_generator.h
+++ b/src/audio_core/command_generator.h
@@ -19,14 +19,17 @@ class MixContext;
 class SplitterContext;
 class ServerSplitterDestinationData;
 class ServerMixInfo;
-
+class EffectContext;
+class EffectBase;
+struct AuxInfoDSP;
 using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
 
 class CommandGenerator {
 public:
     explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
                               VoiceContext& voice_context, MixContext& mix_context,
-                              SplitterContext& splitter_context, Core::Memory::Memory& memory);
+                              SplitterContext& splitter_context, EffectContext& effect_context,
+                              Core::Memory::Memory& memory);
     ~CommandGenerator();
 
     void ClearMixBuffers();
@@ -67,8 +70,17 @@ private:
                                      std::size_t mix_buffer_offset);
     void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
                                            std::size_t mix_buffer_offset, s32 sample_rate);
+    void GenerateEffectCommand(ServerMixInfo& mix_info);
+    void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
+    void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
+    void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
     ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
 
+    s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data,
+                       u32 sample_count, u32 write_offset, u32 write_count);
+    s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
+                      u32 sample_count, u32 read_offset, u32 read_count);
+
     // DSP Code
     s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
                     s32 channel, std::size_t mix_offset);
@@ -81,6 +93,7 @@ private:
     VoiceContext& voice_context;
     MixContext& mix_context;
     SplitterContext& splitter_context;
+    EffectContext& effect_context;
     Core::Memory::Memory& memory;
     std::vector<s32> mix_buffer{};
     std::vector<s32> sample_buffer{};
diff --git a/src/audio_core/common.h b/src/audio_core/common.h
index 0731d3eb3e..72ebce2218 100644
--- a/src/audio_core/common.h
+++ b/src/audio_core/common.h
@@ -26,6 +26,7 @@ constexpr s32 NO_SPLITTER = -1;
 constexpr s32 NO_MIX = 0x7fffffff;
 constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
 constexpr s32 FINAL_MIX = 0;
+constexpr s32 NO_EFFECT_ORDER = -1;
 constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
 // Any size checks seem to take the sample history into account
 // and our const ends up being 0x3f04, the 4 bytes are most
diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp
index 2497d2f323..adfec3df55 100644
--- a/src/audio_core/effect_context.cpp
+++ b/src/audio_core/effect_context.cpp
@@ -6,6 +6,12 @@
 #include "audio_core/effect_context.h"
 
 namespace AudioCore {
+namespace {
+bool ValidChannelCountForEffect(s32 channel_count) {
+    return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6;
+}
+} // namespace
+
 EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) {
     effects.reserve(effect_count);
     std::generate_n(std::back_inserter(effects), effect_count,
@@ -21,24 +27,273 @@ EffectBase* EffectContext::GetInfo(std::size_t i) {
     return effects.at(i).get();
 }
 
+EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) {
+    switch (effect) {
+    case EffectType::Invalid:
+        effects[i] = std::make_unique<EffectStubbed>();
+        break;
+    case EffectType::BufferMixer:
+        effects[i] = std::make_unique<EffectBufferMixer>();
+        break;
+    case EffectType::Aux:
+        effects[i] = std::make_unique<EffectAuxInfo>();
+        break;
+    case EffectType::Delay:
+        effects[i] = std::make_unique<EffectDelay>();
+        break;
+    case EffectType::Reverb:
+        effects[i] = std::make_unique<EffectReverb>();
+        break;
+    case EffectType::I3dl2Reverb:
+        effects[i] = std::make_unique<EffectI3dl2Reverb>();
+        break;
+    case EffectType::BiquadFilter:
+        effects[i] = std::make_unique<EffectBiquadFilter>();
+        break;
+    default:
+        UNREACHABLE_MSG("Unimplemented effect {}", effect);
+        effects[i] = std::make_unique<EffectStubbed>();
+    }
+    return GetInfo(i);
+}
+
 const EffectBase* EffectContext::GetInfo(std::size_t i) const {
     return effects.at(i).get();
 }
 
-EffectStubbed::EffectStubbed() : EffectBase::EffectBase() {}
+EffectStubbed::EffectStubbed() : EffectBase::EffectBase(EffectType::Invalid) {}
 EffectStubbed::~EffectStubbed() = default;
 
-void EffectStubbed::Update(EffectInfo::InParams& in_params) {
-    if (in_params.is_new) {
-        usage = UsageStatus::New;
-    }
-}
+void EffectStubbed::Update(EffectInfo::InParams& in_params) {}
+void EffectStubbed::UpdateForCommandGeneration() {}
 
-EffectBase::EffectBase() = default;
+EffectBase::EffectBase(EffectType effect_type) : effect_type(effect_type) {}
 EffectBase::~EffectBase() = default;
 
-UsageStatus EffectBase::GetUsage() const {
+UsageState EffectBase::GetUsage() const {
     return usage;
 }
 
+EffectType EffectBase::GetType() const {
+    return effect_type;
+}
+
+bool EffectBase::IsEnabled() const {
+    return enabled;
+}
+
+s32 EffectBase::GetMixID() const {
+    return mix_id;
+}
+
+s32 EffectBase::GetProcessingOrder() const {
+    return processing_order;
+}
+
+EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric::EffectGeneric(EffectType::I3dl2Reverb) {}
+EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
+
+void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
+    auto& internal_params = GetParams();
+    const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data());
+    if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
+        UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels);
+        return;
+    }
+
+    const auto last_status = internal_params.status;
+    mix_id = in_params.mix_id;
+    processing_order = in_params.processing_order;
+    internal_params = *reverb_params;
+    if (!ValidChannelCountForEffect(reverb_params->channel_count)) {
+        internal_params.channel_count = internal_params.max_channels;
+    }
+    enabled = in_params.is_enabled;
+    if (last_status != ParameterStatus::Updated) {
+        internal_params.status = last_status;
+    }
+
+    if (in_params.is_new || skipped) {
+        usage = UsageState::Initialized;
+        internal_params.status = ParameterStatus::Initialized;
+        skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+    }
+}
+
+void EffectI3dl2Reverb::UpdateForCommandGeneration() {
+    if (enabled) {
+        usage = UsageState::Running;
+    } else {
+        usage = UsageState::Stopped;
+    }
+    GetParams().status = ParameterStatus::Updated;
+}
+
+EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric::EffectGeneric(EffectType::BiquadFilter) {}
+EffectBiquadFilter::~EffectBiquadFilter() = default;
+
+void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) {
+    auto& internal_params = GetParams();
+    const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data());
+    mix_id = in_params.mix_id;
+    processing_order = in_params.processing_order;
+    internal_params = *biquad_params;
+    enabled = in_params.is_enabled;
+}
+
+void EffectBiquadFilter::UpdateForCommandGeneration() {
+    if (enabled) {
+        usage = UsageState::Running;
+    } else {
+        usage = UsageState::Stopped;
+    }
+    GetParams().status = ParameterStatus::Updated;
+}
+
+EffectAuxInfo::EffectAuxInfo() : EffectGeneric::EffectGeneric(EffectType::Aux) {}
+EffectAuxInfo::~EffectAuxInfo() = default;
+
+void EffectAuxInfo::Update(EffectInfo::InParams& in_params) {
+    const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data());
+    mix_id = in_params.mix_id;
+    processing_order = in_params.processing_order;
+    GetParams() = *aux_params;
+    enabled = in_params.is_enabled;
+
+    if (in_params.is_new || skipped) {
+        skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0;
+        if (skipped) {
+            return;
+        }
+
+        // There's two AuxInfos which are an identical size, the first one is managed by the cpu,
+        // the second is managed by the dsp. All we care about is managing the DSP one
+        send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP);
+        send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2);
+
+        recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP);
+        recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2);
+    }
+}
+
+void EffectAuxInfo::UpdateForCommandGeneration() {
+    if (enabled) {
+        usage = UsageState::Running;
+    } else {
+        usage = UsageState::Stopped;
+    }
+}
+
+const VAddr EffectAuxInfo::GetSendInfo() const {
+    return send_info;
+}
+
+const VAddr EffectAuxInfo::GetSendBuffer() const {
+    return send_buffer;
+}
+
+const VAddr EffectAuxInfo::GetRecvInfo() const {
+    return recv_info;
+}
+
+const VAddr EffectAuxInfo::GetRecvBuffer() const {
+    return recv_buffer;
+}
+
+EffectDelay::EffectDelay() : EffectGeneric::EffectGeneric(EffectType::Delay) {}
+EffectDelay::~EffectDelay() = default;
+
+void EffectDelay::Update(EffectInfo::InParams& in_params) {
+    const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data());
+    auto& internal_params = GetParams();
+    if (!ValidChannelCountForEffect(delay_params->max_channels)) {
+        return;
+    }
+
+    const auto last_status = internal_params.status;
+    mix_id = in_params.mix_id;
+    processing_order = in_params.processing_order;
+    internal_params = *delay_params;
+    if (!ValidChannelCountForEffect(delay_params->channels)) {
+        internal_params.channels = internal_params.max_channels;
+    }
+    enabled = in_params.is_enabled;
+
+    if (last_status != ParameterStatus::Updated) {
+        internal_params.status = last_status;
+    }
+
+    if (in_params.is_new || skipped) {
+        usage = UsageState::Initialized;
+        internal_params.status = ParameterStatus::Initialized;
+        skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+    }
+}
+
+void EffectDelay::UpdateForCommandGeneration() {
+    if (enabled) {
+        usage = UsageState::Running;
+    } else {
+        usage = UsageState::Stopped;
+    }
+    GetParams().status = ParameterStatus::Updated;
+}
+
+EffectBufferMixer::EffectBufferMixer() : EffectGeneric::EffectGeneric(EffectType::BufferMixer) {}
+EffectBufferMixer::~EffectBufferMixer() = default;
+
+void EffectBufferMixer::Update(EffectInfo::InParams& in_params) {
+    mix_id = in_params.mix_id;
+    processing_order = in_params.processing_order;
+    GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data());
+    enabled = in_params.is_enabled;
+}
+
+void EffectBufferMixer::UpdateForCommandGeneration() {
+    if (enabled) {
+        usage = UsageState::Running;
+    } else {
+        usage = UsageState::Stopped;
+    }
+}
+
+EffectReverb::EffectReverb() : EffectGeneric::EffectGeneric(EffectType::Reverb) {}
+EffectReverb::~EffectReverb() = default;
+
+void EffectReverb::Update(EffectInfo::InParams& in_params) {
+    const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data());
+    auto& internal_params = GetParams();
+    if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
+        return;
+    }
+
+    const auto last_status = internal_params.status;
+    mix_id = in_params.mix_id;
+    processing_order = in_params.processing_order;
+    internal_params = *reverb_params;
+    if (!ValidChannelCountForEffect(reverb_params->channels)) {
+        internal_params.channels = internal_params.max_channels;
+    }
+    enabled = in_params.is_enabled;
+
+    if (last_status != ParameterStatus::Updated) {
+        internal_params.status = last_status;
+    }
+
+    if (in_params.is_new || skipped) {
+        usage = UsageState::Initialized;
+        internal_params.status = ParameterStatus::Initialized;
+        skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+    }
+}
+
+void EffectReverb::UpdateForCommandGeneration() {
+    if (enabled) {
+        usage = UsageState::Running;
+    } else {
+        usage = UsageState::Stopped;
+    }
+    GetParams().status = ParameterStatus::Updated;
+}
+
 } // namespace AudioCore
diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h
index e3c3672966..2f2da72dd2 100644
--- a/src/audio_core/effect_context.h
+++ b/src/audio_core/effect_context.h
@@ -31,6 +31,19 @@ enum class UsageStatus : u8 {
     Removed = 4,
 };
 
+enum class UsageState {
+    Invalid = 0,
+    Initialized = 1,
+    Running = 2,
+    Stopped = 3,
+};
+
+enum class ParameterStatus : u8 {
+    Initialized = 0,
+    Updating = 1,
+    Updated = 2,
+};
+
 struct BufferMixerParams {
     std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
     std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
@@ -39,6 +52,14 @@ struct BufferMixerParams {
 };
 static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
 
+struct AuxInfoDSP {
+    u32_le read_offset{};
+    u32_le write_offset{};
+    u32_le remaining{};
+    INSERT_PADDING_WORDS(13);
+};
+static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size");
+
 struct AuxInfo {
     std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
     std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
@@ -54,6 +75,81 @@ struct AuxInfo {
 };
 static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
 
+struct I3dl2ReverbParams {
+    std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+    std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+    u16_le max_channels{};
+    u16_le channel_count{};
+    INSERT_PADDING_BYTES(1);
+    u32_le sample_rate{};
+    f32 room_hf{};
+    f32 hf_reference{};
+    f32 decay_time{};
+    f32 hf_decay_ratio{};
+    f32 room{};
+    f32 reflection{};
+    f32 reverb{};
+    f32 diffusion{};
+    f32 reflection_delay{};
+    f32 reverb_delay{};
+    f32 density{};
+    f32 dry_gain{};
+    ParameterStatus status{};
+    INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size");
+
+struct BiquadFilterParams {
+    std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+    std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+    std::array<s16_le, 3> numerator;
+    std::array<s16_le, 2> denominator;
+    s8 channel_count{};
+    ParameterStatus status{};
+};
+static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size");
+
+struct DelayParams {
+    std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+    std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+    u16_le max_channels{};
+    u16_le channels{};
+    s32_le max_delay{};
+    s32_le delay{};
+    s32_le sample_rate{};
+    s32_le gain{};
+    s32_le feedback_gain{};
+    s32_le out_gain{};
+    s32_le dry_gain{};
+    s32_le channel_spread{};
+    s32_le low_pass{};
+    ParameterStatus status{};
+    INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size");
+
+struct ReverbParams {
+    std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+    std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+    u16_le max_channels{};
+    u16_le channels{};
+    s32_le sample_rate{};
+    s32_le mode0{};
+    s32_le mode0_gain{};
+    s32_le pre_delay{};
+    s32_le mode1{};
+    s32_le mode1_gain{};
+    s32_le decay{};
+    s32_le hf_decay_ratio{};
+    s32_le coloration{};
+    s32_le reverb_gain{};
+    s32_le out_gain{};
+    s32_le dry_gain{};
+    ParameterStatus status{};
+    INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size");
+
 class EffectInfo {
 public:
     struct InParams {
@@ -64,7 +160,7 @@ public:
         s32_le mix_id{};
         u64_le buffer_address{};
         u64_le buffer_size{};
-        s32_le priority{};
+        s32_le processing_order{};
         INSERT_PADDING_BYTES(4);
         union {
             std::array<u8, 0xa0> raw;
@@ -79,16 +175,50 @@ public:
     static_assert(sizeof(EffectInfo::OutParams) == 0x10, "OutParams is an invalid size");
 };
 
+struct AuxAddress {
+    VAddr send_dsp_info{};
+    VAddr send_buffer_base{};
+    VAddr return_dsp_info{};
+    VAddr return_buffer_base{};
+};
+
 class EffectBase {
 public:
-    EffectBase();
+    EffectBase(EffectType effect_type);
     ~EffectBase();
 
     virtual void Update(EffectInfo::InParams& in_params) = 0;
-    UsageStatus GetUsage() const;
+    virtual void UpdateForCommandGeneration() = 0;
+    UsageState GetUsage() const;
+    EffectType GetType() const;
+    bool IsEnabled() const;
+    s32 GetMixID() const;
+    s32 GetProcessingOrder() const;
 
 protected:
-    UsageStatus usage{UsageStatus::Invalid};
+    UsageState usage{UsageState::Invalid};
+    EffectType effect_type{};
+    s32 mix_id{};
+    s32 processing_order{};
+    bool enabled = false;
+};
+
+template <typename T>
+class EffectGeneric : public EffectBase {
+public:
+    EffectGeneric(EffectType effect_type) : EffectBase::EffectBase(effect_type) {}
+    ~EffectGeneric() = default;
+
+    T& GetParams() {
+        return internal_params;
+    }
+
+    const I3dl2ReverbParams& GetParams() const {
+        return internal_params;
+    }
+
+private:
+    T internal_params{};
 };
 
 class EffectStubbed : public EffectBase {
@@ -97,6 +227,82 @@ public:
     ~EffectStubbed();
 
     void Update(EffectInfo::InParams& in_params) override;
+    void UpdateForCommandGeneration() override;
+};
+
+class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
+public:
+    explicit EffectI3dl2Reverb();
+    ~EffectI3dl2Reverb();
+
+    void Update(EffectInfo::InParams& in_params) override;
+    void UpdateForCommandGeneration() override;
+
+private:
+    bool skipped = false;
+};
+
+class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
+public:
+    explicit EffectBiquadFilter();
+    ~EffectBiquadFilter();
+
+    void Update(EffectInfo::InParams& in_params) override;
+    void UpdateForCommandGeneration() override;
+};
+
+class EffectAuxInfo : public EffectGeneric<AuxInfo> {
+public:
+    explicit EffectAuxInfo();
+    ~EffectAuxInfo();
+
+    void Update(EffectInfo::InParams& in_params) override;
+    void UpdateForCommandGeneration() override;
+    const VAddr GetSendInfo() const;
+    const VAddr GetSendBuffer() const;
+    const VAddr GetRecvInfo() const;
+    const VAddr GetRecvBuffer() const;
+
+private:
+    VAddr send_info{};
+    VAddr send_buffer{};
+    VAddr recv_info{};
+    VAddr recv_buffer{};
+    bool skipped = false;
+    AuxAddress addresses{};
+};
+
+class EffectDelay : public EffectGeneric<DelayParams> {
+public:
+    explicit EffectDelay();
+    ~EffectDelay();
+
+    void Update(EffectInfo::InParams& in_params) override;
+    void UpdateForCommandGeneration() override;
+
+private:
+    bool skipped = false;
+};
+
+class EffectBufferMixer : public EffectGeneric<BufferMixerParams> {
+public:
+    explicit EffectBufferMixer();
+    ~EffectBufferMixer();
+
+    void Update(EffectInfo::InParams& in_params) override;
+    void UpdateForCommandGeneration() override;
+};
+
+class EffectReverb : public EffectGeneric<ReverbParams> {
+public:
+    explicit EffectReverb();
+    ~EffectReverb();
+
+    void Update(EffectInfo::InParams& in_params) override;
+    void UpdateForCommandGeneration() override;
+
+private:
+    bool skipped = false;
 };
 
 class EffectContext {
@@ -106,6 +312,7 @@ public:
 
     std::size_t GetCount() const;
     EffectBase* GetInfo(std::size_t i);
+    EffectBase* RetargetEffect(std::size_t i, EffectType effect);
     const EffectBase* GetInfo(std::size_t i) const;
 
 private:
diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp
index 286aa03211..f53ce21a5a 100644
--- a/src/audio_core/info_updater.cpp
+++ b/src/audio_core/info_updater.cpp
@@ -244,14 +244,15 @@ bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
     // Update effects
     for (std::size_t i = 0; i < effect_count; i++) {
         auto* info = effect_context.GetInfo(i);
+        if (effect_in[i].type != info->GetType()) {
+            info = effect_context.RetargetEffect(i, effect_in[i].type);
+        }
+
         info->Update(effect_in[i]);
 
-        // TODO(ogniK): Update individual effects
-        if ((!is_active && info->GetUsage() != UsageStatus::New) ||
-            info->GetUsage() == UsageStatus::Removed) {
+        if ((!is_active && info->GetUsage() != UsageState::Initialized) ||
+            info->GetUsage() == UsageState::Stopped) {
             effect_out[i].status = UsageStatus::Removed;
-        } else if (info->GetUsage() == UsageStatus::New) {
-            effect_out[i].status = UsageStatus::New;
         } else {
             effect_out[i].status = UsageStatus::Used;
         }
@@ -290,7 +291,8 @@ bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
 }
 
 ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
-                                    SplitterContext& splitter_context) {
+                                    SplitterContext& splitter_context,
+                                    EffectContext& effect_context) {
     std::vector<MixInfo::InParams> mix_in_params;
 
     if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
@@ -387,13 +389,13 @@ ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buf
         auto& mix_info_params = mix_info.GetInParams();
         if (mix_info_params.in_use != mix_in.in_use) {
             mix_info_params.in_use = mix_in.in_use;
-            // TODO(ogniK): Update effect processing order
+            mix_info.ResetEffectProcessingOrder();
             should_sort = true;
         }
 
         if (mix_in.in_use) {
             should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
-                                           splitter_context);
+                                           splitter_context, effect_context);
         }
     }
 
diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h
index 6969de67dd..06f9d770f0 100644
--- a/src/audio_core/info_updater.h
+++ b/src/audio_core/info_updater.h
@@ -34,7 +34,7 @@ public:
     bool UpdateEffects(EffectContext& effect_context, bool is_active);
     bool UpdateSplitterInfo(SplitterContext& splitter_context);
     ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
-                           SplitterContext& splitter_context);
+                           SplitterContext& splitter_context, EffectContext& effect_context);
     bool UpdateSinks(SinkContext& sink_context);
     bool UpdatePerformanceBuffer();
     bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp
index f6f119a113..0428914907 100644
--- a/src/audio_core/mix_context.cpp
+++ b/src/audio_core/mix_context.cpp
@@ -4,6 +4,7 @@
 
 #include "audio_core/behavior_info.h"
 #include "audio_core/common.h"
+#include "audio_core/effect_context.h"
 #include "audio_core/mix_context.h"
 #include "audio_core/splitter_context.h"
 
@@ -11,7 +12,8 @@ namespace AudioCore {
 MixContext::MixContext() = default;
 MixContext::~MixContext() = default;
 
-void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count) {
+void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
+                            std::size_t effect_count) {
     info_count = mix_count;
     infos.resize(info_count);
     auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
@@ -21,6 +23,10 @@ void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_c
         sorted_info.push_back(&info);
     }
 
+    for (auto& info : infos) {
+        info.SetEffectCount(effect_count);
+    }
+
     // Only initialize our edge matrix and node states if splitters are supported
     if (behavior_info.IsSplitterSupported()) {
         node_states.Initialize(mix_count);
@@ -185,7 +191,8 @@ ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
 }
 
 bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
-                           BehaviorInfo& behavior_info, SplitterContext& splitter_context) {
+                           BehaviorInfo& behavior_info, SplitterContext& splitter_context,
+                           EffectContext& effect_context) {
     in_params.volume = mix_in.volume;
     in_params.sample_rate = mix_in.sample_rate;
     in_params.buffer_count = mix_in.buffer_count;
@@ -206,6 +213,15 @@ bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix
         in_params.splitter_id = AudioCommon::NO_SPLITTER;
     }
 
+    ResetEffectProcessingOrder();
+    const auto effect_count = effect_context.GetCount();
+    for (std::size_t i = 0; i < effect_count; i++) {
+        auto* effect_info = effect_context.GetInfo(i);
+        if (effect_info->GetMixID() == in_params.mix_id) {
+            effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
+        }
+    }
+
     // TODO(ogniK): Update effect processing order
     return require_sort;
 }
@@ -228,6 +244,21 @@ void ServerMixInfo::Cleanup() {
     std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
 }
 
+void ServerMixInfo::SetEffectCount(std::size_t count) {
+    effect_processing_order.resize(count);
+    ResetEffectProcessingOrder();
+}
+
+void ServerMixInfo::ResetEffectProcessingOrder() {
+    for (auto& order : effect_processing_order) {
+        order = AudioCommon::NO_EFFECT_ORDER;
+    }
+}
+
+s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
+    return effect_processing_order.at(i);
+}
+
 bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
                                      SplitterContext& splitter_context) {
     // Mixes are identical
diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h
index 3815666999..6a588eeb4e 100644
--- a/src/audio_core/mix_context.h
+++ b/src/audio_core/mix_context.h
@@ -13,6 +13,7 @@
 
 namespace AudioCore {
 class BehaviorInfo;
+class EffectContext;
 
 class MixInfo {
 public:
@@ -65,11 +66,16 @@ public:
     ServerMixInfo::InParams& GetInParams();
 
     bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
-                BehaviorInfo& behavior_info, SplitterContext& splitter_context);
+                BehaviorInfo& behavior_info, SplitterContext& splitter_context,
+                EffectContext& effect_context);
     bool HasAnyConnection() const;
     void Cleanup();
+    void SetEffectCount(std::size_t count);
+    void ResetEffectProcessingOrder();
+    s32 GetEffectOrder(std::size_t i) const;
 
 private:
+    std::vector<s32> effect_processing_order;
     InParams in_params{};
     bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
                           SplitterContext& splitter_context);
@@ -80,7 +86,8 @@ public:
     MixContext();
     ~MixContext();
 
-    void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count);
+    void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
+                    std::size_t effect_count);
     void SortInfo();
     bool TsortInfo(SplitterContext& splitter_context);