summaryrefslogtreecommitdiffstats
path: root/src/audio_core/renderer
diff options
context:
space:
mode:
authorKelebek1 <eeeedddccc@hotmail.co.uk>2022-07-17 00:48:45 +0200
committerKelebek1 <eeeedddccc@hotmail.co.uk>2022-07-22 02:11:32 +0200
commit458da8a94877677f086f06cdeecf959ec4283a33 (patch)
tree583166d77602ad90a0d552f37de8729ad80fd6c1 /src/audio_core/renderer
parentMerge pull request #8598 from Link4565/recv-dontwait (diff)
downloadyuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.gz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.bz2
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.lz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.xz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.zst
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.zip
Diffstat (limited to 'src/audio_core/renderer')
-rw-r--r--src/audio_core/renderer/adsp/adsp.cpp118
-rw-r--r--src/audio_core/renderer/adsp/adsp.h173
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.cpp226
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.h203
-rw-r--r--src/audio_core/renderer/adsp/command_buffer.h21
-rw-r--r--src/audio_core/renderer/adsp/command_list_processor.cpp109
-rw-r--r--src/audio_core/renderer/adsp/command_list_processor.h118
-rw-r--r--src/audio_core/renderer/audio_device.cpp52
-rw-r--r--src/audio_core/renderer/audio_device.h88
-rw-r--r--src/audio_core/renderer/audio_renderer.cpp67
-rw-r--r--src/audio_core/renderer/audio_renderer.h97
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.cpp191
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.h376
-rw-r--r--src/audio_core/renderer/behavior/info_updater.cpp539
-rw-r--r--src/audio_core/renderer/behavior/info_updater.h205
-rw-r--r--src/audio_core/renderer/command/command_buffer.cpp714
-rw-r--r--src/audio_core/renderer/command/command_buffer.h466
-rw-r--r--src/audio_core/renderer/command/command_generator.cpp796
-rw-r--r--src/audio_core/renderer/command/command_generator.h349
-rw-r--r--src/audio_core/renderer/command/command_list_header.h22
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.cpp3620
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.h254
-rw-r--r--src/audio_core/renderer/command/commands.h32
-rw-r--r--src/audio_core/renderer/command/data_source/adpcm.cpp84
-rw-r--r--src/audio_core/renderer/command/data_source/adpcm.h119
-rw-r--r--src/audio_core/renderer/command/data_source/decode.cpp428
-rw-r--r--src/audio_core/renderer/command/data_source/decode.h59
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_float.cpp86
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_float.h113
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_int16.cpp87
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_int16.h110
-rw-r--r--src/audio_core/renderer/command/effect/aux_.cpp207
-rw-r--r--src/audio_core/renderer/command/effect/aux_.h66
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.cpp118
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.h74
-rw-r--r--src/audio_core/renderer/command/effect/capture.cpp142
-rw-r--r--src/audio_core/renderer/command/effect/capture.h62
-rw-r--r--src/audio_core/renderer/command/effect/compressor.cpp156
-rw-r--r--src/audio_core/renderer/command/effect/compressor.h60
-rw-r--r--src/audio_core/renderer/command/effect/delay.cpp238
-rw-r--r--src/audio_core/renderer/command/effect/delay.h60
-rw-r--r--src/audio_core/renderer/command/effect/i3dl2_reverb.cpp437
-rw-r--r--src/audio_core/renderer/command/effect/i3dl2_reverb.h60
-rw-r--r--src/audio_core/renderer/command/effect/light_limiter.cpp222
-rw-r--r--src/audio_core/renderer/command/effect/light_limiter.h103
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp45
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h59
-rw-r--r--src/audio_core/renderer/command/effect/reverb.cpp440
-rw-r--r--src/audio_core/renderer/command/effect/reverb.h62
-rw-r--r--src/audio_core/renderer/command/icommand.h93
-rw-r--r--src/audio_core/renderer/command/mix/clear_mix.cpp24
-rw-r--r--src/audio_core/renderer/command/mix/clear_mix.h45
-rw-r--r--src/audio_core/renderer/command/mix/copy_mix.cpp27
-rw-r--r--src/audio_core/renderer/command/mix/copy_mix.h49
-rw-r--r--src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp64
-rw-r--r--src/audio_core/renderer/command/mix/depop_for_mix_buffers.h55
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.cpp36
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.h54
-rw-r--r--src/audio_core/renderer/command/mix/mix.cpp70
-rw-r--r--src/audio_core/renderer/command/mix/mix.h54
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.cpp94
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.h73
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp65
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.h61
-rw-r--r--src/audio_core/renderer/command/mix/volume.cpp72
-rw-r--r--src/audio_core/renderer/command/mix/volume.h53
-rw-r--r--src/audio_core/renderer/command/mix/volume_ramp.cpp84
-rw-r--r--src/audio_core/renderer/command/mix/volume_ramp.h56
-rw-r--r--src/audio_core/renderer/command/performance/performance.cpp43
-rw-r--r--src/audio_core/renderer/command/performance/performance.h51
-rw-r--r--src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp74
-rw-r--r--src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h59
-rw-r--r--src/audio_core/renderer/command/resample/resample.cpp883
-rw-r--r--src/audio_core/renderer/command/resample/resample.h29
-rw-r--r--src/audio_core/renderer/command/resample/upsample.cpp262
-rw-r--r--src/audio_core/renderer/command/resample/upsample.h60
-rw-r--r--src/audio_core/renderer/command/sink/circular_buffer.cpp48
-rw-r--r--src/audio_core/renderer/command/sink/circular_buffer.h55
-rw-r--r--src/audio_core/renderer/command/sink/device.cpp55
-rw-r--r--src/audio_core/renderer/command/sink/device.h57
-rw-r--r--src/audio_core/renderer/effect/aux_.cpp93
-rw-r--r--src/audio_core/renderer/effect/aux_.h123
-rw-r--r--src/audio_core/renderer/effect/biquad_filter.cpp52
-rw-r--r--src/audio_core/renderer/effect/biquad_filter.h79
-rw-r--r--src/audio_core/renderer/effect/buffer_mixer.cpp49
-rw-r--r--src/audio_core/renderer/effect/buffer_mixer.h75
-rw-r--r--src/audio_core/renderer/effect/capture.cpp82
-rw-r--r--src/audio_core/renderer/effect/capture.h65
-rw-r--r--src/audio_core/renderer/effect/compressor.cpp40
-rw-r--r--src/audio_core/renderer/effect/compressor.h106
-rw-r--r--src/audio_core/renderer/effect/delay.cpp93
-rw-r--r--src/audio_core/renderer/effect/delay.h135
-rw-r--r--src/audio_core/renderer/effect/effect_context.cpp41
-rw-r--r--src/audio_core/renderer/effect/effect_context.h75
-rw-r--r--src/audio_core/renderer/effect/effect_info_base.h435
-rw-r--r--src/audio_core/renderer/effect/effect_reset.h71
-rw-r--r--src/audio_core/renderer/effect/effect_result_state.h16
-rw-r--r--src/audio_core/renderer/effect/i3dl2.cpp94
-rw-r--r--src/audio_core/renderer/effect/i3dl2.h200
-rw-r--r--src/audio_core/renderer/effect/light_limiter.cpp81
-rw-r--r--src/audio_core/renderer/effect/light_limiter.h138
-rw-r--r--src/audio_core/renderer/effect/reverb.cpp93
-rw-r--r--src/audio_core/renderer/effect/reverb.h190
-rw-r--r--src/audio_core/renderer/memory/address_info.h125
-rw-r--r--src/audio_core/renderer/memory/memory_pool_info.cpp61
-rw-r--r--src/audio_core/renderer/memory/memory_pool_info.h170
-rw-r--r--src/audio_core/renderer/memory/pool_mapper.cpp243
-rw-r--r--src/audio_core/renderer/memory/pool_mapper.h179
-rw-r--r--src/audio_core/renderer/mix/mix_context.cpp141
-rw-r--r--src/audio_core/renderer/mix/mix_context.h124
-rw-r--r--src/audio_core/renderer/mix/mix_info.cpp120
-rw-r--r--src/audio_core/renderer/mix/mix_info.h124
-rw-r--r--src/audio_core/renderer/nodes/bit_array.h25
-rw-r--r--src/audio_core/renderer/nodes/edge_matrix.cpp38
-rw-r--r--src/audio_core/renderer/nodes/edge_matrix.h82
-rw-r--r--src/audio_core/renderer/nodes/node_states.cpp141
-rw-r--r--src/audio_core/renderer/nodes/node_states.h195
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.cpp25
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.h33
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.cpp23
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.h32
-rw-r--r--src/audio_core/renderer/performance/performance_detail.h50
-rw-r--r--src/audio_core/renderer/performance/performance_entry.h37
-rw-r--r--src/audio_core/renderer/performance/performance_entry_addresses.h17
-rw-r--r--src/audio_core/renderer/performance/performance_frame_header.h36
-rw-r--r--src/audio_core/renderer/performance/performance_manager.cpp645
-rw-r--r--src/audio_core/renderer/performance/performance_manager.h273
-rw-r--r--src/audio_core/renderer/sink/circular_buffer_sink_info.cpp76
-rw-r--r--src/audio_core/renderer/sink/circular_buffer_sink_info.h41
-rw-r--r--src/audio_core/renderer/sink/device_sink_info.cpp57
-rw-r--r--src/audio_core/renderer/sink/device_sink_info.h40
-rw-r--r--src/audio_core/renderer/sink/sink_context.cpp21
-rw-r--r--src/audio_core/renderer/sink/sink_context.h47
-rw-r--r--src/audio_core/renderer/sink/sink_info_base.cpp51
-rw-r--r--src/audio_core/renderer/sink/sink_info_base.h177
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.cpp217
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.h189
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.cpp87
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.h135
-rw-r--r--src/audio_core/renderer/splitter/splitter_info.cpp79
-rw-r--r--src/audio_core/renderer/splitter/splitter_info.h107
-rw-r--r--src/audio_core/renderer/system.cpp802
-rw-r--r--src/audio_core/renderer/system.h307
-rw-r--r--src/audio_core/renderer/system_manager.cpp162
-rw-r--r--src/audio_core/renderer/system_manager.h113
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_info.cpp6
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_info.h35
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.cpp44
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.h45
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_state.h40
-rw-r--r--src/audio_core/renderer/voice/voice_channel_resource.h38
-rw-r--r--src/audio_core/renderer/voice/voice_context.cpp86
-rw-r--r--src/audio_core/renderer/voice/voice_context.h126
-rw-r--r--src/audio_core/renderer/voice/voice_info.cpp408
-rw-r--r--src/audio_core/renderer/voice/voice_info.h378
-rw-r--r--src/audio_core/renderer/voice/voice_state.h70
156 files changed, 24687 insertions, 0 deletions
diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp
new file mode 100644
index 000000000..e05a22d86
--- /dev/null
+++ b/src/audio_core/renderer/adsp/adsp.cpp
@@ -0,0 +1,118 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/adsp.h"
+#include "audio_core/renderer/adsp/command_buffer.h"
+#include "audio_core/sink/sink.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer::ADSP {
+
+ADSP::ADSP(Core::System& system_, Sink::Sink& sink_)
+ : system{system_}, memory{system.Memory()}, sink{sink_} {}
+
+ADSP::~ADSP() {
+ ClearCommandBuffers();
+}
+
+State ADSP::GetState() const {
+ if (running) {
+ return State::Started;
+ }
+ return State::Stopped;
+}
+
+AudioRenderer_Mailbox* ADSP::GetRenderMailbox() {
+ return &render_mailbox;
+}
+
+void ADSP::ClearRemainCount(const u32 session_id) {
+ render_mailbox.ClearRemainCount(session_id);
+}
+
+u64 ADSP::GetSignalledTick() const {
+ return render_mailbox.GetSignalledTick();
+}
+
+u64 ADSP::GetTimeTaken() const {
+ return render_mailbox.GetRenderTimeTaken();
+}
+
+u64 ADSP::GetRenderTimeTaken(const u32 session_id) {
+ return render_mailbox.GetCommandBuffer(session_id).render_time_taken;
+}
+
+u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
+ return render_mailbox.GetRemainCommandCount(session_id);
+}
+
+void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) {
+ render_mailbox.SetCommandBuffer(session_id, command_buffer);
+}
+
+u64 ADSP::GetRenderingStartTick(const u32 session_id) {
+ return render_mailbox.GetSignalledTick() +
+ render_mailbox.GetCommandBuffer(session_id).render_time_taken;
+}
+
+bool ADSP::Start() {
+ if (running) {
+ return running;
+ }
+
+ running = true;
+ systems_active++;
+ audio_renderer = std::make_unique<AudioRenderer>(system);
+ audio_renderer->Start(&render_mailbox);
+ render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK);
+ if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
+ LOG_ERROR(
+ Service_Audio,
+ "Host Audio Renderer -- Failed to receive initialize message response from ADSP!");
+ }
+ return running;
+}
+
+void ADSP::Stop() {
+ systems_active--;
+ if (running && systems_active == 0) {
+ {
+ std::scoped_lock l{mailbox_lock};
+ render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown);
+ if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) {
+ LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
+ "message response from ADSP!");
+ }
+ }
+ audio_renderer->Stop();
+ running = false;
+ }
+}
+
+void ADSP::Signal() {
+ const auto signalled_tick{system.CoreTiming().GetClockTicks()};
+ render_mailbox.SetSignalledTick(signalled_tick);
+ render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render);
+}
+
+void ADSP::Wait() {
+ std::scoped_lock l{mailbox_lock};
+ auto response{render_mailbox.HostWaitMessage()};
+ if (response != RenderMessage::AudioRenderer_RenderResponse) {
+ LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}",
+ static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse),
+ static_cast<u32>(response));
+ }
+
+ ClearCommandBuffers();
+}
+
+void ADSP::ClearCommandBuffers() {
+ render_mailbox.ClearCommandBuffers();
+}
+
+} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h
new file mode 100644
index 000000000..4dfcef4a5
--- /dev/null
+++ b/src/audio_core/renderer/adsp/adsp.h
@@ -0,0 +1,173 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+
+#include "audio_core/renderer/adsp/audio_renderer.h"
+#include "common/common_types.h"
+
+namespace Core {
+namespace Memory {
+class Memory;
+}
+class System;
+} // namespace Core
+
+namespace AudioCore {
+namespace Sink {
+class Sink;
+}
+
+namespace AudioRenderer::ADSP {
+struct CommandBuffer;
+
+enum class State {
+ Started,
+ Stopped,
+};
+
+/**
+ * Represents the ADSP embedded within the audio sysmodule.
+ * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot.
+ *
+ * The kernel will run apps you program for it, Nintendo have the following:
+ *
+ * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all
+ * audio samples end up, and we skip it entirely, since we have very different backends and
+ * mixing is implicitly handled by the OS (but also due to lack of research/simplicity).
+ *
+ * AudioRenderer - Receives command lists generated by the audio render
+ * system, processes them, and sends the samples to Gmix.
+ *
+ * OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix.
+ * Not much research done here, TODO if needed.
+ *
+ * We only implement the AudioRenderer for now.
+ *
+ * Communication for the apps is done through mailboxes, and some shared memory.
+ */
+class ADSP {
+public:
+ explicit ADSP(Core::System& system, Sink::Sink& sink);
+ ~ADSP();
+
+ /**
+ * Start the ADSP.
+ *
+ * @return True if started or already running, otherwise false.
+ */
+ bool Start();
+
+ /**
+ * Stop the ADSP.
+ *
+ * @return True if started or already running, otherwise false.
+ */
+ void Stop();
+
+ /**
+ * Get the ADSP's state.
+ *
+ * @return Started or Stopped.
+ */
+ State GetState() const;
+
+ /**
+ * Get the AudioRenderer mailbox to communicate with it.
+ *
+ * @return The AudioRenderer mailbox.
+ */
+ AudioRenderer_Mailbox* GetRenderMailbox();
+
+ /**
+ * Get the tick the ADSP was signalled.
+ *
+ * @return The tick the ADSP was signalled.
+ */
+ u64 GetSignalledTick() const;
+
+ /**
+ * Get the total time it took for the ADSP to run the last command lists (both command lists).
+ *
+ * @return The tick the ADSP was signalled.
+ */
+ u64 GetTimeTaken() const;
+
+ /**
+ * Get the last time a given command list took to run.
+ *
+ * @param session_id - The session id to check (0 or 1).
+ * @return The time it took.
+ */
+ u64 GetRenderTimeTaken(u32 session_id);
+
+ /**
+ * Clear the remaining command count for a given session.
+ *
+ * @param session_id - The session id to check (0 or 1).
+ */
+ void ClearRemainCount(u32 session_id);
+
+ /**
+ * Get the remaining number of commands left to process for a command list.
+ *
+ * @param session_id - The session id to check (0 or 1).
+ * @return The number of commands remaining.
+ */
+ u32 GetRemainCommandCount(u32 session_id) const;
+
+ /**
+ * Get the last tick a command list started processing.
+ *
+ * @param session_id - The session id to check (0 or 1).
+ * @return The last tick the given command list started.
+ */
+ u64 GetRenderingStartTick(u32 session_id);
+
+ /**
+ * Set a command buffer to be processed.
+ *
+ * @param session_id - The session id to check (0 or 1).
+ * @param command_buffer - The command buffer to process.
+ */
+ void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer);
+
+ /**
+ * Clear the command buffers (does not clear the time taken or the remaining command count)
+ */
+ void ClearCommandBuffers();
+
+ /**
+ * Signal the AudioRenderer to begin processing.
+ */
+ void Signal();
+
+ /**
+ * Wait for the AudioRenderer to finish processing.
+ */
+ void Wait();
+
+private:
+ /// Core system
+ Core::System& system;
+ /// Core memory
+ Core::Memory::Memory& memory;
+ /// Number of systems active, used to prevent accidental shutdowns
+ u8 systems_active{0};
+ /// ADSP running state
+ std::atomic<bool> running{false};
+ /// Output sink used by the ADSP
+ Sink::Sink& sink;
+ /// AudioRenderer app
+ std::unique_ptr<AudioRenderer> audio_renderer{};
+ /// Communication for the AudioRenderer
+ AudioRenderer_Mailbox render_mailbox{};
+ /// Mailbox lock ffor the render mailbox
+ std::mutex mailbox_lock;
+};
+
+} // namespace AudioRenderer::ADSP
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp
new file mode 100644
index 000000000..3967ccfe6
--- /dev/null
+++ b/src/audio_core/renderer/adsp/audio_renderer.cpp
@@ -0,0 +1,226 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <chrono>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/adsp/audio_renderer.h"
+#include "audio_core/sink/sink.h"
+#include "common/logging/log.h"
+#include "common/microprofile.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+
+MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
+
+namespace AudioCore::AudioRenderer::ADSP {
+
+void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) {
+ adsp_messages.enqueue(message_);
+ adsp_event.Set();
+}
+
+RenderMessage AudioRenderer_Mailbox::HostWaitMessage() {
+ host_event.Wait();
+ RenderMessage msg{RenderMessage::Invalid};
+ if (!host_messages.try_dequeue(msg)) {
+ LOG_ERROR(Service_Audio, "Failed to dequeue host message!");
+ }
+ return msg;
+}
+
+void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) {
+ host_messages.enqueue(message_);
+ host_event.Set();
+}
+
+RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
+ adsp_event.Wait();
+ RenderMessage msg{RenderMessage::Invalid};
+ if (!adsp_messages.try_dequeue(msg)) {
+ LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!");
+ }
+ return msg;
+}
+
+CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) {
+ return command_buffers[session_id];
+}
+
+void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) {
+ command_buffers[session_id] = buffer;
+}
+
+u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const {
+ return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken;
+}
+
+u64 AudioRenderer_Mailbox::GetSignalledTick() const {
+ return signalled_tick;
+}
+
+void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) {
+ signalled_tick = tick;
+}
+
+void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) {
+ command_buffers[session_id].remaining_command_count = 0;
+}
+
+u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const {
+ return command_buffers[session_id].remaining_command_count;
+}
+
+void AudioRenderer_Mailbox::ClearCommandBuffers() {
+ command_buffers[0].buffer = 0;
+ command_buffers[0].size = 0;
+ command_buffers[0].reset_buffers = false;
+ command_buffers[1].buffer = 0;
+ command_buffers[1].size = 0;
+ command_buffers[1].reset_buffers = false;
+}
+
+AudioRenderer::AudioRenderer(Core::System& system_)
+ : system{system_}, sink{system.AudioCore().GetOutputSink()} {
+ CreateSinkStreams();
+}
+
+AudioRenderer::~AudioRenderer() {
+ Stop();
+ for (auto& stream : streams) {
+ if (stream) {
+ sink.CloseStream(stream);
+ }
+ stream = nullptr;
+ }
+}
+
+void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
+ if (running) {
+ return;
+ }
+
+ mailbox = mailbox_;
+ thread = std::thread(&AudioRenderer::ThreadFunc, this);
+ for (auto& stream : streams) {
+ stream->Start();
+ }
+ running = true;
+}
+
+void AudioRenderer::Stop() {
+ if (!running) {
+ return;
+ }
+
+ for (auto& stream : streams) {
+ stream->Stop();
+ }
+ thread.join();
+ running = false;
+}
+
+void AudioRenderer::CreateSinkStreams() {
+ u32 channels{sink.GetDeviceChannels()};
+ for (u32 i = 0; i < MaxRendererSessions; i++) {
+ std::string name{fmt::format("ADSP_RenderStream-{}", i)};
+ streams[i] =
+ sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
+ }
+}
+
+void AudioRenderer::ThreadFunc() {
+ constexpr char name[]{"yuzu:AudioRenderer"};
+ MicroProfileOnThreadCreate(name);
+ Common::SetCurrentThreadName(name);
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
+ if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
+ LOG_ERROR(Service_Audio,
+ "ADSP Audio Renderer -- Failed to receive initialize message from host!");
+ return;
+ }
+
+ mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
+
+ constexpr u64 max_process_time{2'304'000ULL};
+
+ while (true) {
+ auto message{mailbox->ADSPWaitMessage()};
+ switch (message) {
+ case RenderMessage::AudioRenderer_Shutdown:
+ mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown);
+ return;
+
+ case RenderMessage::AudioRenderer_Render: {
+ std::array<bool, MaxRendererSessions> buffers_reset{};
+ std::array<u64, MaxRendererSessions> render_times_taken{};
+ const auto start_time{system.CoreTiming().GetClockTicks()};
+
+ for (u32 index = 0; index < 2; index++) {
+ auto& command_buffer{mailbox->GetCommandBuffer(index)};
+ auto& command_list_processor{command_list_processors[index]};
+
+ // Check this buffer is valid, as it may not be used.
+ if (command_buffer.buffer != 0) {
+ // If there are no remaining commands (from the previous list),
+ // this is a new command list, initalize it.
+ if (command_buffer.remaining_command_count == 0) {
+ command_list_processor.Initialize(system, command_buffer.buffer,
+ command_buffer.size, streams[index]);
+ }
+
+ if (command_buffer.reset_buffers && !buffers_reset[index]) {
+ streams[index]->ClearQueue();
+ buffers_reset[index] = true;
+ }
+
+ u64 max_time{max_process_time};
+ if (index == 1 && command_buffer.applet_resource_user_id ==
+ mailbox->GetCommandBuffer(0).applet_resource_user_id) {
+ max_time = max_process_time -
+ Core::Timing::CyclesToNs(render_times_taken[0]).count();
+ if (render_times_taken[0] > max_process_time) {
+ max_time = 0;
+ }
+ }
+
+ max_time = std::min(command_buffer.time_limit, max_time);
+ command_list_processor.SetProcessTimeMax(max_time);
+
+ // Process the command list
+ {
+ MICROPROFILE_SCOPE(Audio_Renderer);
+ render_times_taken[index] =
+ command_list_processor.Process(index) - start_time;
+ }
+
+ if (index == 0) {
+ auto stream{command_list_processor.GetOutputSinkStream()};
+ system.AudioCore().SetStreamQueue(stream->GetQueueSize());
+ }
+
+ const auto end_time{system.CoreTiming().GetClockTicks()};
+
+ command_buffer.remaining_command_count =
+ command_list_processor.GetRemainingCommandCount();
+ command_buffer.render_time_taken = end_time - start_time;
+ }
+ }
+
+ mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
+ } break;
+
+ default:
+ LOG_WARNING(Service_Audio,
+ "ADSP AudioRenderer received an invalid message, msg={:02X}!",
+ static_cast<u32>(message));
+ break;
+ }
+ }
+}
+
+} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h
new file mode 100644
index 000000000..b6ced9d2b
--- /dev/null
+++ b/src/audio_core/renderer/adsp/audio_renderer.h
@@ -0,0 +1,203 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <thread>
+
+#include "audio_core/renderer/adsp/command_buffer.h"
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "common/common_types.h"
+#include "common/reader_writer_queue.h"
+#include "common/thread.h"
+
+namespace Core {
+namespace Timing {
+struct EventType;
+}
+class System;
+} // namespace Core
+
+namespace AudioCore {
+namespace Sink {
+class Sink;
+}
+
+namespace AudioRenderer::ADSP {
+
+enum class RenderMessage {
+ /* 0x00 */ Invalid,
+ /* 0x01 */ AudioRenderer_MapUnmap_Map,
+ /* 0x02 */ AudioRenderer_MapUnmap_MapResponse,
+ /* 0x03 */ AudioRenderer_MapUnmap_Unmap,
+ /* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse,
+ /* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache,
+ /* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse,
+ /* 0x07 */ AudioRenderer_MapUnmap_Shutdown,
+ /* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse,
+ /* 0x16 */ AudioRenderer_InitializeOK = 0x16,
+ /* 0x20 */ AudioRenderer_RenderResponse = 0x20,
+ /* 0x2A */ AudioRenderer_Render = 0x2A,
+ /* 0x34 */ AudioRenderer_Shutdown = 0x34,
+};
+
+/**
+ * A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer
+ * running on the ADSP.
+ */
+class AudioRenderer_Mailbox {
+public:
+ /**
+ * Send a message from the host to the AudioRenderer.
+ *
+ * @param message_ - The message to send to the AudioRenderer.
+ */
+ void HostSendMessage(RenderMessage message);
+
+ /**
+ * Host wait for a message from the AudioRenderer.
+ *
+ * @return The message returned from the AudioRenderer.
+ */
+ RenderMessage HostWaitMessage();
+
+ /**
+ * Send a message from the AudioRenderer to the host.
+ *
+ * @param message_ - The message to send to the host.
+ */
+ void ADSPSendMessage(RenderMessage message);
+
+ /**
+ * AudioRenderer wait for a message from the host.
+ *
+ * @return The message returned from the AudioRenderer.
+ */
+ RenderMessage ADSPWaitMessage();
+
+ /**
+ * Get the command buffer with the given session id (0 or 1).
+ *
+ * @param session_id - The session id to get (0 or 1).
+ * @return The command buffer.
+ */
+ CommandBuffer& GetCommandBuffer(s32 session_id);
+
+ /**
+ * Set the command buffer with the given session id (0 or 1).
+ *
+ * @param session_id - The session id to get (0 or 1).
+ * @param buffer - The command buffer to set.
+ */
+ void SetCommandBuffer(u32 session_id, CommandBuffer& buffer);
+
+ /**
+ * Get the total render time taken for the last command lists sent.
+ *
+ * @return Total render time taken for the last command lists.
+ */
+ u64 GetRenderTimeTaken() const;
+
+ /**
+ * Get the tick the AudioRenderer was signalled.
+ *
+ * @return The tick the AudioRenderer was signalled.
+ */
+ u64 GetSignalledTick() const;
+
+ /**
+ * Set the tick the AudioRenderer was signalled.
+ *
+ * @param tick - The tick the AudioRenderer was signalled.
+ */
+ void SetSignalledTick(u64 tick);
+
+ /**
+ * Clear the remaining command count.
+ *
+ * @param session_id - Index for which command list to clear (0 or 1).
+ */
+ void ClearRemainCount(u32 session_id);
+
+ /**
+ * Get the remaining command count for a given command list.
+ *
+ * @param session_id - Index for which command list to clear (0 or 1).
+ * @return The remaining command count.
+ */
+ u32 GetRemainCommandCount(u32 session_id) const;
+
+ /**
+ * Clear the command buffers (does not clear the time taken or the remaining command count).
+ */
+ void ClearCommandBuffers();
+
+private:
+ /// Host signalling event
+ Common::Event host_event{};
+ /// AudioRenderer signalling event
+ Common::Event adsp_event{};
+ /// Host message queue
+
+ Common::ReaderWriterQueue<RenderMessage> host_messages{};
+ /// AudioRenderer message queue
+
+ Common::ReaderWriterQueue<RenderMessage> adsp_messages{};
+ /// Command buffers
+
+ std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
+ /// Tick the AudioRnederer was signalled
+ u64 signalled_tick{};
+};
+
+/**
+ * The AudioRenderer application running on the ADSP.
+ */
+class AudioRenderer {
+public:
+ explicit AudioRenderer(Core::System& system);
+ ~AudioRenderer();
+
+ /**
+ * Start the AudioRenderer.
+ *
+ * @param The mailbox to use for this session.
+ */
+ void Start(AudioRenderer_Mailbox* mailbox);
+
+ /**
+ * Stop the AudioRenderer.
+ */
+ void Stop();
+
+private:
+ /**
+ * Main AudioRenderer thread, responsible for processing the command lists.
+ */
+ void ThreadFunc();
+
+ /**
+ * Creates the streams which will receive the processed samples.
+ */
+ void CreateSinkStreams();
+
+ /// Core system
+ Core::System& system;
+ /// Main thread
+ std::thread thread{};
+ /// The current state
+ std::atomic<bool> running{};
+ /// The active mailbox
+ AudioRenderer_Mailbox* mailbox{};
+ /// The command lists to process
+ std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
+ /// The output sink the AudioRenderer will use
+ Sink::Sink& sink;
+ /// The streams which will receive the processed samples
+ std::array<Sink::SinkStream*, MaxRendererSessions> streams;
+};
+
+} // namespace AudioRenderer::ADSP
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/adsp/command_buffer.h b/src/audio_core/renderer/adsp/command_buffer.h
new file mode 100644
index 000000000..880b279d8
--- /dev/null
+++ b/src/audio_core/renderer/adsp/command_buffer.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer::ADSP {
+
+struct CommandBuffer {
+ CpuAddr buffer;
+ u64 size;
+ u64 time_limit;
+ u32 remaining_command_count;
+ bool reset_buffers;
+ u64 applet_resource_user_id;
+ u64 render_time_taken;
+};
+
+} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/command_list_processor.cpp b/src/audio_core/renderer/adsp/command_list_processor.cpp
new file mode 100644
index 000000000..e3bf2d7ec
--- /dev/null
+++ b/src/audio_core/renderer/adsp/command_list_processor.cpp
@@ -0,0 +1,109 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <string>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/command_list_header.h"
+#include "audio_core/renderer/command/commands.h"
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer::ADSP {
+
+void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size,
+ Sink::SinkStream* stream_) {
+ system = &system_;
+ memory = &system->Memory();
+ stream = stream_;
+ header = reinterpret_cast<CommandListHeader*>(buffer);
+ commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
+ commands_buffer_size = size;
+ command_count = header->command_count;
+ sample_count = header->sample_count;
+ target_sample_rate = header->sample_rate;
+ mix_buffers = header->samples_buffer;
+ buffer_count = header->buffer_count;
+ processed_command_count = 0;
+}
+
+void CommandListProcessor::SetProcessTimeMax(const u64 time) {
+ max_process_time = time;
+}
+
+u32 CommandListProcessor::GetRemainingCommandCount() const {
+ return command_count - processed_command_count;
+}
+
+void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
+ commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
+ commands_buffer_size = size;
+}
+
+Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
+ return stream;
+}
+
+u64 CommandListProcessor::Process(u32 session_id) {
+ const auto start_time_{system->CoreTiming().GetClockTicks()};
+ const auto command_base{CpuAddr(commands)};
+
+ if (processed_command_count > 0) {
+ current_processing_time += start_time_ - end_time;
+ } else {
+ start_time = start_time_;
+ current_processing_time = 0;
+ }
+
+ std::string dump{fmt::format("\nSession {}\n", session_id)};
+
+ for (u32 index = 0; index < command_count; index++) {
+ auto& command{*reinterpret_cast<ICommand*>(commands)};
+
+ if (command.magic != 0xCAFEBABE) {
+ LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}",
+ command.magic);
+ return system->CoreTiming().GetClockTicks() - start_time_;
+ }
+
+ auto current_offset{CpuAddr(commands) - command_base};
+
+ if (current_offset + command.size > commands_buffer_size) {
+ LOG_ERROR(Service_Audio,
+ "Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}",
+ commands_buffer_size,
+ CpuAddr(commands) + command.size - sizeof(CommandListHeader));
+ return system->CoreTiming().GetClockTicks() - start_time_;
+ }
+
+ if (Settings::values.dump_audio_commands) {
+ command.Dump(*this, dump);
+ }
+
+ if (!command.Verify(*this)) {
+ break;
+ }
+
+ if (command.enabled) {
+ command.Process(*this);
+ } else {
+ dump += fmt::format("\tDisabled!\n");
+ }
+
+ processed_command_count++;
+ commands += command.size;
+ }
+
+ if (Settings::values.dump_audio_commands && dump != last_dump) {
+ LOG_WARNING(Service_Audio, "{}", dump);
+ last_dump = dump;
+ }
+
+ end_time = system->CoreTiming().GetClockTicks();
+ return end_time - start_time_;
+}
+
+} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h
new file mode 100644
index 000000000..3f99173e3
--- /dev/null
+++ b/src/audio_core/renderer/adsp/command_list_processor.h
@@ -0,0 +1,118 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace Core {
+namespace Memory {
+class Memory;
+}
+class System;
+} // namespace Core
+
+namespace AudioCore {
+namespace Sink {
+class SinkStream;
+}
+
+namespace AudioRenderer {
+struct CommandListHeader;
+
+namespace ADSP {
+
+/**
+ * A processor for command lists given to the AudioRenderer.
+ */
+class CommandListProcessor {
+public:
+ /**
+ * Initialize the processor.
+ *
+ * @param system_ - The core system.
+ * @param buffer - The command buffer to process.
+ * @param size - The size of the buffer.
+ * @param stream_ - The stream to be used for sending the samples.
+ */
+ void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
+
+ /**
+ * Set the maximum processing time for this command list.
+ *
+ * @param time - The maximum process time.
+ */
+ void SetProcessTimeMax(u64 time);
+
+ /**
+ * Get the remaining command count for this list.
+ *
+ * @return The remaining command count.
+ */
+ u32 GetRemainingCommandCount() const;
+
+ /**
+ * Set the command buffer.
+ *
+ * @param buffer - The buffer to use.
+ * @param size - The size of the buffer.
+ */
+ void SetBuffer(CpuAddr buffer, u64 size);
+
+ /**
+ * Get the stream for this command list.
+ *
+ * @return The stream associated with this command list.
+ */
+ Sink::SinkStream* GetOutputSinkStream() const;
+
+ /**
+ * Process the command list.
+ *
+ * @param index - Index of the current command list.
+ * @return The time taken to process.
+ */
+ u64 Process(u32 session_id);
+
+ /// Core system
+ Core::System* system{};
+ /// Core memory
+ Core::Memory::Memory* memory{};
+ /// Stream for the processed samples
+ Sink::SinkStream* stream{};
+ /// Header info for this command list
+ CommandListHeader* header{};
+ /// The command buffer
+ u8* commands{};
+ /// The command buffer size
+ u64 commands_buffer_size{};
+ /// The maximum processing time alloted
+ u64 max_process_time{};
+ /// The number of commands in the buffer
+ u32 command_count{};
+ /// The target sample count for output
+ u32 sample_count{};
+ /// The target sample rate for output
+ u32 target_sample_rate{};
+ /// The mixing buffers used by the commands
+ std::span<s32> mix_buffers{};
+ /// The number of mix buffers
+ u32 buffer_count{};
+ /// The number of processed commands so far
+ u32 processed_command_count{};
+ /// The processing start time of this list
+ u64 start_time{};
+ /// The current processing time for this list
+ u64 current_processing_time{};
+ /// The end processing time for this list
+ u64 end_time{};
+ /// Last command list string generated, used for dumping audio commands to console
+ std::string last_dump{};
+};
+
+} // namespace ADSP
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp
new file mode 100644
index 000000000..d5886e55e
--- /dev/null
+++ b/src/audio_core/renderer/audio_device.cpp
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_core.h"
+#include "audio_core/common/feature_support.h"
+#include "audio_core/renderer/audio_device.h"
+#include "audio_core/sink/sink.h"
+#include "core/core.h"
+
+namespace AudioCore::AudioRenderer {
+
+AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
+ const u32 revision)
+ : output_sink{system.AudioCore().GetOutputSink()},
+ applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
+
+u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
+ const size_t max_count) {
+ std::span<AudioDeviceName> names{};
+
+ if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
+ names = usb_device_names;
+ } else {
+ names = device_names;
+ }
+
+ u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
+ for (u32 i = 0; i < out_count; i++) {
+ out_buffer.push_back(names[i]);
+ }
+ return out_count;
+}
+
+u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
+ const size_t max_count) {
+ u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
+
+ for (u32 i = 0; i < out_count; i++) {
+ out_buffer.push_back(output_device_names[i]);
+ }
+ return out_count;
+}
+
+void AudioDevice::SetDeviceVolumes(const f32 volume) {
+ output_sink.SetDeviceVolume(volume);
+}
+
+f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) {
+ return output_sink.GetDeviceVolume();
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h
new file mode 100644
index 000000000..1f449f261
--- /dev/null
+++ b/src/audio_core/renderer/audio_device.h
@@ -0,0 +1,88 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/audio_render_manager.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore {
+namespace Sink {
+class Sink;
+}
+
+namespace AudioRenderer {
+/**
+ * An interface to an output audio device available to the Switch.
+ */
+class AudioDevice {
+public:
+ struct AudioDeviceName {
+ std::array<char, 0x100> name;
+
+ AudioDeviceName(const char* name_) {
+ std::strncpy(name.data(), name_, name.size());
+ }
+ };
+
+ std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput",
+ "AudioBuiltInSpeakerOutput", "AudioTvOutput",
+ "AudioUsbDeviceOutput"};
+ std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput",
+ "AudioBuiltInSpeakerOutput", "AudioTvOutput"};
+ std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput",
+ "AudioExternalOutput"};
+
+ explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
+
+ /**
+ * Get a list of the available output devices.
+ *
+ * @param out_buffer - Output buffer to write the available device names.
+ * @param max_count - Maximum number of devices to write (count of out_buffer).
+ * @return Number of device names written.
+ */
+ u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
+
+ /**
+ * Get a list of the available output devices.
+ * Different to above somehow...
+ *
+ * @param out_buffer - Output buffer to write the available device names.
+ * @param max_count - Maximum number of devices to write (count of out_buffer).
+ * @return Number of device names written.
+ */
+ u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
+
+ /**
+ * Set the volume of all streams in the backend sink.
+ *
+ * @param volume - Volume to set.
+ */
+ void SetDeviceVolumes(f32 volume);
+
+ /**
+ * Get the volume for a given device name.
+ * Note: This is not fully implemented, we only assume 1 device for all streams.
+ *
+ * @param name - Name of the device to check. Unused.
+ * @return Volume of the device.
+ */
+ f32 GetDeviceVolume(std::string_view name);
+
+private:
+ /// Backend output sink for the device
+ Sink::Sink& output_sink;
+ /// Resource id this device is used for
+ const u64 applet_resource_user_id;
+ /// User audio renderer revision
+ const u32 user_revision;
+};
+
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/audio_renderer.cpp b/src/audio_core/renderer/audio_renderer.cpp
new file mode 100644
index 000000000..51aa17599
--- /dev/null
+++ b/src/audio_core/renderer/audio_renderer.cpp
@@ -0,0 +1,67 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/audio_render_manager.h"
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/renderer/audio_renderer.h"
+#include "audio_core/renderer/system_manager.h"
+#include "core/core.h"
+#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::AudioRenderer {
+
+Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event)
+ : core{system_}, manager{manager_}, system{system_, rendered_event} {}
+
+Result Renderer::Initialize(const AudioRendererParameterInternal& params,
+ Kernel::KTransferMemory* transfer_memory,
+ const u64 transfer_memory_size, const u32 process_handle,
+ const u64 applet_resource_user_id, const s32 session_id) {
+ if (params.execution_mode == ExecutionMode::Auto) {
+ if (!manager.AddSystem(system)) {
+ LOG_ERROR(Service_Audio,
+ "Both Audio Render sessions are in use, cannot create any more");
+ return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
+ }
+ system_registered = true;
+ }
+
+ initialized = true;
+ system.Initialize(params, transfer_memory, transfer_memory_size, process_handle,
+ applet_resource_user_id, session_id);
+
+ return ResultSuccess;
+}
+
+void Renderer::Finalize() {
+ auto session_id{system.GetSessionId()};
+
+ system.Finalize();
+
+ if (system_registered) {
+ manager.RemoveSystem(system);
+ system_registered = false;
+ }
+
+ manager.ReleaseSessionId(session_id);
+}
+
+System& Renderer::GetSystem() {
+ return system;
+}
+
+void Renderer::Start() {
+ system.Start();
+}
+
+void Renderer::Stop() {
+ system.Stop();
+}
+
+Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance,
+ std::span<u8> output) {
+ return system.Update(input, performance, output);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/audio_renderer.h b/src/audio_core/renderer/audio_renderer.h
new file mode 100644
index 000000000..90c6f9727
--- /dev/null
+++ b/src/audio_core/renderer/audio_renderer.h
@@ -0,0 +1,97 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/system.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KTransferMemory;
+}
+
+namespace AudioCore {
+struct AudioRendererParameterInternal;
+
+namespace AudioRenderer {
+class Manager;
+
+/**
+ * Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls.
+ */
+class Renderer {
+public:
+ explicit Renderer(Core::System& system, Manager& manager, Kernel::KEvent* rendered_event);
+
+ /**
+ * Initialize the renderer.
+ * Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes
+ * everything to a default state.
+ *
+ * @param params - Input parameters to initialize the system with.
+ * @param transfer_memory - Game-supplied memory for all workbuffers. Unused.
+ * @param transfer_memory_size - Size of the transfer memory. Unused.
+ * @param process_handle - Process handle, also used for memory. Unused.
+ * @param applet_resource_user_id - Applet id for this renderer. Unused.
+ * @param session_id - Session id of this renderer.
+ * @return Result code.
+ */
+ Result Initialize(const AudioRendererParameterInternal& params,
+ Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
+ u32 process_handle, u64 applet_resource_user_id, s32 session_id);
+
+ /**
+ * Finalize the renderer for shutdown.
+ */
+ void Finalize();
+
+ /**
+ * Get the renderer's system.
+ *
+ * @return Reference to the system.
+ */
+ System& GetSystem();
+
+ /**
+ * Start the renderer.
+ */
+ void Start();
+
+ /**
+ * Stop the renderer.
+ */
+ void Stop();
+
+ /**
+ * Update the audio renderer with new information.
+ * Called via RequestUpdate from the AudRen:U service.
+ *
+ * @param input - Input buffer containing the new data.
+ * @param performance - Optional performance buffer for outputting performance metrics.
+ * @param output - Output data from the renderer.
+ * @return Result code.
+ */
+ Result RequestUpdate(std::span<const u8> input, std::span<u8> performance,
+ std::span<u8> output);
+
+private:
+ /// System core
+ Core::System& core;
+ /// Manager this renderer is registered with
+ Manager& manager;
+ /// Is the audio renderer initialized?
+ bool initialized{};
+ /// Is the system registered with the manager?
+ bool system_registered{};
+ /// Audio render system, main driver of audio rendering
+ System system;
+};
+
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp
new file mode 100644
index 000000000..c5d4d66d8
--- /dev/null
+++ b/src/audio_core/renderer/behavior/behavior_info.cpp
@@ -0,0 +1,191 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/common/feature_support.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+
+namespace AudioCore::AudioRenderer {
+
+BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {}
+
+u32 BehaviorInfo::GetProcessRevisionNum() const {
+ return process_revision;
+}
+
+u32 BehaviorInfo::GetProcessRevision() const {
+ return Common::MakeMagic('R', 'E', 'V',
+ static_cast<char>(static_cast<u8>('0') + process_revision));
+}
+
+u32 BehaviorInfo::GetUserRevisionNum() const {
+ return user_revision;
+}
+
+u32 BehaviorInfo::GetUserRevision() const {
+ return Common::MakeMagic('R', 'E', 'V',
+ static_cast<char>(static_cast<u8>('0') + user_revision));
+}
+
+void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) {
+ user_revision = GetRevisionNum(user_revision_);
+}
+
+void BehaviorInfo::ClearError() {
+ error_count = 0;
+}
+
+void BehaviorInfo::AppendError(ErrorInfo& error) {
+ LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
+ error.error_code.raw, error.address);
+ if (error_count < MaxErrors) {
+ errors[error_count++] = error;
+ }
+}
+
+void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) {
+ auto error_count_{std::min(error_count, MaxErrors)};
+ std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo));
+
+ for (size_t i = 0; i < error_count_; i++) {
+ out_errors[i] = errors[i];
+ }
+ out_count = error_count_;
+}
+
+void BehaviorInfo::UpdateFlags(const Flags flags_) {
+ flags = flags_;
+}
+
+bool BehaviorInfo::IsMemoryForceMappingEnabled() const {
+ return flags.IsMemoryForceMappingEnabled;
+}
+
+bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
+ return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision);
+}
+
+bool BehaviorInfo::IsSplitterSupported() const {
+ return CheckFeatureSupported(SupportTags::Splitter, user_revision);
+}
+
+bool BehaviorInfo::IsSplitterBugFixed() const {
+ return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision);
+}
+
+bool BehaviorInfo::IsEffectInfoVersion2Supported() const {
+ return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision);
+}
+
+bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const {
+ return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize,
+ user_revision);
+}
+
+bool BehaviorInfo::IsWaveBufferVer2Supported() const {
+ return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision);
+}
+
+bool BehaviorInfo::IsLongSizePreDelaySupported() const {
+ return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision);
+}
+
+bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const {
+ return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2,
+ user_revision);
+}
+
+bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const {
+ return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3,
+ user_revision);
+}
+
+bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const {
+ return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
+ user_revision);
+}
+
+bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const {
+ return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
+ user_revision);
+}
+
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
+ return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent,
+ user_revision);
+}
+
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
+ return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent,
+ user_revision);
+}
+
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
+ return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent,
+ user_revision);
+}
+
+bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
+ return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision);
+}
+
+bool BehaviorInfo::IsElapsedFrameCountSupported() const {
+ return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision);
+}
+
+bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const {
+ return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision);
+}
+
+size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const {
+ if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) {
+ return 2;
+ }
+ return 1;
+}
+
+bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
+ return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision);
+}
+
+bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
+ return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint,
+ user_revision);
+}
+
+bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const {
+ return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision);
+}
+
+bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const {
+ return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision);
+}
+
+bool BehaviorInfo::UseBiquadFilterFloatProcessing() const {
+ return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision);
+}
+
+bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
+ return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision);
+}
+
+bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const {
+ return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision);
+}
+
+bool BehaviorInfo::IsDeviceApiVersion2Supported() const {
+ return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision);
+}
+
+bool BehaviorInfo::IsDelayChannelMappingChanged() const {
+ return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision);
+}
+
+bool BehaviorInfo::IsReverbChannelMappingChanged() const {
+ return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision);
+}
+
+bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const {
+ return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h
new file mode 100644
index 000000000..7333c297f
--- /dev/null
+++ b/src/audio_core/renderer/behavior/behavior_info.h
@@ -0,0 +1,376 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Holds host and user revisions, checks whether render features can be enabled, and reports errors.
+ */
+class BehaviorInfo {
+ static constexpr u32 MaxErrors = 10;
+
+public:
+ struct ErrorInfo {
+ /* 0x00 */ Result error_code{0};
+ /* 0x04 */ u32 unk_04;
+ /* 0x08 */ CpuAddr address;
+ };
+ static_assert(sizeof(ErrorInfo) == 0x10, "BehaviorInfo::ErrorInfo has the wrong size!");
+
+ struct Flags {
+ u64 IsMemoryForceMappingEnabled : 1;
+ };
+
+ struct InParameter {
+ /* 0x00 */ u32 revision;
+ /* 0x08 */ Flags flags;
+ };
+ static_assert(sizeof(InParameter) == 0x10, "BehaviorInfo::InParameter has the wrong size!");
+
+ struct OutStatus {
+ /* 0x00 */ std::array<ErrorInfo, MaxErrors> errors;
+ /* 0xA0 */ u32 error_count;
+ /* 0xA4 */ char unkA4[0xC];
+ };
+ static_assert(sizeof(OutStatus) == 0xB0, "BehaviorInfo::OutStatus has the wrong size!");
+
+ BehaviorInfo();
+
+ /**
+ * Get the host revision as a number.
+ *
+ * @return The host revision.
+ */
+ u32 GetProcessRevisionNum() const;
+
+ /**
+ * Get the host revision in chars, e.g REV8.
+ * Rev 10 and higher use the ascii characters above 9.
+ * E.g:
+ * Rev 10 = REV:
+ * Rev 11 = REV;
+ *
+ * @return The host revision.
+ */
+ u32 GetProcessRevision() const;
+
+ /**
+ * Get the user revision as a number.
+ *
+ * @return The user revision.
+ */
+ u32 GetUserRevisionNum() const;
+
+ /**
+ * Get the user revision in chars, e.g REV8.
+ * Rev 10 and higher use the ascii characters above 9. REV: REV; etc.
+ *
+ * @return The user revision.
+ */
+ u32 GetUserRevision() const;
+
+ /**
+ * Set the user revision.
+ *
+ * @param user_revision - The user's revision.
+ */
+ void SetUserLibRevision(u32 user_revision);
+
+ /**
+ * Clear the current error count.
+ */
+ void ClearError();
+
+ /**
+ * Append an error to the error list.
+ *
+ * @param error - The new error.
+ */
+ void AppendError(ErrorInfo& error);
+
+ /**
+ * Copy errors to the given output container.
+ *
+ * @param out_errors - Output container to receive the errors.
+ * @param out_count - The number of errors written.
+ */
+ void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count);
+
+ /**
+ * Update the behaviour flags.
+ *
+ * @param flags - New flags to use.
+ */
+ void UpdateFlags(Flags flags);
+
+ /**
+ * Check if memory pools can be forcibly mapped.
+ *
+ * @return True if enabled, otherwise false.
+ */
+ bool IsMemoryForceMappingEnabled() const;
+
+ /**
+ * Check if the ADPCM context bug is fixed.
+ * The ADPCM context was not being sent to the AudioRenderer, leading to incorrect scaling being
+ * used.
+ *
+ * @return True if fixed, otherwise false.
+ */
+ bool IsAdpcmLoopContextBugFixed() const;
+
+ /**
+ * Check if the splitter is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsSplitterSupported() const;
+
+ /**
+ * Check if the splitter bug is fixed.
+ * Update is given the wrong number of splitter destinations, leading to invalid data
+ * being processed.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsSplitterBugFixed() const;
+
+ /**
+ * Check if effects version 2 are supported.
+ * This gives support for returning effect states from the AudioRenderer, currently only used
+ * for Limiter statistics.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsEffectInfoVersion2Supported() const;
+
+ /**
+ * Check if a variadic command buffer is supported.
+ * As of Rev 5 with the added optional performance metric logging, the command
+ * buffer can be a variable size, so take that into account for calcualting its size.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsVariadicCommandBufferSizeSupported() const;
+
+ /**
+ * Check if wave buffers version 2 are supported.
+ * See WaveBufferVersion1 and WaveBufferVersion2.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsWaveBufferVer2Supported() const;
+
+ /**
+ * Check if long size pre delay is supported.
+ * This allows a longer initial delay time for the Reverb command.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsLongSizePreDelaySupported() const;
+
+ /**
+ * Check if the command time estimator version 2 is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsCommandProcessingTimeEstimatorVersion2Supported() const;
+
+ /**
+ * Check if the command time estimator version 3 is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsCommandProcessingTimeEstimatorVersion3Supported() const;
+
+ /**
+ * Check if the command time estimator version 4 is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsCommandProcessingTimeEstimatorVersion4Supported() const;
+
+ /**
+ * Check if the command time estimator version 5 is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsCommandProcessingTimeEstimatorVersion5Supported() const;
+
+ /**
+ * Check if the AudioRenderer can use up to 70% of the allocated processing timeslice.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
+
+ /**
+ * Check if the AudioRenderer can use up to 75% of the allocated processing timeslice.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
+
+ /**
+ * Check if the AudioRenderer can use up to 80% of the allocated processing timeslice.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
+
+ /**
+ * Check if voice flushing is supported
+ * This allowws low-priority voices to be dropped if the AudioRenderer is running behind.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsFlushVoiceWaveBuffersSupported() const;
+
+ /**
+ * Check if counting the number of elapsed frames is supported.
+ * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
+ * processed a command list.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsElapsedFrameCountSupported() const;
+
+ /**
+ * Check if performance metrics version 2 are supported.
+ * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
+ * (Unused?).
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsPerformanceMetricsDataFormatVersion2Supported() const;
+
+ /**
+ * Get the supported performance metrics version.
+ * Version 2 logs some extra fields in output, such as number of voices dropped,
+ * processing start time, if the AudioRenderer exceeded its time, etc.
+ *
+ * @return Version supported, either 1 or 2.
+ */
+ size_t GetPerformanceMetricsDataFormat() const;
+
+ /**
+ * Check if skipping voice pitch and sample rate conversion is supported.
+ * This speeds up the data source commands by skipping resampling if unwanted.
+ * See AudioCore::AudioRenderer::DecodeFromWaveBuffers
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsVoicePitchAndSrcSkippedSupported() const;
+
+ /**
+ * Check if resetting played sample count at loop points is supported.
+ * This resets the number of samples played in a voice state when a loop point is reached.
+ * See AudioCore::AudioRenderer::DecodeFromWaveBuffers
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
+
+ /**
+ * Check if the clear state bug for biquad filters is fixed.
+ * The biquad state was not marked as needing re-initialisation when the effect was updated, it
+ * was only initialized once with a new effect.
+ *
+ * @return True if fixed, otherwise false.
+ */
+ bool IsBiquadFilterEffectStateClearBugFixed() const;
+
+ /**
+ * Check if Q23 precision is supported for fixed point.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsVolumeMixParameterPrecisionQ23Supported() const;
+
+ /**
+ * Check if float processing for biuad filters is supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool UseBiquadFilterFloatProcessing() const;
+
+ /**
+ * Check if dirty-only mix updates are supported.
+ * This saves a lot of buffer size as mixes can be large and not change much.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsMixInParameterDirtyOnlyUpdateSupported() const;
+
+ /**
+ * Check if multi-tap biquad filters are supported.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool UseMultiTapBiquadFilterProcessing() const;
+
+ /**
+ * Check if device api version 2 is supported.
+ * In the SDK but not in any sysmodule? Not sure, left here for completeness anyway.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsDeviceApiVersion2Supported() const;
+
+ /**
+ * Check if new channel mappings are used for Delay commands.
+ * Older commands used:
+ * front left/front right/back left/back right/center/lfe
+ * Whereas everywhere else in the code uses:
+ * front left/front right/center/lfe/back left/back right
+ * This corrects that and makes everything standardised.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsDelayChannelMappingChanged() const;
+
+ /**
+ * Check if new channel mappings are used for Reverb commands.
+ * Older commands used:
+ * front left/front right/back left/back right/center/lfe
+ * Whereas everywhere else in the code uses:
+ * front left/front right/center/lfe/back left/back right
+ * This corrects that and makes everything standardised.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsReverbChannelMappingChanged() const;
+
+ /**
+ * Check if new channel mappings are used for I3dl2Reverb commands.
+ * Older commands used:
+ * front left/front right/back left/back right/center/lfe
+ * Whereas everywhere else in the code uses:
+ * front left/front right/center/lfe/back left/back right
+ * This corrects that and makes everything standardised.
+ *
+ * @return True if supported, otherwise false.
+ */
+ bool IsI3dl2ReverbChannelMappingChanged() const;
+
+ /// Host version
+ u32 process_revision;
+ /// User version
+ u32 user_revision{};
+ /// Behaviour flags
+ Flags flags{};
+ /// Errors generated and reported during Update
+ std::array<ErrorInfo, MaxErrors> errors{};
+ /// Error count
+ u32 error_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp
new file mode 100644
index 000000000..06a37e1a6
--- /dev/null
+++ b/src/audio_core/renderer/behavior/info_updater.cpp
@@ -0,0 +1,539 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/common/feature_support.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/behavior/info_updater.h"
+#include "audio_core/renderer/effect/effect_context.h"
+#include "audio_core/renderer/effect/effect_reset.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/mix/mix_context.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
+#include "audio_core/renderer/sink/device_sink_info.h"
+#include "audio_core/renderer/sink/sink_context.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+#include "audio_core/renderer/voice/voice_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_,
+ const u32 process_handle_, BehaviorInfo& behaviour_)
+ : input{input_.data() + sizeof(UpdateDataHeader)},
+ input_origin{input_}, output{output_.data() + sizeof(UpdateDataHeader)},
+ output_origin{output_}, in_header{reinterpret_cast<const UpdateDataHeader*>(
+ input_origin.data())},
+ out_header{reinterpret_cast<UpdateDataHeader*>(output_origin.data())},
+ expected_input_size{input_.size()}, expected_output_size{output_.size()},
+ process_handle{process_handle_}, behaviour{behaviour_} {
+ std::construct_at<UpdateDataHeader>(out_header, behaviour.GetProcessRevision());
+}
+
+Result InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
+ const auto voice_count{voice_context.GetCount()};
+ std::span<const VoiceChannelResource::InParameter> in_params{
+ reinterpret_cast<const VoiceChannelResource::InParameter*>(input), voice_count};
+
+ for (u32 i = 0; i < voice_count; i++) {
+ auto& resource{voice_context.GetChannelResource(i)};
+ resource.in_use = in_params[i].in_use;
+ if (in_params[i].in_use) {
+ resource.mix_volumes = in_params[i].mix_volumes;
+ }
+ }
+
+ const auto consumed_input_size{voice_count *
+ static_cast<u32>(sizeof(VoiceChannelResource::InParameter))};
+ if (consumed_input_size != in_header->voice_resources_size) {
+ LOG_ERROR(Service_Audio,
+ "Consumed an incorrect voice resource size, header size={}, consumed={}",
+ in_header->voice_resources_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += consumed_input_size;
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
+ std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
+ behaviour.IsMemoryForceMappingEnabled());
+ const auto voice_count{voice_context.GetCount()};
+ std::span<const VoiceInfo::InParameter> in_params{
+ reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count};
+ std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output),
+ voice_count};
+
+ for (u32 i = 0; i < voice_count; i++) {
+ auto& voice_info{voice_context.GetInfo(i)};
+ voice_info.in_use = false;
+ }
+
+ u32 new_voice_count{0};
+
+ for (u32 i = 0; i < voice_count; i++) {
+ const auto& in_param{in_params[i]};
+ std::array<VoiceState*, MaxChannels> voice_states{};
+
+ if (!in_param.in_use) {
+ continue;
+ }
+
+ auto& voice_info{voice_context.GetInfo(in_param.id)};
+
+ for (u32 channel = 0; channel < in_param.channel_count; channel++) {
+ voice_states[channel] = &voice_context.GetState(in_param.channel_resource_ids[channel]);
+ }
+
+ if (in_param.is_new) {
+ voice_info.Initialize();
+
+ for (u32 channel = 0; channel < in_param.channel_count; channel++) {
+ std::memset(voice_states[channel], 0, sizeof(VoiceState));
+ }
+ }
+
+ BehaviorInfo::ErrorInfo update_error{};
+ voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour);
+
+ if (!update_error.error_code.IsSuccess()) {
+ behaviour.AppendError(update_error);
+ }
+
+ std::array<std::array<BehaviorInfo::ErrorInfo, 2>, MaxWaveBuffers> wavebuffer_errors{};
+ voice_info.UpdateWaveBuffers(wavebuffer_errors, MaxWaveBuffers * 2, in_param, voice_states,
+ pool_mapper, behaviour);
+
+ for (auto& wavebuffer_error : wavebuffer_errors) {
+ for (auto& error : wavebuffer_error) {
+ if (error.error_code.IsError()) {
+ behaviour.AppendError(error);
+ }
+ }
+ }
+
+ voice_info.WriteOutStatus(out_params[i], in_param, voice_states);
+ new_voice_count += in_param.channel_count;
+ }
+
+ auto consumed_input_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::InParameter))};
+ auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))};
+ if (consumed_input_size != in_header->voices_size) {
+ LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}",
+ in_header->voices_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ out_header->voices_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ input += consumed_input_size;
+ output += consumed_output_size;
+
+ voice_context.SetActiveCount(new_voice_count);
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateEffects(EffectContext& effect_context, const bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ if (behaviour.IsEffectInfoVersion2Supported()) {
+ return UpdateEffectsVersion2(effect_context, renderer_active, memory_pools,
+ memory_pool_count);
+ } else {
+ return UpdateEffectsVersion1(effect_context, renderer_active, memory_pools,
+ memory_pool_count);
+ }
+}
+
+Result InfoUpdater::UpdateEffectsVersion1(EffectContext& effect_context, const bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
+ behaviour.IsMemoryForceMappingEnabled());
+
+ const auto effect_count{effect_context.GetCount()};
+
+ std::span<const EffectInfoBase::InParameterVersion1> in_params{
+ reinterpret_cast<const EffectInfoBase::InParameterVersion1*>(input), effect_count};
+ std::span<EffectInfoBase::OutStatusVersion1> out_params{
+ reinterpret_cast<EffectInfoBase::OutStatusVersion1*>(output), effect_count};
+
+ for (u32 i = 0; i < effect_count; i++) {
+ auto effect_info{&effect_context.GetInfo(i)};
+ if (effect_info->GetType() != in_params[i].type) {
+ effect_info->ForceUnmapBuffers(pool_mapper);
+ ResetEffect(effect_info, in_params[i].type);
+ }
+
+ BehaviorInfo::ErrorInfo error_info{};
+ effect_info->Update(error_info, in_params[i], pool_mapper);
+ if (error_info.error_code.IsError()) {
+ behaviour.AppendError(error_info);
+ }
+
+ effect_info->StoreStatus(out_params[i], renderer_active);
+ }
+
+ auto consumed_input_size{effect_count *
+ static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion1))};
+ auto consumed_output_size{effect_count *
+ static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion1))};
+ if (consumed_input_size != in_header->effects_size) {
+ LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}",
+ in_header->effects_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ out_header->effects_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ input += consumed_input_size;
+ output += consumed_output_size;
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateEffectsVersion2(EffectContext& effect_context, const bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
+ behaviour.IsMemoryForceMappingEnabled());
+
+ const auto effect_count{effect_context.GetCount()};
+
+ std::span<const EffectInfoBase::InParameterVersion2> in_params{
+ reinterpret_cast<const EffectInfoBase::InParameterVersion2*>(input), effect_count};
+ std::span<EffectInfoBase::OutStatusVersion2> out_params{
+ reinterpret_cast<EffectInfoBase::OutStatusVersion2*>(output), effect_count};
+
+ for (u32 i = 0; i < effect_count; i++) {
+ auto effect_info{&effect_context.GetInfo(i)};
+ if (effect_info->GetType() != in_params[i].type) {
+ effect_info->ForceUnmapBuffers(pool_mapper);
+ ResetEffect(effect_info, in_params[i].type);
+ }
+
+ BehaviorInfo::ErrorInfo error_info{};
+ effect_info->Update(error_info, in_params[i], pool_mapper);
+
+ if (error_info.error_code.IsError()) {
+ behaviour.AppendError(error_info);
+ }
+
+ effect_info->StoreStatus(out_params[i], renderer_active);
+
+ if (in_params[i].is_new) {
+ effect_info->InitializeResultState(effect_context.GetDspSharedResultState(i));
+ effect_info->InitializeResultState(effect_context.GetResultState(i));
+ }
+ effect_info->UpdateResultState(out_params[i].result_state,
+ effect_context.GetResultState(i));
+ }
+
+ auto consumed_input_size{effect_count *
+ static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion2))};
+ auto consumed_output_size{effect_count *
+ static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion2))};
+ if (consumed_input_size != in_header->effects_size) {
+ LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}",
+ in_header->effects_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ out_header->effects_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ input += consumed_input_size;
+ output += consumed_output_size;
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_count,
+ EffectContext& effect_context, SplitterContext& splitter_context) {
+ s32 mix_count{0};
+ u32 consumed_input_size{0};
+
+ if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
+ auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)};
+ mix_count = in_dirty_params->count;
+ input += sizeof(MixInfo::InDirtyParameter);
+ consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) +
+ mix_count * sizeof(MixInfo::InParameter));
+ } else {
+ mix_count = mix_context.GetCount();
+ consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
+ }
+
+ if (mix_buffer_count == 0) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ std::span<const MixInfo::InParameter> in_params{
+ reinterpret_cast<const MixInfo::InParameter*>(input), static_cast<size_t>(mix_count)};
+
+ u32 total_buffer_count{0};
+ for (s32 i = 0; i < mix_count; i++) {
+ const auto& params{in_params[i]};
+
+ if (params.in_use) {
+ total_buffer_count += params.buffer_count;
+ if (params.dest_mix_id > static_cast<s32>(mix_context.GetCount()) &&
+ params.dest_mix_id != UnusedMixId && params.mix_id != FinalMixId) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+ }
+ }
+
+ if (total_buffer_count > mix_buffer_count) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ bool mix_dirty{false};
+ for (s32 i = 0; i < mix_count; i++) {
+ const auto& params{in_params[i]};
+
+ s32 mix_id{i};
+ if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
+ mix_id = params.mix_id;
+ }
+
+ auto mix_info{mix_context.GetInfo(mix_id)};
+ if (mix_info->in_use != params.in_use) {
+ mix_info->in_use = params.in_use;
+ if (!params.in_use) {
+ mix_info->ClearEffectProcessingOrder();
+ }
+ mix_dirty = true;
+ }
+
+ if (params.in_use) {
+ mix_dirty |= mix_info->Update(mix_context.GetEdgeMatrix(), params, effect_context,
+ splitter_context, behaviour);
+ }
+ }
+
+ if (mix_dirty) {
+ if (behaviour.IsSplitterSupported() && splitter_context.UsingSplitter()) {
+ if (!mix_context.TSortInfo(splitter_context)) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+ } else {
+ mix_context.SortInfo();
+ }
+ }
+
+ if (consumed_input_size != in_header->mix_size) {
+ LOG_ERROR(Service_Audio, "Consumed an incorrect mixes size, header size={}, consumed={}",
+ in_header->mix_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += mix_count * sizeof(MixInfo::InParameter);
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
+ behaviour.IsMemoryForceMappingEnabled());
+
+ std::span<const SinkInfoBase::InParameter> in_params{
+ reinterpret_cast<const SinkInfoBase::InParameter*>(input), memory_pool_count};
+ std::span<SinkInfoBase::OutStatus> out_params{
+ reinterpret_cast<SinkInfoBase::OutStatus*>(output), memory_pool_count};
+
+ const auto sink_count{sink_context.GetCount()};
+
+ for (u32 i = 0; i < sink_count; i++) {
+ const auto& params{in_params[i]};
+ auto sink_info{sink_context.GetInfo(i)};
+
+ if (sink_info->GetType() != params.type) {
+ sink_info->CleanUp();
+ switch (params.type) {
+ case SinkInfoBase::Type::Invalid:
+ std::construct_at<SinkInfoBase>(reinterpret_cast<SinkInfoBase*>(sink_info));
+ break;
+ case SinkInfoBase::Type::DeviceSink:
+ std::construct_at<DeviceSinkInfo>(reinterpret_cast<DeviceSinkInfo*>(sink_info));
+ break;
+ case SinkInfoBase::Type::CircularBufferSink:
+ std::construct_at<CircularBufferSinkInfo>(
+ reinterpret_cast<CircularBufferSinkInfo*>(sink_info));
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(params.type));
+ break;
+ }
+ }
+
+ BehaviorInfo::ErrorInfo error_info{};
+ sink_info->Update(error_info, out_params[i], params, pool_mapper);
+
+ if (error_info.error_code.IsError()) {
+ behaviour.AppendError(error_info);
+ }
+ }
+
+ const auto consumed_input_size{sink_count *
+ static_cast<u32>(sizeof(SinkInfoBase::InParameter))};
+ const auto consumed_output_size{sink_count * static_cast<u32>(sizeof(SinkInfoBase::OutStatus))};
+ if (consumed_input_size != in_header->sinks_size) {
+ LOG_ERROR(Service_Audio, "Consumed an incorrect sinks size, header size={}, consumed={}",
+ in_header->sinks_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += consumed_input_size;
+ output += consumed_output_size;
+ out_header->sinks_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools,
+ const u32 memory_pool_count) {
+ PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
+ behaviour.IsMemoryForceMappingEnabled());
+ std::span<const MemoryPoolInfo::InParameter> in_params{
+ reinterpret_cast<const MemoryPoolInfo::InParameter*>(input), memory_pool_count};
+ std::span<MemoryPoolInfo::OutStatus> out_params{
+ reinterpret_cast<MemoryPoolInfo::OutStatus*>(output), memory_pool_count};
+
+ for (size_t i = 0; i < memory_pool_count; i++) {
+ auto state{pool_mapper.Update(memory_pools[i], in_params[i], out_params[i])};
+ if (state != MemoryPoolInfo::ResultState::Success &&
+ state != MemoryPoolInfo::ResultState::BadParam &&
+ state != MemoryPoolInfo::ResultState::MapFailed &&
+ state != MemoryPoolInfo::ResultState::InUse) {
+ LOG_WARNING(Service_Audio, "Invalid ResultState from updating memory pools");
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+ }
+
+ const auto consumed_input_size{memory_pool_count *
+ static_cast<u32>(sizeof(MemoryPoolInfo::InParameter))};
+ const auto consumed_output_size{memory_pool_count *
+ static_cast<u32>(sizeof(MemoryPoolInfo::OutStatus))};
+ if (consumed_input_size != in_header->memory_pool_size) {
+ LOG_ERROR(Service_Audio,
+ "Consumed an incorrect memory pool size, header size={}, consumed={}",
+ in_header->memory_pool_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += consumed_input_size;
+ output += consumed_output_size;
+ out_header->memory_pool_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdatePerformanceBuffer(std::span<u8> performance_output,
+ const u64 performance_output_size,
+ PerformanceManager* performance_manager) {
+ auto in_params{reinterpret_cast<const PerformanceManager::InParameter*>(input)};
+ auto out_params{reinterpret_cast<PerformanceManager::OutStatus*>(output)};
+
+ if (performance_manager != nullptr) {
+ out_params->history_size =
+ performance_manager->CopyHistories(performance_output.data(), performance_output_size);
+ performance_manager->SetDetailTarget(in_params->target_node_id);
+ } else {
+ out_params->history_size = 0;
+ }
+
+ const auto consumed_input_size{static_cast<u32>(sizeof(PerformanceManager::InParameter))};
+ const auto consumed_output_size{static_cast<u32>(sizeof(PerformanceManager::OutStatus))};
+ if (consumed_input_size != in_header->performance_buffer_size) {
+ LOG_ERROR(Service_Audio,
+ "Consumed an incorrect performance size, header size={}, consumed={}",
+ in_header->performance_buffer_size, consumed_input_size);
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += consumed_input_size;
+ output += consumed_output_size;
+ out_header->performance_buffer_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) {
+ const auto in_params{reinterpret_cast<const BehaviorInfo::InParameter*>(input)};
+
+ if (!CheckValidRevision(in_params->revision)) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ if (in_params->revision != behaviour_.GetUserRevision()) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ behaviour_.ClearError();
+ behaviour_.UpdateFlags(in_params->flags);
+
+ if (in_header->behaviour_size != sizeof(BehaviorInfo::InParameter)) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += sizeof(BehaviorInfo::InParameter);
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) {
+ auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)};
+ behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count);
+
+ const auto consumed_output_size{static_cast<u32>(sizeof(BehaviorInfo::OutStatus))};
+
+ output += consumed_output_size;
+ out_header->behaviour_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
+ u32 consumed_size{0};
+ if (!splitter_context.Update(input, consumed_size)) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+
+ input += consumed_size;
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::UpdateRendererInfo(const u64 elapsed_frames) {
+ struct RenderInfo {
+ /* 0x00 */ u64 frames_elapsed;
+ /* 0x08 */ char unk08[0x8];
+ };
+ static_assert(sizeof(RenderInfo) == 0x10, "RenderInfo has the wrong size!");
+
+ auto out_params{reinterpret_cast<RenderInfo*>(output)};
+ out_params->frames_elapsed = elapsed_frames;
+
+ const auto consumed_output_size{static_cast<u32>(sizeof(RenderInfo))};
+
+ output += consumed_output_size;
+ out_header->render_info_size = consumed_output_size;
+ out_header->size += consumed_output_size;
+
+ return ResultSuccess;
+}
+
+Result InfoUpdater::CheckConsumedSize() {
+ if (CpuAddr(input) - CpuAddr(input_origin.data()) != expected_input_size) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ } else if (CpuAddr(output) - CpuAddr(output_origin.data()) != expected_output_size) {
+ return Service::Audio::ERR_INVALID_UPDATE_DATA;
+ }
+ return ResultSuccess;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h
new file mode 100644
index 000000000..f0b445d9c
--- /dev/null
+++ b/src/audio_core/renderer/behavior/info_updater.h
@@ -0,0 +1,205 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "common/common_types.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::AudioRenderer {
+class BehaviorInfo;
+class VoiceContext;
+class MixContext;
+class SinkContext;
+class SplitterContext;
+class EffectContext;
+class MemoryPoolInfo;
+class PerformanceManager;
+
+class InfoUpdater {
+ struct UpdateDataHeader {
+ explicit UpdateDataHeader(u32 revision_) : revision{revision_} {}
+
+ /* 0x00 */ u32 revision;
+ /* 0x04 */ u32 behaviour_size{};
+ /* 0x08 */ u32 memory_pool_size{};
+ /* 0x0C */ u32 voices_size{};
+ /* 0x10 */ u32 voice_resources_size{};
+ /* 0x14 */ u32 effects_size{};
+ /* 0x18 */ u32 mix_size{};
+ /* 0x1C */ u32 sinks_size{};
+ /* 0x20 */ u32 performance_buffer_size{};
+ /* 0x24 */ char unk24[4];
+ /* 0x28 */ u32 render_info_size{};
+ /* 0x2C */ char unk2C[0x10];
+ /* 0x3C */ u32 size{sizeof(UpdateDataHeader)};
+ };
+ static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has the wrong size!");
+
+public:
+ explicit InfoUpdater(std::span<const u8> input, std::span<u8> output, u32 process_handle,
+ BehaviorInfo& behaviour);
+
+ /**
+ * Update the voice channel resources.
+ *
+ * @param voice_context - Voice context to update.
+ * @return Result code.
+ */
+ Result UpdateVoiceChannelResources(VoiceContext& voice_context);
+
+ /**
+ * Update voices.
+ *
+ * @param voice_context - Voice context to update.
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateVoices(VoiceContext& voice_context, std::span<MemoryPoolInfo> memory_pools,
+ u32 memory_pool_count);
+
+ /**
+ * Update effects.
+ *
+ * @param effect_context - Effect context to update.
+ * @param renderer_active - Whether the AudioRenderer is active.
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateEffects(EffectContext& effect_context, bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
+
+ /**
+ * Update mixes.
+ *
+ * @param mix_context - Mix context to update.
+ * @param mix_buffer_count - Number of mix buffers.
+ * @param effect_context - Effect context to update effort order.
+ * @param splitter_context - Splitter context for the mixes.
+ * @return Result code.
+ */
+ Result UpdateMixes(MixContext& mix_context, u32 mix_buffer_count, EffectContext& effect_context,
+ SplitterContext& splitter_context);
+
+ /**
+ * Update sinks.
+ *
+ * @param sink_context - Sink context to update.
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
+ u32 memory_pool_count);
+
+ /**
+ * Update memory pools.
+ *
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
+
+ /**
+ * Update the performance buffer.
+ *
+ * @param output - Output buffer for performance metrics.
+ * @param output_size - Output buffer size.
+ * @param performance_manager - Performance manager..
+ * @return Result code.
+ */
+ Result UpdatePerformanceBuffer(std::span<u8> output, u64 output_size,
+ PerformanceManager* performance_manager);
+
+ /**
+ * Update behaviour.
+ *
+ * @param behaviour - Behaviour to update.
+ * @return Result code.
+ */
+ Result UpdateBehaviorInfo(BehaviorInfo& behaviour);
+
+ /**
+ * Update errors.
+ *
+ * @param behaviour - Behaviour to update.
+ * @return Result code.
+ */
+ Result UpdateErrorInfo(BehaviorInfo& behaviour);
+
+ /**
+ * Update splitter.
+ *
+ * @param splitter_context - Splitter context to update.
+ * @return Result code.
+ */
+ Result UpdateSplitterInfo(SplitterContext& splitter_context);
+
+ /**
+ * Update renderer info.
+ *
+ * @param elapsed_frames - Number of elapsed frames.
+ * @return Result code.
+ */
+ Result UpdateRendererInfo(u64 elapsed_frames);
+
+ /**
+ * Check that the input.output sizes match their expected values.
+ *
+ * @return Result code.
+ */
+ Result CheckConsumedSize();
+
+private:
+ /**
+ * Update effects version 1.
+ *
+ * @param effect_context - Effect context to update.
+ * @param renderer_active - Is the AudioRenderer active?
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateEffectsVersion1(EffectContext& effect_context, bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
+
+ /**
+ * Update effects version 2.
+ *
+ * @param effect_context - Effect context to update.
+ * @param renderer_active - Is the AudioRenderer active?
+ * @param memory_pools - Memory pools to use for these voices.
+ * @param memory_pool_count - Number of memory pools.
+ * @return Result code.
+ */
+ Result UpdateEffectsVersion2(EffectContext& effect_context, bool renderer_active,
+ std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
+
+ /// Input buffer
+ u8 const* input;
+ /// Input buffer start
+ std::span<const u8> input_origin;
+ /// Output buffer start
+ u8* output;
+ /// Output buffer start
+ std::span<u8> output_origin;
+ /// Input header
+ const UpdateDataHeader* in_header;
+ /// Output header
+ UpdateDataHeader* out_header;
+ /// Expected input size, see CheckConsumedSize
+ u64 expected_input_size;
+ /// Expected output size, see CheckConsumedSize
+ u64 expected_output_size;
+ /// Unused
+ u32 process_handle;
+ /// Behaviour
+ BehaviorInfo& behaviour;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp
new file mode 100644
index 000000000..40074cf14
--- /dev/null
+++ b/src/audio_core/renderer/command/command_buffer.cpp
@@ -0,0 +1,714 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_list_header.h"
+#include "audio_core/renderer/command/command_processing_time_estimator.h"
+#include "audio_core/renderer/effect/biquad_filter.h"
+#include "audio_core/renderer/effect/delay.h"
+#include "audio_core/renderer/effect/reverb.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/mix/mix_info.h"
+#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
+#include "audio_core/renderer/sink/device_sink_info.h"
+#include "audio_core/renderer/sink/sink_info_base.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "audio_core/renderer/voice/voice_state.h"
+
+namespace AudioCore::AudioRenderer {
+
+template <typename T, CommandId Id>
+T& CommandBuffer::GenerateStart(const s32 node_id) {
+ if (size + sizeof(T) >= command_list.size_bytes()) {
+ LOG_ERROR(
+ Service_Audio,
+ "Attempting to write commands beyond the end of allocated command buffer memory!");
+ UNREACHABLE();
+ }
+
+ auto& cmd{*std::construct_at<T>(reinterpret_cast<T*>(&command_list[size]))};
+
+ cmd.magic = CommandMagic;
+ cmd.enabled = true;
+ cmd.type = Id;
+ cmd.size = sizeof(T);
+ cmd.node_id = node_id;
+
+ return cmd;
+}
+
+template <typename T>
+void CommandBuffer::GenerateEnd(T& cmd) {
+ cmd.estimated_process_time = time_estimator->Estimate(cmd);
+ estimated_process_time += cmd.estimated_process_time;
+ size += sizeof(T);
+ count++;
+}
+
+void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id,
+ const MemoryPoolInfo& memory_pool_,
+ VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<PcmInt16DataSourceVersion1Command, CommandId::DataSourcePcmInt16Version1>(
+ node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+ cmd.channel_index = channel;
+ cmd.channel_count = voice_info.channel_count;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+
+ GenerateEnd<PcmInt16DataSourceVersion1Command>(cmd);
+}
+
+void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<PcmInt16DataSourceVersion2Command, CommandId::DataSourcePcmInt16Version2>(
+ node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+ cmd.channel_index = channel;
+ cmd.channel_count = voice_info.channel_count;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+
+ GenerateEnd<PcmInt16DataSourceVersion2Command>(cmd);
+}
+
+void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id,
+ const MemoryPoolInfo& memory_pool_,
+ VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<PcmFloatDataSourceVersion1Command, CommandId::DataSourcePcmFloatVersion1>(
+ node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+ cmd.channel_index = channel;
+ cmd.channel_count = voice_info.channel_count;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+
+ GenerateEnd<PcmFloatDataSourceVersion1Command>(cmd);
+}
+
+void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<PcmFloatDataSourceVersion2Command, CommandId::DataSourcePcmFloatVersion2>(
+ node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+ cmd.channel_index = channel;
+ cmd.channel_count = voice_info.channel_count;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+
+ GenerateEnd<PcmFloatDataSourceVersion2Command>(cmd);
+}
+
+void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id,
+ const MemoryPoolInfo& memory_pool_,
+ VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<AdpcmDataSourceVersion1Command, CommandId::DataSourceAdpcmVersion1>(node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+ cmd.data_address = voice_info.data_address.GetReference(true);
+ cmd.data_size = voice_info.data_address.GetSize();
+
+ GenerateEnd<AdpcmDataSourceVersion1Command>(cmd);
+}
+
+void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{
+ GenerateStart<AdpcmDataSourceVersion2Command, CommandId::DataSourceAdpcmVersion2>(node_id)};
+
+ cmd.src_quality = voice_info.src_quality;
+ cmd.output_index = buffer_count + channel;
+ cmd.flags = voice_info.flags & 3;
+ cmd.sample_rate = voice_info.sample_rate;
+ cmd.pitch = voice_info.pitch;
+ cmd.channel_index = channel;
+ cmd.channel_count = voice_info.channel_count;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
+ }
+
+ cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
+ cmd.data_address = voice_info.data_address.GetReference(true);
+ cmd.data_size = voice_info.data_address.GetSize();
+
+ GenerateEnd<AdpcmDataSourceVersion2Command>(cmd);
+}
+
+void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset,
+ const s16 input_index, const f32 volume,
+ const u8 precision) {
+ auto& cmd{GenerateStart<VolumeCommand, CommandId::Volume>(node_id)};
+
+ cmd.precision = precision;
+ cmd.input_index = buffer_offset + input_index;
+ cmd.output_index = buffer_offset + input_index;
+ cmd.volume = volume;
+
+ GenerateEnd<VolumeCommand>(cmd);
+}
+
+void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info,
+ const s16 buffer_count, const u8 precision) {
+ auto& cmd{GenerateStart<VolumeRampCommand, CommandId::VolumeRamp>(node_id)};
+
+ cmd.input_index = buffer_count;
+ cmd.output_index = buffer_count;
+ cmd.prev_volume = voice_info.prev_volume;
+ cmd.volume = voice_info.volume;
+ cmd.precision = precision;
+
+ GenerateEnd<VolumeRampCommand>(cmd);
+}
+
+void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel,
+ const u32 biquad_index,
+ const bool use_float_processing) {
+ auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
+
+ cmd.input = buffer_count + channel;
+ cmd.output = buffer_count + channel;
+
+ cmd.biquad = voice_info.biquads[biquad_index];
+
+ cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()),
+ MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
+
+ cmd.needs_init = !voice_info.biquad_initialized[biquad_index];
+ cmd.use_float_processing = use_float_processing;
+
+ GenerateEnd<BiquadFilterCommand>(cmd);
+}
+
+void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 buffer_offset, const s8 channel,
+ const bool needs_init,
+ const bool use_float_processing) {
+ auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
+
+ const auto& parameter{
+ *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ const auto state{
+ reinterpret_cast<VoiceState::BiquadFilterState*>(effect_info.GetStateBuffer())};
+
+ cmd.input = buffer_offset + parameter.inputs[channel];
+ cmd.output = buffer_offset + parameter.outputs[channel];
+
+ cmd.biquad.b = parameter.b;
+ cmd.biquad.a = parameter.a;
+
+ cmd.state = memory_pool->Translate(CpuAddr(state),
+ MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
+
+ cmd.needs_init = needs_init;
+ cmd.use_float_processing = use_float_processing;
+
+ GenerateEnd<BiquadFilterCommand>(cmd);
+}
+
+void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index,
+ const s16 output_index, const s16 buffer_offset,
+ const f32 volume, const u8 precision) {
+ auto& cmd{GenerateStart<MixCommand, CommandId::Mix>(node_id)};
+
+ cmd.input_index = input_index;
+ cmd.output_index = output_index;
+ cmd.volume = volume;
+ cmd.precision = precision;
+
+ GenerateEnd<MixCommand>(cmd);
+}
+
+void CommandBuffer::GenerateMixRampCommand(const s32 node_id,
+ [[maybe_unused]] const s16 buffer_count,
+ const s16 input_index, const s16 output_index,
+ const f32 volume, const f32 prev_volume,
+ const CpuAddr prev_samples, const u8 precision) {
+ if (volume == 0.0f && prev_volume == 0.0f) {
+ return;
+ }
+
+ auto& cmd{GenerateStart<MixRampCommand, CommandId::MixRamp>(node_id)};
+
+ cmd.input_index = input_index;
+ cmd.output_index = output_index;
+ cmd.prev_volume = prev_volume;
+ cmd.volume = volume;
+ cmd.previous_sample = prev_samples;
+ cmd.precision = precision;
+
+ GenerateEnd<MixRampCommand>(cmd);
+}
+
+void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count,
+ const s16 input_index, s16 output_index,
+ std::span<const f32> volumes,
+ std::span<const f32> prev_volumes,
+ const CpuAddr prev_samples, const u8 precision) {
+ auto& cmd{GenerateStart<MixRampGroupedCommand, CommandId::MixRampGrouped>(node_id)};
+
+ cmd.buffer_count = buffer_count;
+
+ for (s32 i = 0; i < buffer_count; i++) {
+ cmd.inputs[i] = input_index;
+ cmd.outputs[i] = output_index++;
+ cmd.prev_volumes[i] = prev_volumes[i];
+ cmd.volumes[i] = volumes[i];
+ }
+
+ cmd.previous_samples = prev_samples;
+ cmd.precision = precision;
+
+ GenerateEnd<MixRampGroupedCommand>(cmd);
+}
+
+void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state,
+ std::span<const s32> buffer, const s16 buffer_count,
+ s16 buffer_offset, const bool was_playing) {
+ auto& cmd{GenerateStart<DepopPrepareCommand, CommandId::DepopPrepare>(node_id)};
+
+ cmd.enabled = was_playing;
+
+ for (u32 i = 0; i < MaxMixBuffers; i++) {
+ cmd.inputs[i] = buffer_offset++;
+ }
+
+ cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()),
+ MaxMixBuffers * sizeof(s32));
+ cmd.buffer_count = buffer_count;
+ cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer_count * sizeof(s32));
+
+ GenerateEnd<DepopPrepareCommand>(cmd);
+}
+
+void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info,
+ std::span<const s32> depop_buffer) {
+ auto& cmd{GenerateStart<DepopForMixBuffersCommand, CommandId::DepopForMixBuffers>(node_id)};
+
+ cmd.input = mix_info.buffer_offset;
+ cmd.count = mix_info.buffer_count;
+ cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f;
+ cmd.depop_buffer =
+ memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32));
+
+ GenerateEnd<DepopForMixBuffersCommand>(cmd);
+}
+
+void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 buffer_offset) {
+ auto& cmd{GenerateStart<DelayCommand, CommandId::Delay>(node_id)};
+
+ const auto& parameter{
+ *reinterpret_cast<DelayInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ const auto state{effect_info.GetStateBuffer()};
+
+ if (IsChannelCountValid(parameter.channel_count)) {
+ const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))};
+ if (state_buffer) {
+ for (s16 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+
+ if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) {
+ UseOldChannelMapping(cmd.inputs, cmd.outputs);
+ }
+
+ cmd.parameter = parameter;
+ cmd.effect_enabled = effect_info.IsEnabled();
+ cmd.state = state_buffer;
+ cmd.workbuffer = effect_info.GetWorkbuffer(-1);
+ }
+ }
+
+ GenerateEnd<DelayCommand>(cmd);
+}
+
+void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset,
+ UpsamplerInfo& upsampler_info, const u32 input_count,
+ std::span<const s8> inputs, const s16 buffer_count,
+ const u32 sample_count_, const u32 sample_rate_) {
+ auto& cmd{GenerateStart<UpsampleCommand, CommandId::Upsample>(node_id)};
+
+ cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos,
+ upsampler_info.sample_count * sizeof(s32));
+ cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels);
+ cmd.buffer_count = buffer_count;
+ cmd.unk_20 = 0;
+ cmd.source_sample_count = sample_count_;
+ cmd.source_sample_rate = sample_rate_;
+
+ upsampler_info.input_count = input_count;
+ for (u32 i = 0; i < input_count; i++) {
+ upsampler_info.inputs[i] = buffer_offset + inputs[i];
+ }
+
+ cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo));
+
+ GenerateEnd<UpsampleCommand>(cmd);
+}
+
+void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span<const s8> inputs,
+ const s16 buffer_offset,
+ std::span<const f32> downmix_coeff) {
+ auto& cmd{GenerateStart<DownMix6chTo2chCommand, CommandId::DownMix6chTo2ch>(node_id)};
+
+ for (u32 i = 0; i < MaxChannels; i++) {
+ cmd.inputs[i] = buffer_offset + inputs[i];
+ cmd.outputs[i] = buffer_offset + inputs[i];
+ }
+
+ for (u32 i = 0; i < 4; i++) {
+ cmd.down_mix_coeff[i] = downmix_coeff[i];
+ }
+
+ GenerateEnd<DownMix6chTo2chCommand>(cmd);
+}
+
+void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 input_index, const s16 output_index,
+ const s16 buffer_offset, const u32 update_count,
+ const u32 count_max, const u32 write_offset) {
+ auto& cmd{GenerateStart<AuxCommand, CommandId::Aux>(node_id)};
+
+ if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
+ cmd.input = buffer_offset + input_index;
+ cmd.output = buffer_offset + output_index;
+ cmd.send_buffer_info = effect_info.GetSendBufferInfo();
+ cmd.send_buffer = effect_info.GetSendBuffer();
+ cmd.return_buffer_info = effect_info.GetReturnBufferInfo();
+ cmd.return_buffer = effect_info.GetReturnBuffer();
+ cmd.count_max = count_max;
+ cmd.write_offset = write_offset;
+ cmd.update_count = update_count;
+ cmd.effect_enabled = effect_info.IsEnabled();
+ }
+
+ GenerateEnd<AuxCommand>(cmd);
+}
+
+void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset,
+ SinkInfoBase& sink_info, const u32 session_id,
+ std::span<s32> samples_buffer) {
+ auto& cmd{GenerateStart<DeviceSinkCommand, CommandId::DeviceSink>(node_id)};
+ const auto& parameter{
+ *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
+ auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
+
+ cmd.session_id = session_id;
+
+ if (state.upsampler_info != nullptr) {
+ const auto size_{state.upsampler_info->sample_count * parameter.input_count};
+ const auto size_bytes{size_ * sizeof(s32)};
+ const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)};
+ cmd.sample_buffer = {reinterpret_cast<s32*>(addr),
+ parameter.input_count * state.upsampler_info->sample_count};
+ } else {
+ cmd.sample_buffer = samples_buffer;
+ }
+
+ cmd.input_count = parameter.input_count;
+ for (u32 i = 0; i < parameter.input_count; i++) {
+ cmd.inputs[i] = buffer_offset + parameter.inputs[i];
+ }
+
+ GenerateEnd<DeviceSinkCommand>(cmd);
+}
+
+void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info,
+ const s16 buffer_offset) {
+ auto& cmd{GenerateStart<CircularBufferSinkCommand, CommandId::CircularBufferSink>(node_id)};
+ const auto& parameter{*reinterpret_cast<CircularBufferSinkInfo::CircularBufferInParameter*>(
+ sink_info.GetParameter())};
+ auto state{
+ *reinterpret_cast<CircularBufferSinkInfo::CircularBufferState*>(sink_info.GetState())};
+
+ cmd.input_count = parameter.input_count;
+ for (u32 i = 0; i < parameter.input_count; i++) {
+ cmd.inputs[i] = buffer_offset + parameter.inputs[i];
+ }
+
+ cmd.address = state.address_info.GetReference(true);
+ cmd.size = parameter.size;
+ cmd.pos = state.current_pos;
+
+ GenerateEnd<CircularBufferSinkCommand>(cmd);
+}
+
+void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 buffer_offset,
+ const bool long_size_pre_delay_supported) {
+ auto& cmd{GenerateStart<ReverbCommand, CommandId::Reverb>(node_id)};
+
+ const auto& parameter{
+ *reinterpret_cast<ReverbInfo::ParameterVersion2*>(effect_info.GetParameter())};
+ const auto state{effect_info.GetStateBuffer()};
+
+ if (IsChannelCountValid(parameter.channel_count)) {
+ const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))};
+ if (state_buffer) {
+ for (s16 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+
+ if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) {
+ UseOldChannelMapping(cmd.inputs, cmd.outputs);
+ }
+
+ cmd.parameter = parameter;
+ cmd.effect_enabled = effect_info.IsEnabled();
+ cmd.state = state_buffer;
+ cmd.workbuffer = effect_info.GetWorkbuffer(-1);
+ cmd.long_size_pre_delay_supported = long_size_pre_delay_supported;
+ }
+ }
+
+ GenerateEnd<ReverbCommand>(cmd);
+}
+
+void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 buffer_offset) {
+ auto& cmd{GenerateStart<I3dl2ReverbCommand, CommandId::I3dl2Reverb>(node_id)};
+
+ const auto& parameter{
+ *reinterpret_cast<I3dl2ReverbInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ const auto state{effect_info.GetStateBuffer()};
+
+ if (IsChannelCountValid(parameter.channel_count)) {
+ const auto state_buffer{
+ memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))};
+ if (state_buffer) {
+ for (s16 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+
+ if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) {
+ UseOldChannelMapping(cmd.inputs, cmd.outputs);
+ }
+
+ cmd.parameter = parameter;
+ cmd.effect_enabled = effect_info.IsEnabled();
+ cmd.state = state_buffer;
+ cmd.workbuffer = effect_info.GetWorkbuffer(-1);
+ }
+ }
+
+ GenerateEnd<I3dl2ReverbCommand>(cmd);
+}
+
+void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state,
+ const PerformanceEntryAddresses& entry_addresses) {
+ auto& cmd{GenerateStart<PerformanceCommand, CommandId::Performance>(node_id)};
+
+ cmd.state = state;
+ cmd.entry_address = entry_addresses;
+
+ GenerateEnd<PerformanceCommand>(cmd);
+}
+
+void CommandBuffer::GenerateClearMixCommand(const s32 node_id) {
+ auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)};
+ GenerateEnd<ClearMixBufferCommand>(cmd);
+}
+
+void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 buffer_offset, const s8 channel) {
+ auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)};
+
+ const auto& parameter{
+ *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ cmd.input_index = buffer_offset + parameter.inputs[channel];
+ cmd.output_index = buffer_offset + parameter.outputs[channel];
+
+ GenerateEnd<CopyMixBufferCommand>(cmd);
+}
+
+void CommandBuffer::GenerateLightLimiterCommand(
+ const s32 node_id, const s16 buffer_offset,
+ const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state,
+ const bool enabled, const CpuAddr workbuffer) {
+ auto& cmd{GenerateStart<LightLimiterVersion1Command, CommandId::LightLimiterVersion1>(node_id)};
+
+ if (IsChannelCountValid(parameter.channel_count)) {
+ const auto state_buffer{
+ memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
+ if (state_buffer) {
+ for (s8 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+
+ std::memcpy(&cmd.parameter, &parameter, sizeof(LightLimiterInfo::ParameterVersion1));
+ cmd.effect_enabled = enabled;
+ cmd.state = state_buffer;
+ cmd.workbuffer = workbuffer;
+ }
+ }
+
+ GenerateEnd<LightLimiterVersion1Command>(cmd);
+}
+
+void CommandBuffer::GenerateLightLimiterCommand(
+ const s32 node_id, const s16 buffer_offset,
+ const LightLimiterInfo::ParameterVersion2& parameter,
+ const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state,
+ const bool enabled, const CpuAddr workbuffer) {
+ auto& cmd{GenerateStart<LightLimiterVersion2Command, CommandId::LightLimiterVersion2>(node_id)};
+ if (IsChannelCountValid(parameter.channel_count)) {
+ const auto state_buffer{
+ memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
+ if (state_buffer) {
+ for (s8 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+
+ cmd.parameter = parameter;
+ cmd.effect_enabled = enabled;
+ cmd.state = state_buffer;
+ if (cmd.parameter.statistics_enabled) {
+ cmd.result_state = memory_pool->Translate(
+ CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal));
+ } else {
+ cmd.result_state = 0;
+ }
+ cmd.workbuffer = workbuffer;
+ }
+ }
+
+ GenerateEnd<LightLimiterVersion2Command>(cmd);
+}
+
+void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel) {
+ auto& cmd{GenerateStart<MultiTapBiquadFilterCommand, CommandId::MultiTapBiquadFilter>(node_id)};
+
+ cmd.input = buffer_count + channel;
+ cmd.output = buffer_count + channel;
+ cmd.biquads = voice_info.biquads;
+
+ cmd.states[0] =
+ memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()),
+ MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
+ cmd.states[1] =
+ memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()),
+ MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
+
+ cmd.needs_init[0] = !voice_info.biquad_initialized[0];
+ cmd.needs_init[1] = !voice_info.biquad_initialized[1];
+ cmd.filter_tap_count = MaxBiquadFilters;
+
+ GenerateEnd<MultiTapBiquadFilterCommand>(cmd);
+}
+
+void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info,
+ const s16 input_index, const s16 output_index,
+ const s16 buffer_offset, const u32 update_count,
+ const u32 count_max, const u32 write_offset) {
+ auto& cmd{GenerateStart<CaptureCommand, CommandId::Capture>(node_id)};
+
+ if (effect_info.GetSendBuffer()) {
+ cmd.input = buffer_offset + input_index;
+ cmd.output = buffer_offset + output_index;
+ cmd.send_buffer_info = effect_info.GetSendBufferInfo();
+ cmd.send_buffer = effect_info.GetSendBuffer();
+ cmd.count_max = count_max;
+ cmd.write_offset = write_offset;
+ cmd.update_count = update_count;
+ cmd.effect_enabled = effect_info.IsEnabled();
+ }
+
+ GenerateEnd<CaptureCommand>(cmd);
+}
+
+void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info,
+ s32 node_id) {
+ auto& cmd{GenerateStart<CompressorCommand, CommandId::Compressor>(node_id)};
+
+ auto& parameter{
+ *reinterpret_cast<CompressorInfo::ParameterVersion2*>(effect_info.GetParameter())};
+ auto state{reinterpret_cast<CompressorInfo::State*>(effect_info.GetStateBuffer())};
+
+ if (IsChannelCountValid(parameter.channel_count)) {
+ auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))};
+ if (state_buffer) {
+ for (u16 channel = 0; channel < parameter.channel_count; channel++) {
+ cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
+ cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
+ }
+ cmd.parameter = parameter;
+ cmd.workbuffer = state_buffer;
+ cmd.enabled = effect_info.IsEnabled();
+ }
+ }
+
+ GenerateEnd<CompressorCommand>(cmd);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h
new file mode 100644
index 000000000..496b0e50a
--- /dev/null
+++ b/src/audio_core/renderer/command/command_buffer.h
@@ -0,0 +1,466 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/command/commands.h"
+#include "audio_core/renderer/effect/light_limiter.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+struct UpsamplerInfo;
+struct VoiceState;
+class EffectInfoBase;
+class ICommandProcessingTimeEstimator;
+class MixInfo;
+class MemoryPoolInfo;
+class SinkInfoBase;
+class VoiceInfo;
+
+/**
+ * Utility functions to generate and add commands into the current command list.
+ */
+class CommandBuffer {
+public:
+ /**
+ * Generate a PCM s16 version 1 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
+ VoiceInfo& voice_info, const VoiceState& voice_state,
+ s16 buffer_count, s8 channel);
+
+ /**
+ * Generate a PCM s16 version 2 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state, s16 buffer_count,
+ s8 channel);
+
+ /**
+ * Generate a PCM f32 version 1 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
+ VoiceInfo& voice_info, const VoiceState& voice_state,
+ s16 buffer_count, s8 channel);
+
+ /**
+ * Generate a PCM f32 version 2 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state, s16 buffer_count,
+ s8 channel);
+
+ /**
+ * Generate an ADPCM version 1 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
+ VoiceInfo& voice_info, const VoiceState& voice_state,
+ s16 buffer_count, s8 channel);
+
+ /**
+ * Generate an ADPCM version 2 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command is generated from.
+ * @param voice_state - The voice state the DSP will use for this command.
+ * @param buffer_count - Number of mix buffers in use,
+ * data will be read into this index + channel.
+ * @param channel - Channel index for this command.
+ */
+ void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state, s16 buffer_count, s8 channel);
+
+ /**
+ * Generate a volume command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_offset - Base mix buffer index to generate this command at.
+ * @param input_index - Channel index and mix buffer offset for this command.
+ * @param volume - Mix volume added to the input samples.
+ * @param precision - Number of decimal bits for fixed point operations.
+ */
+ void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume,
+ u8 precision);
+
+ /**
+ * Generate a volume ramp command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command takes its volumes from.
+ * @param buffer_count - Number of active mix buffers, command will generate at this index.
+ * @param precision - Number of decimal bits for fixed point operations.
+ */
+ void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count,
+ u8 precision);
+
+ /**
+ * Generate a biquad filter command from a voice, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command takes biquad parameters from.
+ * @param voice_state - Used by the AudioRenderer to track previous samples.
+ * @param buffer_count - Number of active mix buffers,
+ * command will generate at this index + channel.
+ * @param channel - Channel index for this filter to work on.
+ * @param biquad_index - Which biquad filter to use for this command (0-1).
+ * @param use_float_processing - Should int or float processing be used?
+ */
+ void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state, s16 buffer_count, s8 channel,
+ u32 biquad_index, bool use_float_processing);
+
+ /**
+ * Generate a biquad filter effect command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - The effect info this command takes biquad parameters from.
+ * @param buffer_offset - Mix buffer offset this command will use,
+ * command will generate at this index + channel.
+ * @param channel - Channel index for this filter to work on.
+ * @param needs_init - True if the biquad state needs initialisation.
+ * @param use_float_processing - Should int or float processing be used?
+ */
+ void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
+ s8 channel, bool needs_init, bool use_float_processing);
+
+ /**
+ * Generate a mix command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param input_index - Input mix buffer index for this command.
+ * Added to the buffer offset.
+ * @param output_index - Output mix buffer index for this command.
+ * Added to the buffer offset.
+ * @param buffer_offset - Mix buffer offset this command will use.
+ * @param volume - Volume to be applied to the input.
+ * @param precision - Number of decimal bits for fixed point operations.
+ */
+ void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset,
+ f32 volume, u8 precision);
+
+ /**
+ * Generate a mix ramp command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_count - Number of active mix buffers.
+ * @param input_index - Input mix buffer index for this command.
+ * Added to buffer_count.
+ * @param output_index - Output mix buffer index for this command.
+ * Added to buffer_count.
+ * @param volume - Current mix volume used for calculating the ramp.
+ * @param prev_volume - Previous mix volume, used for calculating the ramp,
+ * also applied to the input.
+ * @param precision - Number of decimal bits for fixed point operations.
+ */
+ void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
+ f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision);
+
+ /**
+ * Generate a mix ramp grouped command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_count - Number of active mix buffers.
+ * @param input_index - Input mix buffer index for this command.
+ * Added to buffer_count.
+ * @param output_index - Output mix buffer index for this command.
+ * Added to buffer_count.
+ * @param volumes - Current mix volumes used for calculating the ramp.
+ * @param prev_volumes - Previous mix volumes, used for calculating the ramp,
+ * also applied to the input.
+ * @param precision - Number of decimal bits for fixed point operations.
+ */
+ void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
+ s16 output_index, std::span<const f32> volumes,
+ std::span<const f32> prev_volumes, CpuAddr prev_samples,
+ u8 precision);
+
+ /**
+ * Generate a depop prepare command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_state - State to track the previous depop samples for each mix buffer.
+ * @param buffer - State to track the current depop samples for each mix buffer.
+ * @param buffer_count - Number of active mix buffers.
+ * @param buffer_offset - Base mix buffer index to generate the channel depops at.
+ * @param was_playing - Command only needs to work if the voice was previously playing.
+ */
+ void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state,
+ std::span<const s32> buffer, s16 buffer_count,
+ s16 buffer_offset, bool was_playing);
+
+ /**
+ * Generate a depop command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param mix_info - Mix info to get the buffer count and base offsets from.
+ * @param depop_buffer - Buffer of current depop sample values to be added to the input
+ * channels.
+ */
+ void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info,
+ std::span<const s32> depop_buffer);
+
+ /**
+ * Generate a delay command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - Delay effect info to generate this command from.
+ * @param buffer_offset - Base mix buffer offset to apply the apply the delay.
+ */
+ void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
+
+ /**
+ * Generate an upsample command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_offset - Base mix buffer offset to upsample.
+ * @param upsampler_info - Upsampler info to control the upsampling.
+ * @param input_count - Number of input channels to upsample.
+ * @param inputs - Input mix buffer indexes.
+ * @param buffer_count - Number of active mix buffers.
+ * @param sample_count - Source sample count of the input.
+ * @param sample_rate - Source sample rate of the input.
+ */
+ void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info,
+ u32 input_count, std::span<const s8> inputs, s16 buffer_count,
+ u32 sample_count, u32 sample_rate);
+
+ /**
+ * Generate a downmix 6 -> 2 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param inputs - Input mix buffer indexes.
+ * @param buffer_offset - Base mix buffer offset of the channels to downmix.
+ * @param downmix_coeff - Downmixing coefficients.
+ */
+ void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset,
+ std::span<const f32> downmix_coeff);
+
+ /**
+ * Generate an aux buffer command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - Aux effect info to generate this command from.
+ * @param input_index - Input mix buffer index for this command.
+ * Added to buffer_offset.
+ * @param output_index - Output mix buffer index for this command.
+ * Added to buffer_offset.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param update_count - Number of samples to write back to the game as updated, can be 0.
+ * @param count_max - Maximum number of samples to read or write.
+ * @param write_offset - Current read or write offset within the buffer.
+ */
+ void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
+ s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max,
+ u32 write_offset);
+
+ /**
+ * Generate a device sink command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param sink_info - The sink_info to generate this command from.
+ * @session_id - System session id this command is generated from.
+ * @samples_buffer - The buffer to be sent to the sink if upsampling is not used.
+ */
+ void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
+ u32 session_id, std::span<s32> samples_buffer);
+
+ /**
+ * Generate a circular buffer sink command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param sink_info - The sink_info to generate this command from.
+ * @param buffer_offset - Base mix buffer offset to use.
+ */
+ void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset);
+
+ /**
+ * Generate a reverb command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - Reverb effect info to generate this command from.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb
+ * begins?
+ */
+ void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
+ bool long_size_pre_delay_supported);
+
+ /**
+ * Generate an I3DL2 reverb command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - I3DL2Reverb effect info to generate this command from.
+ * @param buffer_offset - Base mix buffer offset to use.
+ */
+ void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
+
+ /**
+ * Generate a performance command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param state - State of the performance.
+ * @param entry_addresses - The addresses to be filled in by the AudioRenderer.
+ */
+ void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
+ const PerformanceEntryAddresses& entry_addresses);
+
+ /**
+ * Generate a clear mix command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ */
+ void GenerateClearMixCommand(s32 node_id);
+
+ /**
+ * Generate a copy mix command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - BiquadFilter effect info to generate this command from.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param channel - Index to the effect's parameters input indexes for this command.
+ */
+ void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
+ s8 channel);
+
+ /**
+ * Generate a light limiter version 1 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param parameter - Effect parameter to generate from.
+ * @param state - State used by the AudioRenderer between commands.
+ * @param enabled - Is this command enabled?
+ * @param workbuffer - Game-supplied memory for the state.
+ */
+ void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
+ const LightLimiterInfo::ParameterVersion1& parameter,
+ const LightLimiterInfo::State& state, bool enabled,
+ CpuAddr workbuffer);
+
+ /**
+ * Generate a light limiter version 2 command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param parameter - Effect parameter to generate from.
+ * @param statistics - Statistics reported by the AudioRenderer on the limiter's state.
+ * @param state - State used by the AudioRenderer between commands.
+ * @param enabled - Is this command enabled?
+ * @param workbuffer - Game-supplied memory for the state.
+ */
+ void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
+ const LightLimiterInfo::ParameterVersion2& parameter,
+ const LightLimiterInfo::StatisticsInternal& statistics,
+ const LightLimiterInfo::State& state, bool enabled,
+ CpuAddr workbuffer);
+
+ /**
+ * Generate a multitap biquad filter command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param voice_info - The voice info this command takes biquad parameters from.
+ * @param voice_state - Used by the AudioRenderer to track previous samples.
+ * @param buffer_count - Number of active mix buffers,
+ * command will generate at this index + channel.
+ * @param channel - Channel index for this filter to work on.
+ */
+ void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
+ const VoiceState& voice_state, s16 buffer_count,
+ s8 channel);
+
+ /**
+ * Generate a capture command, adding it to the command list.
+ *
+ * @param node_id - Node id of the voice this command is generated for.
+ * @param effect_info - Capture effect info to generate this command from.
+ * @param input_index - Input mix buffer index for this command.
+ * Added to buffer_offset.
+ * @param output_index - Output mix buffer index for this command (unused).
+ * Added to buffer_offset.
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param update_count - Number of samples to write back to the game as updated, can be 0.
+ * @param count_max - Maximum number of samples to read or write.
+ * @param write_offset - Current read or write offset within the buffer.
+ */
+ void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
+ s16 output_index, s16 buffer_offset, u32 update_count,
+ u32 count_max, u32 write_offset);
+
+ /**
+ * Generate a compressor command, adding it to the command list.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info - Capture effect info to generate this command from.
+ * @param node_id - Node id of the voice this command is generated for.
+ */
+ void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
+
+ /// Command list buffer generated commands will be added to
+ std::span<u8> command_list{};
+ /// Input sample count, unused
+ u32 sample_count{};
+ /// Input sample rate, unused
+ u32 sample_rate{};
+ /// Current size of the command buffer
+ u64 size{};
+ /// Current number of commands added
+ u32 count{};
+ /// Current estimated processing time for all commands
+ u32 estimated_process_time{};
+ /// Used for mapping buffers for the AudioRenderer
+ MemoryPoolInfo* memory_pool{};
+ /// Used for estimating command process times
+ ICommandProcessingTimeEstimator* time_estimator{};
+ /// Used to check which rendering features are currently enabled
+ BehaviorInfo* behavior{};
+
+private:
+ template <typename T, CommandId Id>
+ T& GenerateStart(const s32 node_id);
+ template <typename T>
+ void GenerateEnd(T& cmd);
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp
new file mode 100644
index 000000000..2ea50d128
--- /dev/null
+++ b/src/audio_core/renderer/command/command_generator.cpp
@@ -0,0 +1,796 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_generator.h"
+#include "audio_core/renderer/command/command_list_header.h"
+#include "audio_core/renderer/effect/aux_.h"
+#include "audio_core/renderer/effect/biquad_filter.h"
+#include "audio_core/renderer/effect/buffer_mixer.h"
+#include "audio_core/renderer/effect/capture.h"
+#include "audio_core/renderer/effect/effect_context.h"
+#include "audio_core/renderer/effect/light_limiter.h"
+#include "audio_core/renderer/mix/mix_context.h"
+#include "audio_core/renderer/performance/detail_aspect.h"
+#include "audio_core/renderer/performance/entry_aspect.h"
+#include "audio_core/renderer/sink/device_sink_info.h"
+#include "audio_core/renderer/sink/sink_context.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+#include "audio_core/renderer/voice/voice_context.h"
+#include "common/alignment.h"
+
+namespace AudioCore::AudioRenderer {
+
+CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_,
+ const CommandListHeader& command_list_header_,
+ const AudioRendererSystemContext& render_context_,
+ VoiceContext& voice_context_, MixContext& mix_context_,
+ EffectContext& effect_context_, SinkContext& sink_context_,
+ SplitterContext& splitter_context_,
+ PerformanceManager* performance_manager_)
+ : command_buffer{command_buffer_}, command_header{command_list_header_},
+ render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_},
+ effect_context{effect_context_}, sink_context{sink_context_},
+ splitter_context{splitter_context_}, performance_manager{performance_manager_} {
+ command_buffer.GenerateClearMixCommand(InvalidNodeId);
+}
+
+void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info,
+ const VoiceState& voice_state, const s8 channel) {
+ if (voice_info.mix_id == UnusedMixId) {
+ if (voice_info.splitter_id != UnusedSplitterId) {
+ auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)};
+ u32 dest_id{0};
+ while (destination != nullptr) {
+ if (destination->IsConfigured()) {
+ auto mix_id{destination->GetMixId()};
+ if (mix_id < mix_context.GetCount()) {
+ auto mix_info{mix_context.GetInfo(mix_id)};
+ command_buffer.GenerateDepopPrepareCommand(
+ voice_info.node_id, voice_state, render_context.depop_buffer,
+ mix_info->buffer_count, mix_info->buffer_offset,
+ voice_info.was_playing);
+ }
+ }
+ dest_id++;
+ destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id);
+ }
+ }
+ } else {
+ auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
+ command_buffer.GenerateDepopPrepareCommand(
+ voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count,
+ mix_info->buffer_offset, voice_info.was_playing);
+ }
+
+ if (voice_info.was_playing) {
+ return;
+ }
+
+ if (render_context.behavior->IsWaveBufferVer2Supported()) {
+ switch (voice_info.sample_format) {
+ case SampleFormat::PcmInt16:
+ command_buffer.GeneratePcmInt16Version2Command(
+ voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
+ channel);
+ break;
+ case SampleFormat::PcmFloat:
+ command_buffer.GeneratePcmFloatVersion2Command(
+ voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
+ channel);
+ break;
+ case SampleFormat::Adpcm:
+ command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
+ static_cast<u32>(voice_info.sample_format));
+ break;
+ }
+ } else {
+ switch (voice_info.sample_format) {
+ case SampleFormat::PcmInt16:
+ command_buffer.GeneratePcmInt16Version1Command(
+ voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ case SampleFormat::PcmFloat:
+ command_buffer.GeneratePcmFloatVersion1Command(
+ voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ case SampleFormat::Adpcm:
+ command_buffer.GenerateAdpcmVersion1Command(
+ voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
+ static_cast<u32>(voice_info.sample_format));
+ break;
+ }
+ }
+}
+
+void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
+ std::span<const f32> prev_mix_volumes,
+ const VoiceState& voice_state, s16 output_index,
+ const s16 buffer_count, const s16 input_index,
+ const s32 node_id) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ if (buffer_count > 8) {
+ const auto prev_samples{render_context.memory_pool_info->Translate(
+ CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))};
+ command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index,
+ output_index, mix_volumes, prev_mix_volumes,
+ prev_samples, precision);
+ } else {
+ for (s16 i = 0; i < buffer_count; i++, output_index++) {
+ const auto prev_samples{render_context.memory_pool_info->Translate(
+ CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))};
+
+ command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index,
+ mix_volumes[i], prev_mix_volumes[i], prev_samples,
+ precision);
+ }
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel,
+ const s32 node_id) {
+ const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled};
+ const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()};
+
+ if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() &&
+ use_float_processing) {
+ command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state,
+ buffer_count, channel);
+ } else {
+ for (u32 i = 0; i < MaxBiquadFilters; i++) {
+ if (voice_info.biquads[i].enabled) {
+ command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state,
+ buffer_count, channel, i,
+ use_float_processing);
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ for (s8 channel = 0; channel < voice_info.channel_count; channel++) {
+ const auto resource_id{voice_info.channel_resource_ids[channel]};
+ auto& voice_state{voice_context.GetDspSharedState(resource_id)};
+ auto& channel_resource{voice_context.GetChannelResource(resource_id)};
+
+ PerformanceDetailType detail_type{PerformanceDetailType::Invalid};
+ switch (voice_info.sample_format) {
+ case SampleFormat::PcmInt16:
+ detail_type = PerformanceDetailType::Unk1;
+ break;
+ case SampleFormat::PcmFloat:
+ detail_type = PerformanceDetailType::Unk10;
+ break;
+ default:
+ detail_type = PerformanceDetailType::Unk2;
+ break;
+ }
+
+ DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id,
+ detail_type);
+ GenerateDataSourceCommand(voice_info, voice_state, channel);
+
+ if (data_source_detail.initialized) {
+ command_buffer.GeneratePerformanceCommand(data_source_detail.node_id,
+ PerformanceState::Stop,
+ data_source_detail.performance_entry_address);
+ }
+
+ if (voice_info.was_playing) {
+ voice_info.prev_volume = 0.0f;
+ continue;
+ }
+
+ if (!voice_info.HasAnyConnection()) {
+ continue;
+ }
+
+ DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id,
+ PerformanceDetailType::Unk4);
+ GenerateBiquadFilterCommandForVoice(
+ voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id);
+
+ if (biquad_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ biquad_detail_aspect.node_id, PerformanceState::Stop,
+ biquad_detail_aspect.performance_entry_address);
+ }
+
+ DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice,
+ voice_info.node_id, PerformanceDetailType::Unk3);
+ command_buffer.GenerateVolumeRampCommand(
+ voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision);
+ if (volume_ramp_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ volume_ramp_detail_aspect.node_id, PerformanceState::Stop,
+ volume_ramp_detail_aspect.performance_entry_address);
+ }
+
+ voice_info.prev_volume = voice_info.volume;
+
+ if (voice_info.mix_id == UnusedMixId) {
+ if (voice_info.splitter_id != UnusedSplitterId) {
+ auto i{channel};
+ auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)};
+ while (destination != nullptr) {
+ if (destination->IsConfigured()) {
+ const auto mix_id{destination->GetMixId()};
+ if (mix_id < mix_context.GetCount() &&
+ static_cast<s32>(mix_id) != UnusedSplitterId) {
+ auto mix_info{mix_context.GetInfo(mix_id)};
+ GenerateVoiceMixCommand(
+ destination->GetMixVolume(), destination->GetMixVolumePrev(),
+ voice_state, mix_info->buffer_offset, mix_info->buffer_count,
+ render_context.mix_buffer_count + channel, voice_info.node_id);
+ destination->MarkAsNeedToUpdateInternalState();
+ }
+ }
+ i += voice_info.channel_count;
+ destination = splitter_context.GetDesintationData(voice_info.splitter_id, i);
+ }
+ }
+ } else {
+ DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice,
+ voice_info.node_id, PerformanceDetailType::Unk3);
+ auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
+ GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes,
+ voice_state, mix_info->buffer_offset, mix_info->buffer_count,
+ render_context.mix_buffer_count + channel, voice_info.node_id);
+ if (volume_mix_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ volume_mix_detail_aspect.node_id, PerformanceState::Stop,
+ volume_mix_detail_aspect.performance_entry_address);
+ }
+
+ channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
+ }
+ voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled;
+ voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled;
+ }
+}
+
+void CommandGenerator::GenerateVoiceCommands() {
+ const auto voice_count{voice_context.GetCount()};
+
+ for (u32 i = 0; i < voice_count; i++) {
+ auto sorted_info{voice_context.GetSortedInfo(i)};
+
+ if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) {
+ continue;
+ }
+
+ EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id);
+
+ GenerateVoiceCommand(*sorted_info);
+
+ if (voice_entry_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id,
+ PerformanceState::Stop,
+ voice_entry_aspect.performance_entry_address);
+ }
+ }
+
+ splitter_context.UpdateInternalState();
+}
+
+void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info, const s32 node_id) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ if (effect_info.IsEnabled()) {
+ const auto& parameter{
+ *reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ for (u32 i = 0; i < parameter.mix_count; i++) {
+ if (parameter.volumes[i] != 0.0f) {
+ command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i],
+ buffer_offset + parameter.outputs[i],
+ buffer_offset, parameter.volumes[i], precision);
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id) {
+ command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset);
+}
+
+void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id,
+ const bool long_size_pre_delay_supported) {
+ command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset,
+ long_size_pre_delay_supported);
+}
+
+void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info,
+ const s32 node_id) {
+ command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset);
+}
+
+void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id) {
+
+ if (effect_info.IsEnabled()) {
+ effect_info.GetWorkbuffer(0);
+ effect_info.GetWorkbuffer(1);
+ }
+
+ if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
+ const auto& parameter{
+ *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ auto channel_index{parameter.mix_buffer_count - 1};
+ u32 write_offset{0};
+ for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
+ auto new_update_count{command_header.sample_count + write_offset};
+ const auto update_count{channel_index > 0 ? 0 : new_update_count};
+ command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i],
+ parameter.outputs[i], buffer_offset, update_count,
+ parameter.count_max, write_offset);
+ write_offset = new_update_count;
+ }
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info,
+ const s32 node_id) {
+ const auto& parameter{
+ *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ if (effect_info.IsEnabled()) {
+ bool needs_init{false};
+
+ switch (parameter.state) {
+ case EffectInfoBase::ParameterState::Initialized:
+ needs_init = true;
+ break;
+ case EffectInfoBase::ParameterState::Updating:
+ case EffectInfoBase::ParameterState::Updated:
+ if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) {
+ needs_init = false;
+ } else {
+ needs_init = parameter.state == EffectInfoBase::ParameterState::Updating;
+ }
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}",
+ static_cast<u32>(parameter.state));
+ break;
+ }
+
+ for (s8 channel = 0; channel < parameter.channel_count; channel++) {
+ command_buffer.GenerateBiquadFilterCommand(
+ node_id, effect_info, buffer_offset, channel, needs_init,
+ render_context.behavior->UseBiquadFilterFloatProcessing());
+ }
+ } else {
+ for (s8 channel = 0; channel < parameter.channel_count; channel++) {
+ command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset,
+ channel);
+ }
+ }
+}
+
+void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info,
+ const s32 node_id,
+ const u32 effect_index) {
+
+ const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())};
+
+ if (render_context.behavior->IsEffectInfoVersion2Supported()) {
+ const auto& parameter{
+ *reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())};
+ const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(
+ &effect_context.GetDspSharedResultState(effect_index))};
+ command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state,
+ state, effect_info.IsEnabled(),
+ effect_info.GetWorkbuffer(-1));
+ } else {
+ const auto& parameter{
+ *reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state,
+ effect_info.IsEnabled(),
+ effect_info.GetWorkbuffer(-1));
+ }
+}
+
+void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id) {
+ if (effect_info.IsEnabled()) {
+ effect_info.GetWorkbuffer(0);
+ }
+
+ if (effect_info.GetSendBuffer()) {
+ const auto& parameter{
+ *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ auto channel_index{parameter.mix_buffer_count - 1};
+ u32 write_offset{0};
+ for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
+ auto new_update_count{command_header.sample_count + write_offset};
+ const auto update_count{channel_index > 0 ? 0 : new_update_count};
+ command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i],
+ parameter.outputs[i], buffer_offset, update_count,
+ parameter.count_max, write_offset);
+ write_offset = new_update_count;
+ }
+ }
+}
+
+void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info, const s32 node_id) {
+ command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id);
+}
+
+void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) {
+ const auto effect_count{effect_context.GetCount()};
+ for (u32 i = 0; i < effect_count; i++) {
+ const auto effect_index{mix_info.effect_order_buffer[i]};
+ if (effect_index == -1) {
+ break;
+ }
+
+ auto& effect_info = effect_context.GetInfo(effect_index);
+ if (effect_info.ShouldSkip()) {
+ continue;
+ }
+
+ const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix
+ : PerformanceEntryType::SubMix};
+
+ switch (effect_info.GetType()) {
+ case EffectInfoBase::Type::Mix: {
+ DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk5);
+ GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (mix_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ mix_detail_aspect.node_id, PerformanceState::Stop,
+ mix_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Aux: {
+ DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk7);
+ GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (aux_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ aux_detail_aspect.node_id, PerformanceState::Stop,
+ aux_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Delay: {
+ DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk6);
+ GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (delay_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ delay_detail_aspect.node_id, PerformanceState::Stop,
+ delay_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Reverb: {
+ DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk8);
+ GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
+ render_context.behavior->IsLongSizePreDelaySupported());
+ if (reverb_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ reverb_detail_aspect.node_id, PerformanceState::Stop,
+ reverb_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::I3dl2Reverb: {
+ DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk9);
+ GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (i3dl2_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ i3dl2_detail_aspect.node_id, PerformanceState::Stop,
+ i3dl2_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::BiquadFilter: {
+ DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk4);
+ GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info,
+ mix_info.node_id);
+ if (biquad_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ biquad_detail_aspect.node_id, PerformanceState::Stop,
+ biquad_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::LightLimiter: {
+ DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk11);
+ GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
+ effect_index);
+ if (light_limiter_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ light_limiter_detail_aspect.node_id, PerformanceState::Stop,
+ light_limiter_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Capture: {
+ DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk12);
+ GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (capture_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ capture_detail_aspect.node_id, PerformanceState::Stop,
+ capture_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Compressor: {
+ DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk13);
+ GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (capture_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ capture_detail_aspect.node_id, PerformanceState::Stop,
+ capture_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid effect type {}",
+ static_cast<u32>(effect_info.GetType()));
+ break;
+ }
+
+ effect_info.UpdateForCommandGeneration();
+ }
+}
+
+void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ if (!mix_info.HasAnyConnection()) {
+ return;
+ }
+
+ if (mix_info.dst_mix_id == UnusedMixId) {
+ if (mix_info.dst_splitter_id != UnusedSplitterId) {
+ s16 dest_id{0};
+ auto destination{
+ splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)};
+ while (destination != nullptr) {
+ if (destination->IsConfigured()) {
+ auto splitter_mix_id{destination->GetMixId()};
+ if (splitter_mix_id < mix_context.GetCount()) {
+ auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)};
+ const s16 input_index{static_cast<s16>(mix_info.buffer_offset +
+ (dest_id % mix_info.buffer_count))};
+ for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) {
+ auto volume{mix_info.volume * destination->GetMixVolume(i)};
+ if (volume != 0.0f) {
+ command_buffer.GenerateMixCommand(
+ mix_info.node_id, input_index,
+ splitter_mix_info->buffer_offset + i, mix_info.buffer_offset,
+ volume, precision);
+ }
+ }
+ }
+ }
+ dest_id++;
+ destination =
+ splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id);
+ }
+ }
+ } else {
+ auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)};
+ for (s16 i = 0; i < mix_info.buffer_count; i++) {
+ for (s16 j = 0; j < dest_mix_info->buffer_count; j++) {
+ auto volume{mix_info.volume * mix_info.mix_volumes[i][j]};
+ if (volume != 0.0f) {
+ command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i,
+ dest_mix_info->buffer_offset + j,
+ mix_info.buffer_offset, volume, precision);
+ }
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) {
+ command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info,
+ render_context.depop_buffer);
+ GenerateEffectCommand(mix_info);
+
+ DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id,
+ PerformanceDetailType::Unk5);
+
+ GenerateMixCommands(mix_info);
+
+ if (mix_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop,
+ mix_detail_aspect.performance_entry_address);
+ }
+}
+
+void CommandGenerator::GenerateSubMixCommands() {
+ const auto submix_count{mix_context.GetCount()};
+ for (s32 i = 0; i < submix_count; i++) {
+ auto sorted_info{mix_context.GetSortedInfo(i)};
+ if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) {
+ continue;
+ }
+
+ EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id);
+
+ GenerateSubMixCommand(*sorted_info);
+
+ if (submix_entry_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ submix_entry_aspect.node_id, PerformanceState::Stop,
+ submix_entry_aspect.performance_entry_address);
+ }
+ }
+}
+
+void CommandGenerator::GenerateFinalMixCommand() {
+ auto& final_mix_info{*mix_context.GetFinalMixInfo()};
+
+ command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info,
+ render_context.depop_buffer);
+ GenerateEffectCommand(final_mix_info);
+
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ for (s16 i = 0; i < final_mix_info.buffer_count; i++) {
+ DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id,
+ PerformanceDetailType::Unk3);
+ command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset,
+ i, final_mix_info.volume, precision);
+ if (volume_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop,
+ volume_aspect.performance_entry_address);
+ }
+ }
+}
+
+void CommandGenerator::GenerateFinalMixCommands() {
+ auto final_mix_info{mix_context.GetFinalMixInfo()};
+ EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id);
+ GenerateFinalMixCommand();
+ if (final_mix_entry.initialized) {
+ command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop,
+ final_mix_entry.performance_entry_address);
+ }
+}
+
+void CommandGenerator::GenerateSinkCommands() {
+ const auto sink_count{sink_context.GetCount()};
+
+ for (u32 i = 0; i < sink_count; i++) {
+ auto sink_info{sink_context.GetInfo(i)};
+ if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) {
+ auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())};
+ if (command_header.sample_rate != TargetSampleRate &&
+ state->upsampler_info == nullptr) {
+ auto device_state{sink_info->GetDeviceState()};
+ device_state->upsampler_info = render_context.upsampler_manager->Allocate();
+ }
+
+ EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink,
+ sink_info->GetNodeId());
+ auto final_mix{mix_context.GetFinalMixInfo()};
+ GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
+
+ if (device_sink_entry.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ device_sink_entry.node_id, PerformanceState::Stop,
+ device_sink_entry.performance_entry_address);
+ }
+ }
+ }
+
+ for (u32 i = 0; i < sink_count; i++) {
+ auto sink_info{sink_context.GetInfo(i)};
+ if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) {
+ EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink,
+ sink_info->GetNodeId());
+ auto final_mix{mix_context.GetFinalMixInfo()};
+ GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
+
+ if (circular_buffer_entry.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ circular_buffer_entry.node_id, PerformanceState::Stop,
+ circular_buffer_entry.performance_entry_address);
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
+ if (sink_info.ShouldSkip()) {
+ return;
+ }
+
+ switch (sink_info.GetType()) {
+ case SinkInfoBase::Type::DeviceSink:
+ GenerateDeviceSinkCommand(buffer_offset, sink_info);
+ break;
+
+ case SinkInfoBase::Type::CircularBufferSink:
+ command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info,
+ buffer_offset);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType()));
+ break;
+ }
+
+ sink_info.UpdateForCommandGeneration();
+}
+
+void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
+ auto& parameter{
+ *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
+ auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
+
+ if (render_context.channels == 2 && parameter.downmix_enabled) {
+ command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs,
+ buffer_offset, parameter.downmix_coeff);
+ }
+
+ if (state.upsampler_info != nullptr) {
+ command_buffer.GenerateUpsampleCommand(
+ InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count,
+ parameter.inputs, command_header.buffer_count, command_header.sample_count,
+ command_header.sample_rate);
+ }
+
+ command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info,
+ render_context.session_id,
+ command_header.samples_buffer);
+}
+
+void CommandGenerator::GeneratePerformanceCommand(
+ s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) {
+ command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h
new file mode 100644
index 000000000..d80d9b0d8
--- /dev/null
+++ b/src/audio_core/renderer/command/command_generator.h
@@ -0,0 +1,349 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/command/commands.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+struct AudioRendererSystemContext;
+
+namespace AudioRenderer {
+class CommandBuffer;
+struct CommandListHeader;
+class VoiceContext;
+class MixContext;
+class EffectContext;
+class SplitterContext;
+class SinkContext;
+class BehaviorInfo;
+class VoiceInfo;
+struct VoiceState;
+class MixInfo;
+class SinkInfoBase;
+
+/**
+ * Generates all commands to build up a command list, which are sent to the AudioRender for
+ * processing.
+ */
+class CommandGenerator {
+public:
+ explicit CommandGenerator(CommandBuffer& command_buffer,
+ const CommandListHeader& command_list_header,
+ const AudioRendererSystemContext& render_context,
+ VoiceContext& voice_context, MixContext& mix_context,
+ EffectContext& effect_context, SinkContext& sink_context,
+ SplitterContext& splitter_context,
+ PerformanceManager* performance_manager);
+
+ /**
+ * Calculate the buffer size needed for commands.
+ *
+ * @param behavior - Used to check what features are enabled.
+ * @param params - Input rendering parameters for numbers of voices/mixes/sinks etc.
+ */
+ static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior,
+ const AudioRendererParameterInternal& params) {
+ u64 size{0};
+
+ // Effects
+ size += params.effects * sizeof(EffectInfoBase);
+
+ // Voices
+ u64 voice_size{0};
+ if (behavior.IsWaveBufferVer2Supported()) {
+ voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command),
+ sizeof(PcmInt16DataSourceVersion2Command)),
+ sizeof(PcmFloatDataSourceVersion2Command));
+ } else {
+ voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command),
+ sizeof(PcmInt16DataSourceVersion1Command)),
+ sizeof(PcmFloatDataSourceVersion1Command));
+ }
+ voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters;
+ voice_size += sizeof(VolumeRampCommand);
+ voice_size += sizeof(MixRampGroupedCommand);
+
+ size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size);
+
+ // Sub mixes
+ size += sizeof(DepopForMixBuffersCommand) +
+ (sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers;
+
+ // Final mix
+ size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers;
+
+ // Splitters
+ size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers;
+
+ // Sinks
+ size +=
+ params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand));
+
+ // Performance
+ size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 +
+ PerformanceManager::MaxDetailEntries) *
+ sizeof(PerformanceCommand);
+ return size;
+ }
+
+ /**
+ * Get the current command buffer used to generate commands.
+ *
+ * @return The command buffer.
+ */
+ CommandBuffer& GetCommandBuffer() {
+ return command_buffer;
+ }
+
+ /**
+ * Get the current performance manager,
+ *
+ * @return The performance manager. May be nullptr.
+ */
+ PerformanceManager* GetPerformanceManager() {
+ return performance_manager;
+ }
+
+ /**
+ * Generate a data source command.
+ * These are the basis for all audio output.
+ *
+ * @param voice_info - Generate the command from this voice.
+ * @param voice_state - State used by the AudioRenderer across calls.
+ * @param channel - Channel index to generate the command into.
+ */
+ void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state,
+ s8 channel);
+
+ /**
+ * Generate voice mixing commands.
+ * These are used to mix buffers together, to mix one input to many outputs,
+ * and also used as copy commands to move data around and prevent it being accidentally
+ * overwritten, e.g by another data source command into the same channel.
+ *
+ * @param mix_volumes - Current volumes of the mix.
+ * @param prev_mix_volumes - Previous volumes of the mix.
+ * @param voice_state - State used by the AudioRenderer across calls.
+ * @param output_index - Output mix buffer index.
+ * @param buffer_count - Number of active mix buffers.
+ * @param input_index - Input mix buffer index.
+ * @param node_id - Node id of the voice this command is generated for.
+ */
+ void GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
+ std::span<const f32> prev_mix_volumes,
+ const VoiceState& voice_state, s16 output_index, s16 buffer_count,
+ s16 input_index, s32 node_id);
+
+ /**
+ * Generate a biquad filter command for a voice.
+ *
+ * @param voice_info - Voice info this command is generated from.
+ * @param voice_state - State used by the AudioRenderer across calls.
+ * @param buffer_count - Number of active mix buffers.
+ * @param channel - Channel index of this command.
+ * @param node_id - Node id of the voice this command is generated for.
+ */
+ void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state,
+ s16 buffer_count, s8 channel, s32 node_id);
+
+ /**
+ * Generate commands for a voice.
+ * Includes a data source, biquad filter, volume and mixing.
+ *
+ * @param voice_info - Voice info these commands are generated from.
+ */
+ void GenerateVoiceCommand(VoiceInfo& voice_info);
+
+ /**
+ * Generate commands for all voices.
+ */
+ void GenerateVoiceCommands();
+
+ /**
+ * Generate a mixing command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - BufferMixer effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base,
+ s32 node_id);
+
+ /**
+ * Generate a delay effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - Delay effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id);
+
+ /**
+ * Generate a reverb effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - Reverb effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts.
+ */
+ void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id,
+ bool long_size_pre_delay_supported);
+
+ /**
+ * Generate an I3DL2 reverb effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - I3DL2Reverb effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
+ s32 node_id);
+
+ /**
+ * Generate an aux effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - Aux effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
+
+ /**
+ * Generate a biquad filter effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - Aux effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
+ s32 node_id);
+
+ /**
+ * Generate a light limiter effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - Limiter effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ * @param effect_index - Index for the statistics state.
+ */
+ void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
+ s32 node_id, u32 effect_index);
+
+ /**
+ * Generate a capture effect command.
+ * Writes a mix buffer back to game memory.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - Capture effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
+
+ /**
+ * Generate a compressor effect command.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param effect_info_base - Compressor effect info.
+ * @param node_id - Node id of the mix this command is generated for.
+ */
+ void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id);
+
+ /**
+ * Generate all effect commands for a mix.
+ *
+ * @param mix_info - Mix to generate effects from.
+ */
+ void GenerateEffectCommand(MixInfo& mix_info);
+
+ /**
+ * Generate all mix commands.
+ *
+ * @param mix_info - Mix to generate effects from.
+ */
+ void GenerateMixCommands(MixInfo& mix_info);
+
+ /**
+ * Generate a submix command.
+ * Generates all effects and all mixing commands.
+ *
+ * @param mix_info - Mix to generate effects from.
+ */
+ void GenerateSubMixCommand(MixInfo& mix_info);
+
+ /**
+ * Generate all submix command.
+ */
+ void GenerateSubMixCommands();
+
+ /**
+ * Generate the final mix.
+ */
+ void GenerateFinalMixCommand();
+
+ /**
+ * Generate the final mix commands.
+ */
+ void GenerateFinalMixCommands();
+
+ /**
+ * Generate all sink commands.
+ */
+ void GenerateSinkCommands();
+
+ /**
+ * Generate a sink command.
+ * Sends samples out to the backend, or a game-supplied circular buffer.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param sink_info - Sink info to generate the commands from.
+ */
+ void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
+
+ /**
+ * Generate a device sink command.
+ * Sends samples out to the backend.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param sink_info - Sink info to generate the commands from.
+ */
+ void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
+
+ /**
+ * Generate a performance command.
+ * Used to report performance metrics of the AudioRenderer back to the game.
+ *
+ * @param buffer_offset - Base mix buffer offset to use.
+ * @param sink_info - Sink info to generate the commands from.
+ */
+ void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
+ const PerformanceEntryAddresses& entry_addresses);
+
+private:
+ /// Commands will be written by this buffer
+ CommandBuffer& command_buffer;
+ /// Header information for the commands generated
+ const CommandListHeader& command_header;
+ /// Various things to control generation
+ const AudioRendererSystemContext& render_context;
+ /// Used for generating voices
+ VoiceContext& voice_context;
+ /// Used for generating mixes
+ MixContext& mix_context;
+ /// Used for generating effects
+ EffectContext& effect_context;
+ /// Used for generating sinks
+ SinkContext& sink_context;
+ /// Used for generating submixes
+ SplitterContext& splitter_context;
+ /// Used for generating performance
+ PerformanceManager* performance_manager;
+};
+
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/command/command_list_header.h b/src/audio_core/renderer/command/command_list_header.h
new file mode 100644
index 000000000..988530b1f
--- /dev/null
+++ b/src/audio_core/renderer/command/command_list_header.h
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+struct CommandListHeader {
+ u64 buffer_size;
+ u32 command_count;
+ std::span<s32> samples_buffer;
+ s16 buffer_count;
+ u32 sample_count;
+ u32 sample_rate;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.cpp b/src/audio_core/renderer/command/command_processing_time_estimator.cpp
new file mode 100644
index 000000000..3091f587a
--- /dev/null
+++ b/src/audio_core/renderer/command/command_processing_time_estimator.cpp
@@ -0,0 +1,3620 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/command/command_processing_time_estimator.h"
+
+namespace AudioCore::AudioRenderer {
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ const PcmInt16DataSourceVersion1Command& command) const {
+ return static_cast<u32>(command.pitch * 0.25f * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ const PcmInt16DataSourceVersion2Command& command) const {
+ return static_cast<u32>(command.pitch * 0.25f * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const PcmFloatDataSourceVersion1Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const PcmFloatDataSourceVersion2Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ const AdpcmDataSourceVersion1Command& command) const {
+ return static_cast<u32>(command.pitch * 0.25f * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ const AdpcmDataSourceVersion2Command& command) const {
+ return static_cast<u32>(command.pitch * 0.25f * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const VolumeCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 8.8f) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const VolumeRampCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 9.8f) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const BiquadFilterCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 58.0f) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const MixCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 10.0f) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const MixRampCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 14.4f) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(const MixRampGroupedCommand& command) const {
+ u32 count{0};
+ for (u32 i = 0; i < command.buffer_count; i++) {
+ if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
+ count++;
+ }
+ }
+
+ return static_cast<u32>(((static_cast<f32>(sample_count) * 14.4f) * 1.2f) *
+ static_cast<f32>(count));
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const DepopPrepareCommand& command) const {
+ return 1080;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ const DepopForMixBuffersCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * 8.9f) *
+ static_cast<f32>(command.count));
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(const DelayCommand& command) const {
+ return static_cast<u32>((static_cast<f32>(sample_count) * command.parameter.channel_count) *
+ 202.5f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const UpsampleCommand& command) const {
+ return 357915;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
+ return 16108;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(const AuxCommand& command) const {
+ if (command.enabled) {
+ return 15956;
+ }
+ return 3765;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const DeviceSinkCommand& command) const {
+ return 10042;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const CircularBufferSinkCommand& command) const {
+ return 55;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(const ReverbCommand& command) const {
+ if (command.enabled) {
+ return static_cast<u32>(
+ (command.parameter.channel_count * static_cast<f32>(sample_count) * 750) * 1.2f);
+ }
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(const I3dl2ReverbCommand& command) const {
+ if (command.enabled) {
+ return static_cast<u32>(
+ (command.parameter.channel_count * static_cast<f32>(sample_count) * 530) * 1.2f);
+ }
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const PerformanceCommand& command) const {
+ return 1454;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const ClearMixBufferCommand& command) const {
+ return static_cast<u32>(
+ ((static_cast<f32>(sample_count) * 0.83f) * static_cast<f32>(buffer_count)) * 1.2f);
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const CopyMixBufferCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const LightLimiterVersion1Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const LightLimiterVersion2Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const CaptureCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion1::Estimate(
+ [[maybe_unused]] const CompressorCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const PcmInt16DataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 749.269f +
+ 6138.94f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 1195.456f +
+ 7797.047f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const PcmInt16DataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 749.269f +
+ 6138.94f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 1195.456f +
+ 7797.047f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const PcmFloatDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 749.269f +
+ 6138.94f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 1195.456f +
+ 7797.047f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const PcmFloatDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 749.269f +
+ 6138.94f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 1195.456f +
+ 7797.047f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const AdpcmDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 2125.588f +
+ 9039.47f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 3564.088 +
+ 6225.471);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const AdpcmDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 2125.588f +
+ 9039.47f);
+ case 240:
+ return static_cast<u32>(
+ (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 2.0f) * 3564.088 +
+ 6225.471);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const VolumeCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1280.3f);
+ case 240:
+ return static_cast<u32>(1737.8f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const VolumeRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1403.9f);
+ case 240:
+ return static_cast<u32>(1884.3f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const BiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(4813.2f);
+ case 240:
+ return static_cast<u32>(6915.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const MixCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1342.2f);
+ case 240:
+ return static_cast<u32>(1833.2f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const MixRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1859.0f);
+ case 240:
+ return static_cast<u32>(2286.1f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const MixRampGroupedCommand& command) const {
+ u32 count{0};
+ for (u32 i = 0; i < command.buffer_count; i++) {
+ if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
+ count++;
+ }
+ }
+
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) *
+ static_cast<f32>(count));
+ case 240:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) *
+ static_cast<f32>(count));
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const DepopPrepareCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(306.62f);
+ case 240:
+ return static_cast<u32>(293.22f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(762.96f);
+ case 240:
+ return static_cast<u32>(726.96f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DelayCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(41635.555f);
+ case 2:
+ return static_cast<u32>(97861.211f);
+ case 4:
+ return static_cast<u32>(192515.516f);
+ case 6:
+ return static_cast<u32>(301755.969f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(578.529f);
+ case 2:
+ return static_cast<u32>(663.064f);
+ case 4:
+ return static_cast<u32>(703.983f);
+ case 6:
+ return static_cast<u32>(760.032f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(8770.345f);
+ case 2:
+ return static_cast<u32>(25741.18f);
+ case 4:
+ return static_cast<u32>(47551.168f);
+ case 6:
+ return static_cast<u32>(81629.219f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(521.283f);
+ case 2:
+ return static_cast<u32>(585.396f);
+ case 4:
+ return static_cast<u32>(629.884f);
+ case 6:
+ return static_cast<u32>(713.57f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const UpsampleCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(292000.0f);
+ case 240:
+ return static_cast<u32>(0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(10009.0f);
+ case 240:
+ return static_cast<u32>(14577.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const AuxCommand& command) const {
+ // Is this function bugged, returning the wrong time?
+ // Surely the larger time should be returned when enabled...
+ // CMP W8, #0
+ // MOV W8, #0x60; // 489.163f
+ // MOV W10, #0x64; // 7177.936f
+ // CSEL X8, X10, X8, EQ
+
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(489.163f);
+ }
+ return static_cast<u32>(7177.936f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(485.562f);
+ }
+ return static_cast<u32>(9499.822f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DeviceSinkCommand& command) const {
+ switch (command.input_count) {
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9261.545f);
+ case 240:
+ return static_cast<u32>(9336.054f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9336.054f);
+ case 240:
+ return static_cast<u32>(9566.728f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ const CircularBufferSinkCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 853.629f + 1284.517f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 1726.021f + 1369.683f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(97192.227f);
+ case 2:
+ return static_cast<u32>(103278.555f);
+ case 4:
+ return static_cast<u32>(109579.039f);
+ case 6:
+ return static_cast<u32>(115065.438f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(492.009f);
+ case 2:
+ return static_cast<u32>(554.463f);
+ case 4:
+ return static_cast<u32>(595.864f);
+ case 6:
+ return static_cast<u32>(656.617f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(136463.641f);
+ case 2:
+ return static_cast<u32>(145749.047f);
+ case 4:
+ return static_cast<u32>(154796.938f);
+ case 6:
+ return static_cast<u32>(161968.406f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(495.789f);
+ case 2:
+ return static_cast<u32>(527.163f);
+ case 4:
+ return static_cast<u32>(598.752f);
+ case 6:
+ return static_cast<u32>(666.025f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(const I3dl2ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(138836.484f);
+ case 2:
+ return static_cast<u32>(135428.172f);
+ case 4:
+ return static_cast<u32>(199181.844f);
+ case 6:
+ return static_cast<u32>(247345.906f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(718.704f);
+ case 2:
+ return static_cast<u32>(751.296f);
+ case 4:
+ return static_cast<u32>(797.464f);
+ case 6:
+ return static_cast<u32>(867.426f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(199952.734f);
+ case 2:
+ return static_cast<u32>(195199.5f);
+ case 4:
+ return static_cast<u32>(290575.875f);
+ case 6:
+ return static_cast<u32>(363494.531f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(534.24f);
+ case 2:
+ return static_cast<u32>(570.874f);
+ case 4:
+ return static_cast<u32>(660.933f);
+ case 6:
+ return static_cast<u32>(694.596f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const PerformanceCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(489.35f);
+ case 240:
+ return static_cast<u32>(491.18f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const ClearMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(buffer_count) * 260.4f + 139.65f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(buffer_count) * 668.85f + 193.2f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const CopyMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(836.32f);
+ case 240:
+ return static_cast<u32>(1000.9f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const LightLimiterVersion1Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const LightLimiterVersion2Command& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const CaptureCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion2::Estimate(
+ [[maybe_unused]] const CompressorCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const PcmInt16DataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 427.52f +
+ 6329.442f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 710.143f +
+ 7853.286f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const PcmInt16DataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 427.52f +
+ 6329.442f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 371.876f +
+ 8049.415f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 423.43f +
+ 5062.659f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 710.143f +
+ 7853.286f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 610.487f +
+ 10138.842f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 676.722f +
+ 5810.962f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const PcmFloatDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1672.026f +
+ 7681.211f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2550.414f +
+ 9663.969f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const PcmFloatDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.026f +
+ 7681.211f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.982f +
+ 9038.011f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1673.216f +
+ 6027.577f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2550.414f +
+ 9663.969f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2522.303f +
+ 11758.571f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2537.061f +
+ 7369.309f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const AdpcmDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1827.665f +
+ 7913.808f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2756.372f +
+ 9736.702f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const AdpcmDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1827.665f +
+ 7913.808f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1829.285f +
+ 9607.814f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1824.609f +
+ 6517.476f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2756.372f +
+ 9736.702f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2731.308f +
+ 12154.379f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2732.152f +
+ 7929.442f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const VolumeCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1311.1f);
+ case 240:
+ return static_cast<u32>(1713.6f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const VolumeRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1425.3f);
+ case 240:
+ return static_cast<u32>(1700.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const BiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(4173.2f);
+ case 240:
+ return static_cast<u32>(5585.1f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const MixCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1402.8f);
+ case 240:
+ return static_cast<u32>(1853.2f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const MixRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1968.7f);
+ case 240:
+ return static_cast<u32>(2459.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const MixRampGroupedCommand& command) const {
+ u32 count{0};
+ for (u32 i = 0; i < command.buffer_count; i++) {
+ if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
+ count++;
+ }
+ }
+
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
+ static_cast<f32>(count));
+ case 240:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
+ static_cast<f32>(count));
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const DepopPrepareCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(739.64f);
+ case 240:
+ return static_cast<u32>(910.97f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DelayCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(8929.042f);
+ case 2:
+ return static_cast<u32>(25500.75f);
+ case 4:
+ return static_cast<u32>(47759.617f);
+ case 6:
+ return static_cast<u32>(82203.07f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(1295.206f);
+ case 2:
+ return static_cast<u32>(1213.6f);
+ case 4:
+ return static_cast<u32>(942.028f);
+ case 6:
+ return static_cast<u32>(1001.553f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(11941.051f);
+ case 2:
+ return static_cast<u32>(37197.371f);
+ case 4:
+ return static_cast<u32>(69749.836f);
+ case 6:
+ return static_cast<u32>(120042.398f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(997.668f);
+ case 2:
+ return static_cast<u32>(977.634f);
+ case 4:
+ return static_cast<u32>(792.309f);
+ case 6:
+ return static_cast<u32>(875.427f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const UpsampleCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(312990.0f);
+ case 240:
+ return static_cast<u32>(0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9949.7f);
+ case 240:
+ return static_cast<u32>(14679.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const AuxCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(7182.136f);
+ }
+ return static_cast<u32>(472.111f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(9435.961f);
+ }
+ return static_cast<u32>(462.619f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DeviceSinkCommand& command) const {
+ switch (command.input_count) {
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(8979.956f);
+ case 240:
+ return static_cast<u32>(9221.907f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9177.903f);
+ case 240:
+ return static_cast<u32>(9725.897f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const CircularBufferSinkCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(81475.055f);
+ case 2:
+ return static_cast<u32>(84975.0f);
+ case 4:
+ return static_cast<u32>(91625.148f);
+ case 6:
+ return static_cast<u32>(95332.266f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(536.298f);
+ case 2:
+ return static_cast<u32>(588.798f);
+ case 4:
+ return static_cast<u32>(643.702f);
+ case 6:
+ return static_cast<u32>(705.999f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(120174.469f);
+ case 2:
+ return static_cast<u32>(125262.219f);
+ case 4:
+ return static_cast<u32>(135751.234f);
+ case 6:
+ return static_cast<u32>(141129.234f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(617.641f);
+ case 2:
+ return static_cast<u32>(659.536f);
+ case 4:
+ return static_cast<u32>(711.438f);
+ case 6:
+ return static_cast<u32>(778.071f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(const I3dl2ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(116754.984f);
+ case 2:
+ return static_cast<u32>(125912.055f);
+ case 4:
+ return static_cast<u32>(146336.031f);
+ case 6:
+ return static_cast<u32>(165812.656f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(735.0f);
+ case 2:
+ return static_cast<u32>(766.615f);
+ case 4:
+ return static_cast<u32>(834.067f);
+ case 6:
+ return static_cast<u32>(875.437f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(170292.344f);
+ case 2:
+ return static_cast<u32>(183875.625f);
+ case 4:
+ return static_cast<u32>(214696.188f);
+ case 6:
+ return static_cast<u32>(243846.766f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(508.473f);
+ case 2:
+ return static_cast<u32>(582.445f);
+ case 4:
+ return static_cast<u32>(626.419f);
+ case 6:
+ return static_cast<u32>(682.468f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const PerformanceCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(498.17f);
+ case 240:
+ return static_cast<u32>(489.42f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const ClearMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const CopyMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(842.59f);
+ case 240:
+ return static_cast<u32>(986.72f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const LightLimiterVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21392.383f);
+ case 2:
+ return static_cast<u32>(26829.389f);
+ case 4:
+ return static_cast<u32>(32405.152f);
+ case 6:
+ return static_cast<u32>(52218.586f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30555.504f);
+ case 2:
+ return static_cast<u32>(39010.785f);
+ case 4:
+ return static_cast<u32>(48270.18f);
+ case 6:
+ return static_cast<u32>(76711.875f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ const LightLimiterVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(23308.928f);
+ case 2:
+ return static_cast<u32>(29954.062f);
+ case 4:
+ return static_cast<u32>(35807.477f);
+ case 6:
+ return static_cast<u32>(58339.773f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21392.383f);
+ case 2:
+ return static_cast<u32>(26829.389f);
+ case 4:
+ return static_cast<u32>(32405.152f);
+ case 6:
+ return static_cast<u32>(52218.586f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(33526.121f);
+ case 2:
+ return static_cast<u32>(43549.355f);
+ case 4:
+ return static_cast<u32>(52190.281f);
+ case 6:
+ return static_cast<u32>(85526.516f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30555.504f);
+ case 2:
+ return static_cast<u32>(39010.785f);
+ case 4:
+ return static_cast<u32>(48270.18f);
+ case 6:
+ return static_cast<u32>(76711.875f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const CaptureCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion3::Estimate(
+ [[maybe_unused]] const CompressorCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const PcmInt16DataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 427.52f +
+ 6329.442f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 710.143f +
+ 7853.286f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const PcmInt16DataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 427.52f +
+ 6329.442f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 371.876f +
+ 8049.415f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 423.43f +
+ 5062.659f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 710.143f +
+ 7853.286f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 610.487f +
+ 10138.842f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 676.722f +
+ 5810.962f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const PcmFloatDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1672.026f +
+ 7681.211f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2550.414f +
+ 9663.969f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const PcmFloatDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.026f +
+ 7681.211f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.982f +
+ 9038.011f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1673.216f +
+ 6027.577f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2550.414f +
+ 9663.969f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2522.303f +
+ 11758.571f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2537.061f +
+ 7369.309f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const AdpcmDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1827.665f +
+ 7913.808f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2756.372f +
+ 9736.702f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const AdpcmDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1827.665f +
+ 7913.808f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1829.285f +
+ 9607.814f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1824.609f +
+ 6517.476f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2756.372f +
+ 9736.702f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2731.308f +
+ 12154.379f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2732.152f +
+ 7929.442f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const VolumeCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1311.1f);
+ case 240:
+ return static_cast<u32>(1713.6f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const VolumeRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1425.3f);
+ case 240:
+ return static_cast<u32>(1700.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const BiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(4173.2f);
+ case 240:
+ return static_cast<u32>(5585.1f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const MixCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1402.8f);
+ case 240:
+ return static_cast<u32>(1853.2f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const MixRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1968.7f);
+ case 240:
+ return static_cast<u32>(2459.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const MixRampGroupedCommand& command) const {
+ u32 count{0};
+ for (u32 i = 0; i < command.buffer_count; i++) {
+ if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
+ count++;
+ }
+ }
+
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
+ static_cast<f32>(count));
+ case 240:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
+ static_cast<f32>(count));
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const DepopPrepareCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(739.64f);
+ case 240:
+ return static_cast<u32>(910.97f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DelayCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(8929.042f);
+ case 2:
+ return static_cast<u32>(25500.75f);
+ case 4:
+ return static_cast<u32>(47759.617f);
+ case 6:
+ return static_cast<u32>(82203.07f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(1295.206f);
+ case 2:
+ return static_cast<u32>(1213.6f);
+ case 4:
+ return static_cast<u32>(942.028f);
+ case 6:
+ return static_cast<u32>(1001.553f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(11941.051f);
+ case 2:
+ return static_cast<u32>(37197.371f);
+ case 4:
+ return static_cast<u32>(69749.836f);
+ case 6:
+ return static_cast<u32>(120042.398f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(997.668f);
+ case 2:
+ return static_cast<u32>(977.634f);
+ case 4:
+ return static_cast<u32>(792.309f);
+ case 6:
+ return static_cast<u32>(875.427f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const UpsampleCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(312990.0f);
+ case 240:
+ return static_cast<u32>(0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9949.7f);
+ case 240:
+ return static_cast<u32>(14679.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const AuxCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(7182.136f);
+ }
+ return static_cast<u32>(472.111f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(9435.961f);
+ }
+ return static_cast<u32>(462.619f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DeviceSinkCommand& command) const {
+ switch (command.input_count) {
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(8979.956f);
+ case 240:
+ return static_cast<u32>(9221.907f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9177.903f);
+ case 240:
+ return static_cast<u32>(9725.897f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const CircularBufferSinkCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(81475.055f);
+ case 2:
+ return static_cast<u32>(84975.0f);
+ case 4:
+ return static_cast<u32>(91625.148f);
+ case 6:
+ return static_cast<u32>(95332.266f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(536.298f);
+ case 2:
+ return static_cast<u32>(588.798f);
+ case 4:
+ return static_cast<u32>(643.702f);
+ case 6:
+ return static_cast<u32>(705.999f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(120174.469f);
+ case 2:
+ return static_cast<u32>(125262.219f);
+ case 4:
+ return static_cast<u32>(135751.234f);
+ case 6:
+ return static_cast<u32>(141129.234f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(617.641f);
+ case 2:
+ return static_cast<u32>(659.536f);
+ case 4:
+ return static_cast<u32>(711.438f);
+ case 6:
+ return static_cast<u32>(778.071f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const I3dl2ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(116754.984f);
+ case 2:
+ return static_cast<u32>(125912.055f);
+ case 4:
+ return static_cast<u32>(146336.031f);
+ case 6:
+ return static_cast<u32>(165812.656f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(735.0f);
+ case 2:
+ return static_cast<u32>(766.615f);
+ case 4:
+ return static_cast<u32>(834.067f);
+ case 6:
+ return static_cast<u32>(875.437f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(170292.344f);
+ case 2:
+ return static_cast<u32>(183875.625f);
+ case 4:
+ return static_cast<u32>(214696.188f);
+ case 6:
+ return static_cast<u32>(243846.766f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(508.473f);
+ case 2:
+ return static_cast<u32>(582.445f);
+ case 4:
+ return static_cast<u32>(626.419f);
+ case 6:
+ return static_cast<u32>(682.468f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const PerformanceCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(498.17f);
+ case 240:
+ return static_cast<u32>(489.42f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const ClearMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const CopyMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(842.59f);
+ case 240:
+ return static_cast<u32>(986.72f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const LightLimiterVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21392.383f);
+ case 2:
+ return static_cast<u32>(26829.389f);
+ case 4:
+ return static_cast<u32>(32405.152f);
+ case 6:
+ return static_cast<u32>(52218.586f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30555.504f);
+ case 2:
+ return static_cast<u32>(39010.785f);
+ case 4:
+ return static_cast<u32>(48270.18f);
+ case 6:
+ return static_cast<u32>(76711.875f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ const LightLimiterVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(23308.928f);
+ case 2:
+ return static_cast<u32>(29954.062f);
+ case 4:
+ return static_cast<u32>(35807.477f);
+ case 6:
+ return static_cast<u32>(58339.773f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21392.383f);
+ case 2:
+ return static_cast<u32>(26829.389f);
+ case 4:
+ return static_cast<u32>(32405.152f);
+ case 6:
+ return static_cast<u32>(52218.586f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(33526.121f);
+ case 2:
+ return static_cast<u32>(43549.355f);
+ case 4:
+ return static_cast<u32>(52190.281f);
+ case 6:
+ return static_cast<u32>(85526.516f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30555.504f);
+ case 2:
+ return static_cast<u32>(39010.785f);
+ case 4:
+ return static_cast<u32>(48270.18f);
+ case 6:
+ return static_cast<u32>(76711.875f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(7424.5f);
+ case 240:
+ return static_cast<u32>(9730.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(const CaptureCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(426.982f);
+ }
+ return static_cast<u32>(4261.005f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(435.204f);
+ }
+ return static_cast<u32>(5858.265f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion4::Estimate(
+ [[maybe_unused]] const CompressorCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const PcmInt16DataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 427.52f +
+ 6329.442f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 710.143f +
+ 7853.286f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const PcmInt16DataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 427.52f +
+ 6329.442f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 371.876f +
+ 8049.415f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 423.43f +
+ 5062.659f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 710.143f +
+ 7853.286f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 610.487f +
+ 10138.842f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 676.722f +
+ 5810.962f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const PcmFloatDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1672.026f +
+ 7681.211f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2550.414f +
+ 9663.969f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const PcmFloatDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.026f +
+ 7681.211f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1672.982f +
+ 9038.011f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1673.216f +
+ 6027.577f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2550.414f +
+ 9663.969f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2522.303f +
+ 11758.571f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2537.061f +
+ 7369.309f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const AdpcmDataSourceVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 1827.665f +
+ 7913.808f);
+ case 240:
+ return static_cast<u32>(
+ ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) *
+ 2756.372f +
+ 9736.702f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const AdpcmDataSourceVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1827.665f +
+ 7913.808f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1829.285f +
+ 9607.814f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 1824.609f +
+ 6517.476f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ case 240:
+ switch (command.src_quality) {
+ case SrcQuality::Medium:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2756.372f +
+ 9736.702f);
+ case SrcQuality::High:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2731.308f +
+ 12154.379f);
+ case SrcQuality::Low:
+ return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
+ static_cast<f32>(sample_count)) *
+ (command.pitch * 0.000030518f)) -
+ 1.0f) *
+ 2732.152f +
+ 7929.442f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
+ static_cast<u32>(command.src_quality));
+ return 0;
+ }
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const VolumeCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1311.1f);
+ case 240:
+ return static_cast<u32>(1713.6f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const VolumeRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1425.3f);
+ case 240:
+ return static_cast<u32>(1700.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const BiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(4173.2f);
+ case 240:
+ return static_cast<u32>(5585.1f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const MixCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1402.8f);
+ case 240:
+ return static_cast<u32>(1853.2f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const MixRampCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(1968.7f);
+ case 240:
+ return static_cast<u32>(2459.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const MixRampGroupedCommand& command) const {
+ u32 count{0};
+ for (u32 i = 0; i < command.buffer_count; i++) {
+ if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
+ count++;
+ }
+ }
+
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
+ static_cast<f32>(count));
+ case 240:
+ return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
+ static_cast<f32>(count));
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const DepopPrepareCommand& command) const {
+ return 0;
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(739.64f);
+ case 240:
+ return static_cast<u32>(910.97f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DelayCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(8929.042f);
+ case 2:
+ return static_cast<u32>(25500.75f);
+ case 4:
+ return static_cast<u32>(47759.617f);
+ case 6:
+ return static_cast<u32>(82203.07f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(1295.206f);
+ case 2:
+ return static_cast<u32>(1213.6f);
+ case 4:
+ return static_cast<u32>(942.028f);
+ case 6:
+ return static_cast<u32>(1001.553f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(11941.051f);
+ case 2:
+ return static_cast<u32>(37197.371f);
+ case 4:
+ return static_cast<u32>(69749.836f);
+ case 6:
+ return static_cast<u32>(120042.398f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(997.668f);
+ case 2:
+ return static_cast<u32>(977.634f);
+ case 4:
+ return static_cast<u32>(792.309f);
+ case 6:
+ return static_cast<u32>(875.427f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const UpsampleCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(312990.0f);
+ case 240:
+ return static_cast<u32>(0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9949.7f);
+ case 240:
+ return static_cast<u32>(14679.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const AuxCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(7182.136f);
+ }
+ return static_cast<u32>(472.111f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(9435.961f);
+ }
+ return static_cast<u32>(462.619f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DeviceSinkCommand& command) const {
+ switch (command.input_count) {
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(8979.956f);
+ case 240:
+ return static_cast<u32>(9221.907f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(9177.903f);
+ case 240:
+ return static_cast<u32>(9725.897f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const CircularBufferSinkCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(81475.055f);
+ case 2:
+ return static_cast<u32>(84975.0f);
+ case 4:
+ return static_cast<u32>(91625.148f);
+ case 6:
+ return static_cast<u32>(95332.266f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(536.298f);
+ case 2:
+ return static_cast<u32>(588.798f);
+ case 4:
+ return static_cast<u32>(643.702f);
+ case 6:
+ return static_cast<u32>(705.999f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(120174.469f);
+ case 2:
+ return static_cast<u32>(125262.219f);
+ case 4:
+ return static_cast<u32>(135751.234f);
+ case 6:
+ return static_cast<u32>(141129.234f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(617.641f);
+ case 2:
+ return static_cast<u32>(659.536f);
+ case 4:
+ return static_cast<u32>(711.438f);
+ case 6:
+ return static_cast<u32>(778.071f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const I3dl2ReverbCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(116754.984f);
+ case 2:
+ return static_cast<u32>(125912.055f);
+ case 4:
+ return static_cast<u32>(146336.031f);
+ case 6:
+ return static_cast<u32>(165812.656f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(735.0f);
+ case 2:
+ return static_cast<u32>(766.615f);
+ case 4:
+ return static_cast<u32>(834.067f);
+ case 6:
+ return static_cast<u32>(875.437f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(170292.344f);
+ case 2:
+ return static_cast<u32>(183875.625f);
+ case 4:
+ return static_cast<u32>(214696.188f);
+ case 6:
+ return static_cast<u32>(243846.766f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(508.473f);
+ case 2:
+ return static_cast<u32>(582.445f);
+ case 4:
+ return static_cast<u32>(626.419f);
+ case 6:
+ return static_cast<u32>(682.468f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const PerformanceCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(498.17f);
+ case 240:
+ return static_cast<u32>(489.42f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const ClearMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
+ case 240:
+ return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const CopyMixBufferCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(842.59f);
+ case 240:
+ return static_cast<u32>(986.72f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const LightLimiterVersion1Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21508.01f);
+ case 2:
+ return static_cast<u32>(23120.453f);
+ case 4:
+ return static_cast<u32>(26270.053f);
+ case 6:
+ return static_cast<u32>(40471.902f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30565.961f);
+ case 2:
+ return static_cast<u32>(32812.91f);
+ case 4:
+ return static_cast<u32>(37354.852f);
+ case 6:
+ return static_cast<u32>(58486.699f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ const LightLimiterVersion2Command& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(23639.584f);
+ case 2:
+ return static_cast<u32>(24666.725f);
+ case 4:
+ return static_cast<u32>(28876.459f);
+ case 6:
+ return static_cast<u32>(47096.078f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ } else {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(21508.01f);
+ case 2:
+ return static_cast<u32>(23120.453f);
+ case 4:
+ return static_cast<u32>(26270.053f);
+ case 6:
+ return static_cast<u32>(40471.902f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ }
+ } else if (command.parameter.processing_mode ==
+ LightLimiterInfo::ProcessingMode::Mode1) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(23639.584f);
+ case 2:
+ return static_cast<u32>(29954.062f);
+ case 4:
+ return static_cast<u32>(35807.477f);
+ case 6:
+ return static_cast<u32>(58339.773f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ } else {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(23639.584f);
+ case 2:
+ return static_cast<u32>(29954.062f);
+ case 4:
+ return static_cast<u32>(35807.477f);
+ case 6:
+ return static_cast<u32>(58339.773f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ }
+ } else {
+ LOG_ERROR(Service_Audio, "Invalid processing mode {}",
+ command.parameter.processing_mode);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(897.004f);
+ case 2:
+ return static_cast<u32>(931.549f);
+ case 4:
+ return static_cast<u32>(975.387f);
+ case 6:
+ return static_cast<u32>(1016.778f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ case 240:
+ if (command.enabled) {
+ if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(33875.023f);
+ case 2:
+ return static_cast<u32>(35199.938f);
+ case 4:
+ return static_cast<u32>(41371.230f);
+ case 6:
+ return static_cast<u32>(68370.914f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ } else {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30565.961f);
+ case 2:
+ return static_cast<u32>(32812.91f);
+ case 4:
+ return static_cast<u32>(37354.852f);
+ case 6:
+ return static_cast<u32>(58486.699f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ } else if (command.parameter.processing_mode ==
+ LightLimiterInfo::ProcessingMode::Mode1) {
+ if (command.parameter.statistics_enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(33942.980f);
+ case 2:
+ return static_cast<u32>(28698.893f);
+ case 4:
+ return static_cast<u32>(34774.277f);
+ case 6:
+ return static_cast<u32>(61897.773f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ } else {
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(30610.248f);
+ case 2:
+ return static_cast<u32>(26322.408f);
+ case 4:
+ return static_cast<u32>(30369.000f);
+ case 6:
+ return static_cast<u32>(51892.090f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}",
+ command.parameter.channel_count);
+ return 0;
+ }
+ }
+ } else {
+ LOG_ERROR(Service_Audio, "Invalid processing mode {}",
+ command.parameter.processing_mode);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ return static_cast<u32>(874.429f);
+ case 2:
+ return static_cast<u32>(921.553f);
+ case 4:
+ return static_cast<u32>(945.262f);
+ case 6:
+ return static_cast<u32>(992.26f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(
+ [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(7424.5f);
+ case 240:
+ return static_cast<u32>(9730.4f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CaptureCommand& command) const {
+ switch (sample_count) {
+ case 160:
+ if (command.enabled) {
+ return static_cast<u32>(426.982f);
+ }
+ return static_cast<u32>(4261.005f);
+ case 240:
+ if (command.enabled) {
+ return static_cast<u32>(435.204f);
+ }
+ return static_cast<u32>(5858.265f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+}
+
+u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CompressorCommand& command) const {
+ if (command.enabled) {
+ switch (command.parameter.channel_count) {
+ case 1:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(34430.570f);
+ case 240:
+ return static_cast<u32>(51095.348f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(44253.320f);
+ case 240:
+ return static_cast<u32>(65693.094f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 4:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(63827.457f);
+ case 240:
+ return static_cast<u32>(95382.852f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(83361.484f);
+ case 240:
+ return static_cast<u32>(124509.906f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+ }
+ switch (command.parameter.channel_count) {
+ case 1:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(630.115f);
+ case 240:
+ return static_cast<u32>(840.136f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 2:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(638.274f);
+ case 240:
+ return static_cast<u32>(826.098f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 4:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(705.862f);
+ case 240:
+ return static_cast<u32>(901.876f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ case 6:
+ switch (sample_count) {
+ case 160:
+ return static_cast<u32>(782.019f);
+ case 240:
+ return static_cast<u32>(965.286f);
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
+ return 0;
+ }
+ default:
+ LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
+ return 0;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.h b/src/audio_core/renderer/command/command_processing_time_estimator.h
new file mode 100644
index 000000000..452217196
--- /dev/null
+++ b/src/audio_core/renderer/command/command_processing_time_estimator.h
@@ -0,0 +1,254 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/command/commands.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Estimate the processing time required for all commands.
+ */
+class ICommandProcessingTimeEstimator {
+public:
+ virtual ~ICommandProcessingTimeEstimator() = default;
+
+ virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0;
+ virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0;
+ virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0;
+ virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0;
+ virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0;
+ virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0;
+ virtual u32 Estimate(const VolumeCommand& command) const = 0;
+ virtual u32 Estimate(const VolumeRampCommand& command) const = 0;
+ virtual u32 Estimate(const BiquadFilterCommand& command) const = 0;
+ virtual u32 Estimate(const MixCommand& command) const = 0;
+ virtual u32 Estimate(const MixRampCommand& command) const = 0;
+ virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0;
+ virtual u32 Estimate(const DepopPrepareCommand& command) const = 0;
+ virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0;
+ virtual u32 Estimate(const DelayCommand& command) const = 0;
+ virtual u32 Estimate(const UpsampleCommand& command) const = 0;
+ virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0;
+ virtual u32 Estimate(const AuxCommand& command) const = 0;
+ virtual u32 Estimate(const DeviceSinkCommand& command) const = 0;
+ virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0;
+ virtual u32 Estimate(const ReverbCommand& command) const = 0;
+ virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0;
+ virtual u32 Estimate(const PerformanceCommand& command) const = 0;
+ virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0;
+ virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0;
+ virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0;
+ virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0;
+ virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0;
+ virtual u32 Estimate(const CaptureCommand& command) const = 0;
+ virtual u32 Estimate(const CompressorCommand& command) const = 0;
+};
+
+class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator {
+public:
+ CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_)
+ : sample_count{sample_count_}, buffer_count{buffer_count_} {}
+
+ u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
+ u32 Estimate(const VolumeCommand& command) const override;
+ u32 Estimate(const VolumeRampCommand& command) const override;
+ u32 Estimate(const BiquadFilterCommand& command) const override;
+ u32 Estimate(const MixCommand& command) const override;
+ u32 Estimate(const MixRampCommand& command) const override;
+ u32 Estimate(const MixRampGroupedCommand& command) const override;
+ u32 Estimate(const DepopPrepareCommand& command) const override;
+ u32 Estimate(const DepopForMixBuffersCommand& command) const override;
+ u32 Estimate(const DelayCommand& command) const override;
+ u32 Estimate(const UpsampleCommand& command) const override;
+ u32 Estimate(const DownMix6chTo2chCommand& command) const override;
+ u32 Estimate(const AuxCommand& command) const override;
+ u32 Estimate(const DeviceSinkCommand& command) const override;
+ u32 Estimate(const CircularBufferSinkCommand& command) const override;
+ u32 Estimate(const ReverbCommand& command) const override;
+ u32 Estimate(const I3dl2ReverbCommand& command) const override;
+ u32 Estimate(const PerformanceCommand& command) const override;
+ u32 Estimate(const ClearMixBufferCommand& command) const override;
+ u32 Estimate(const CopyMixBufferCommand& command) const override;
+ u32 Estimate(const LightLimiterVersion1Command& command) const override;
+ u32 Estimate(const LightLimiterVersion2Command& command) const override;
+ u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
+ u32 Estimate(const CaptureCommand& command) const override;
+ u32 Estimate(const CompressorCommand& command) const override;
+
+private:
+ u32 sample_count{};
+ u32 buffer_count{};
+};
+
+class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator {
+public:
+ CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_)
+ : sample_count{sample_count_}, buffer_count{buffer_count_} {}
+
+ u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
+ u32 Estimate(const VolumeCommand& command) const override;
+ u32 Estimate(const VolumeRampCommand& command) const override;
+ u32 Estimate(const BiquadFilterCommand& command) const override;
+ u32 Estimate(const MixCommand& command) const override;
+ u32 Estimate(const MixRampCommand& command) const override;
+ u32 Estimate(const MixRampGroupedCommand& command) const override;
+ u32 Estimate(const DepopPrepareCommand& command) const override;
+ u32 Estimate(const DepopForMixBuffersCommand& command) const override;
+ u32 Estimate(const DelayCommand& command) const override;
+ u32 Estimate(const UpsampleCommand& command) const override;
+ u32 Estimate(const DownMix6chTo2chCommand& command) const override;
+ u32 Estimate(const AuxCommand& command) const override;
+ u32 Estimate(const DeviceSinkCommand& command) const override;
+ u32 Estimate(const CircularBufferSinkCommand& command) const override;
+ u32 Estimate(const ReverbCommand& command) const override;
+ u32 Estimate(const I3dl2ReverbCommand& command) const override;
+ u32 Estimate(const PerformanceCommand& command) const override;
+ u32 Estimate(const ClearMixBufferCommand& command) const override;
+ u32 Estimate(const CopyMixBufferCommand& command) const override;
+ u32 Estimate(const LightLimiterVersion1Command& command) const override;
+ u32 Estimate(const LightLimiterVersion2Command& command) const override;
+ u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
+ u32 Estimate(const CaptureCommand& command) const override;
+ u32 Estimate(const CompressorCommand& command) const override;
+
+private:
+ u32 sample_count{};
+ u32 buffer_count{};
+};
+
+class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator {
+public:
+ CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_)
+ : sample_count{sample_count_}, buffer_count{buffer_count_} {}
+
+ u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
+ u32 Estimate(const VolumeCommand& command) const override;
+ u32 Estimate(const VolumeRampCommand& command) const override;
+ u32 Estimate(const BiquadFilterCommand& command) const override;
+ u32 Estimate(const MixCommand& command) const override;
+ u32 Estimate(const MixRampCommand& command) const override;
+ u32 Estimate(const MixRampGroupedCommand& command) const override;
+ u32 Estimate(const DepopPrepareCommand& command) const override;
+ u32 Estimate(const DepopForMixBuffersCommand& command) const override;
+ u32 Estimate(const DelayCommand& command) const override;
+ u32 Estimate(const UpsampleCommand& command) const override;
+ u32 Estimate(const DownMix6chTo2chCommand& command) const override;
+ u32 Estimate(const AuxCommand& command) const override;
+ u32 Estimate(const DeviceSinkCommand& command) const override;
+ u32 Estimate(const CircularBufferSinkCommand& command) const override;
+ u32 Estimate(const ReverbCommand& command) const override;
+ u32 Estimate(const I3dl2ReverbCommand& command) const override;
+ u32 Estimate(const PerformanceCommand& command) const override;
+ u32 Estimate(const ClearMixBufferCommand& command) const override;
+ u32 Estimate(const CopyMixBufferCommand& command) const override;
+ u32 Estimate(const LightLimiterVersion1Command& command) const override;
+ u32 Estimate(const LightLimiterVersion2Command& command) const override;
+ u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
+ u32 Estimate(const CaptureCommand& command) const override;
+ u32 Estimate(const CompressorCommand& command) const override;
+
+private:
+ u32 sample_count{};
+ u32 buffer_count{};
+};
+
+class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator {
+public:
+ CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_)
+ : sample_count{sample_count_}, buffer_count{buffer_count_} {}
+
+ u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
+ u32 Estimate(const VolumeCommand& command) const override;
+ u32 Estimate(const VolumeRampCommand& command) const override;
+ u32 Estimate(const BiquadFilterCommand& command) const override;
+ u32 Estimate(const MixCommand& command) const override;
+ u32 Estimate(const MixRampCommand& command) const override;
+ u32 Estimate(const MixRampGroupedCommand& command) const override;
+ u32 Estimate(const DepopPrepareCommand& command) const override;
+ u32 Estimate(const DepopForMixBuffersCommand& command) const override;
+ u32 Estimate(const DelayCommand& command) const override;
+ u32 Estimate(const UpsampleCommand& command) const override;
+ u32 Estimate(const DownMix6chTo2chCommand& command) const override;
+ u32 Estimate(const AuxCommand& command) const override;
+ u32 Estimate(const DeviceSinkCommand& command) const override;
+ u32 Estimate(const CircularBufferSinkCommand& command) const override;
+ u32 Estimate(const ReverbCommand& command) const override;
+ u32 Estimate(const I3dl2ReverbCommand& command) const override;
+ u32 Estimate(const PerformanceCommand& command) const override;
+ u32 Estimate(const ClearMixBufferCommand& command) const override;
+ u32 Estimate(const CopyMixBufferCommand& command) const override;
+ u32 Estimate(const LightLimiterVersion1Command& command) const override;
+ u32 Estimate(const LightLimiterVersion2Command& command) const override;
+ u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
+ u32 Estimate(const CaptureCommand& command) const override;
+ u32 Estimate(const CompressorCommand& command) const override;
+
+private:
+ u32 sample_count{};
+ u32 buffer_count{};
+};
+
+class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator {
+public:
+ CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_)
+ : sample_count{sample_count_}, buffer_count{buffer_count_} {}
+
+ u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
+ u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
+ u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
+ u32 Estimate(const VolumeCommand& command) const override;
+ u32 Estimate(const VolumeRampCommand& command) const override;
+ u32 Estimate(const BiquadFilterCommand& command) const override;
+ u32 Estimate(const MixCommand& command) const override;
+ u32 Estimate(const MixRampCommand& command) const override;
+ u32 Estimate(const MixRampGroupedCommand& command) const override;
+ u32 Estimate(const DepopPrepareCommand& command) const override;
+ u32 Estimate(const DepopForMixBuffersCommand& command) const override;
+ u32 Estimate(const DelayCommand& command) const override;
+ u32 Estimate(const UpsampleCommand& command) const override;
+ u32 Estimate(const DownMix6chTo2chCommand& command) const override;
+ u32 Estimate(const AuxCommand& command) const override;
+ u32 Estimate(const DeviceSinkCommand& command) const override;
+ u32 Estimate(const CircularBufferSinkCommand& command) const override;
+ u32 Estimate(const ReverbCommand& command) const override;
+ u32 Estimate(const I3dl2ReverbCommand& command) const override;
+ u32 Estimate(const PerformanceCommand& command) const override;
+ u32 Estimate(const ClearMixBufferCommand& command) const override;
+ u32 Estimate(const CopyMixBufferCommand& command) const override;
+ u32 Estimate(const LightLimiterVersion1Command& command) const override;
+ u32 Estimate(const LightLimiterVersion2Command& command) const override;
+ u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
+ u32 Estimate(const CaptureCommand& command) const override;
+ u32 Estimate(const CompressorCommand& command) const override;
+
+private:
+ u32 sample_count{};
+ u32 buffer_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/commands.h b/src/audio_core/renderer/command/commands.h
new file mode 100644
index 000000000..6d8b8546d
--- /dev/null
+++ b/src/audio_core/renderer/command/commands.h
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/command/data_source/adpcm.h"
+#include "audio_core/renderer/command/data_source/pcm_float.h"
+#include "audio_core/renderer/command/data_source/pcm_int16.h"
+#include "audio_core/renderer/command/effect/aux_.h"
+#include "audio_core/renderer/command/effect/biquad_filter.h"
+#include "audio_core/renderer/command/effect/capture.h"
+#include "audio_core/renderer/command/effect/compressor.h"
+#include "audio_core/renderer/command/effect/delay.h"
+#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
+#include "audio_core/renderer/command/effect/light_limiter.h"
+#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
+#include "audio_core/renderer/command/effect/reverb.h"
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/command/mix/clear_mix.h"
+#include "audio_core/renderer/command/mix/copy_mix.h"
+#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
+#include "audio_core/renderer/command/mix/depop_prepare.h"
+#include "audio_core/renderer/command/mix/mix.h"
+#include "audio_core/renderer/command/mix/mix_ramp.h"
+#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
+#include "audio_core/renderer/command/mix/volume.h"
+#include "audio_core/renderer/command/mix/volume_ramp.h"
+#include "audio_core/renderer/command/performance/performance.h"
+#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
+#include "audio_core/renderer/command/resample/upsample.h"
+#include "audio_core/renderer/command/sink/circular_buffer.h"
+#include "audio_core/renderer/command/sink/device.h"
diff --git a/src/audio_core/renderer/command/data_source/adpcm.cpp b/src/audio_core/renderer/command/data_source/adpcm.cpp
new file mode 100644
index 000000000..e66ed2990
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/adpcm.cpp
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <span>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/data_source/adpcm.h"
+#include "audio_core/renderer/command/data_source/decode.h"
+
+namespace AudioCore::AudioRenderer {
+
+void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample "
+ "rate {} target sample rate {} src quality {}\n",
+ output_index, sample_rate, processor.target_sample_rate, src_quality);
+}
+
+void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::Adpcm},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{0},
+ .channel_count{1},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{data_address},
+ .data_size{data_size},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample "
+ "rate {} target sample rate {} src quality {}\n",
+ output_index, sample_rate, processor.target_sample_rate, src_quality);
+}
+
+void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::Adpcm},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{0},
+ .channel_count{1},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{data_address},
+ .data_size{data_size},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/adpcm.h b/src/audio_core/renderer/command/data_source/adpcm.h
new file mode 100644
index 000000000..a9cf9cee4
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/adpcm.h
@@ -0,0 +1,119 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/common/common.h"
+#include "audio_core/common/wave_buffer.h"
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct AdpcmDataSourceVersion1Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+ /// Coefficients data address
+ CpuAddr data_address;
+ /// Coefficients data size
+ u64 data_size;
+};
+
+/**
+ * AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct AdpcmDataSourceVersion2Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Target channel to read within the wavebuffer
+ s8 channel_index;
+ /// Number of channels within the wavebuffer
+ s8 channel_count;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+ /// Coefficients data address
+ CpuAddr data_address;
+ /// Coefficients data size
+ u64 data_size;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/decode.cpp b/src/audio_core/renderer/command/data_source/decode.cpp
new file mode 100644
index 000000000..ff5d31bd6
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/decode.cpp
@@ -0,0 +1,428 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <vector>
+
+#include "audio_core/renderer/command/data_source/decode.h"
+#include "audio_core/renderer/command/resample/resample.h"
+#include "common/fixed_point.h"
+#include "common/logging/log.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer {
+
+constexpr u32 TempBufferSize = 0x3F00;
+constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
+
+/**
+ * Decode PCM data. Only s16 or f32 is supported.
+ *
+ * @tparam T - Type to decode. Only s16 and f32 are supported.
+ * @param memory - Core memory for reading samples.
+ * @param out_buffer - Output mix buffer to receive the samples.
+ * @param req - Information for how to decode.
+ * @return Number of samples decoded.
+ */
+template <typename T>
+static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
+ const DecodeArg& req) {
+ constexpr s32 min{std::numeric_limits<s16>::min()};
+ constexpr s32 max{std::numeric_limits<s16>::max()};
+
+ if (req.buffer == 0 || req.buffer_size == 0) {
+ return 0;
+ }
+
+ if (req.start_offset >= req.end_offset) {
+ return 0;
+ }
+
+ auto samples_to_decode{
+ std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)};
+ u32 channel_count{static_cast<u32>(req.channel_count)};
+
+ switch (req.channel_count) {
+ default: {
+ const VAddr source{req.buffer +
+ (((req.start_offset + req.offset) * channel_count) * sizeof(T))};
+ const u64 size{channel_count * samples_to_decode};
+ const u64 size_bytes{size * sizeof(T)};
+
+ std::vector<T> samples(size);
+ memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
+
+ if constexpr (std::is_floating_point_v<T>) {
+ for (u32 i = 0; i < samples_to_decode; i++) {
+ auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
+ std::numeric_limits<s16>::max())};
+ out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
+ }
+ } else {
+ for (u32 i = 0; i < samples_to_decode; i++) {
+ out_buffer[i] = samples[i * channel_count + req.target_channel];
+ }
+ }
+ } break;
+
+ case 1:
+ if (req.target_channel != 0) {
+ LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}",
+ req.target_channel);
+ return 0;
+ }
+
+ const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
+ std::vector<T> samples(samples_to_decode);
+ memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
+
+ if constexpr (std::is_floating_point_v<T>) {
+ for (u32 i = 0; i < samples_to_decode; i++) {
+ auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
+ std::numeric_limits<s16>::max())};
+ out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
+ }
+ } else {
+ std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16));
+ }
+ break;
+ }
+
+ return samples_to_decode;
+}
+
+/**
+ * Decode ADPCM data.
+ *
+ * @param memory - Core memory for reading samples.
+ * @param out_buffer - Output mix buffer to receive the samples.
+ * @param req - Information for how to decode.
+ * @return Number of samples decoded.
+ */
+static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
+ const DecodeArg& req) {
+ constexpr u32 SamplesPerFrame{14};
+ constexpr u32 NibblesPerFrame{16};
+
+ if (req.buffer == 0 || req.buffer_size == 0) {
+ return 0;
+ }
+
+ if (req.end_offset < req.start_offset) {
+ return 0;
+ }
+
+ auto end{(req.end_offset % SamplesPerFrame) +
+ NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
+ if (req.end_offset % SamplesPerFrame) {
+ end += 3;
+ } else {
+ end += 1;
+ }
+
+ if (req.buffer_size < end / 2) {
+ return 0;
+ }
+
+ auto samples_to_process{
+ std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
+
+ auto samples_to_read{samples_to_process};
+ auto start_pos{req.start_offset + req.offset};
+ auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
+ auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
+ samples_remaining_in_frame};
+
+ if (samples_remaining_in_frame) {
+ position_in_frame += 2;
+ }
+
+ const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
+ std::vector<u8> wavebuffer(size);
+ memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
+ wavebuffer.size());
+
+ auto context{req.adpcm_context};
+ auto header{context->header};
+ u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
+ u8 scale{static_cast<u8>(header & 0xFU)};
+ s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
+ s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
+
+ auto yn0{context->yn0};
+ auto yn1{context->yn1};
+
+ static constexpr std::array<s32, 16> Steps{
+ 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
+ };
+
+ const auto decode_sample = [&](const s32 code) -> s16 {
+ const auto xn = code * (1 << scale);
+ const auto prediction = coeff0 * yn0 + coeff1 * yn1;
+ const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
+ const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
+ yn1 = yn0;
+ yn0 = static_cast<s16>(saturated);
+ return yn0;
+ };
+
+ u32 read_index{0};
+ u32 write_index{0};
+
+ while (samples_to_read > 0) {
+ // Are we at a new frame?
+ if ((position_in_frame % NibblesPerFrame) == 0) {
+ header = wavebuffer[read_index++];
+ coeff_index = (header >> 4) & 0xF;
+ scale = header & 0xF;
+ coeff0 = req.coefficients[coeff_index * 2 + 0];
+ coeff1 = req.coefficients[coeff_index * 2 + 1];
+ position_in_frame += 2;
+
+ // Can we consume all of this frame's samples?
+ if (samples_to_read >= SamplesPerFrame) {
+ // Can grab all samples until the next header
+ for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
+ auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
+ auto code1{Steps[wavebuffer[read_index] & 0xF]};
+ read_index++;
+
+ out_buffer[write_index++] = decode_sample(code0);
+ out_buffer[write_index++] = decode_sample(code1);
+ }
+
+ position_in_frame += SamplesPerFrame;
+ samples_to_read -= SamplesPerFrame;
+ continue;
+ }
+ }
+
+ // Decode a single sample
+ auto code{wavebuffer[read_index]};
+ if (position_in_frame & 1) {
+ code &= 0xF;
+ read_index++;
+ } else {
+ code >>= 4;
+ }
+
+ out_buffer[write_index++] = decode_sample(Steps[code]);
+
+ position_in_frame++;
+ samples_to_read--;
+ }
+
+ context->header = header;
+ context->yn0 = yn0;
+ context->yn1 = yn1;
+
+ return samples_to_process;
+}
+
+/**
+ * Decode implementation.
+ * Decode wavebuffers according to the given args.
+ *
+ * @param memory - Core memory to read data from.
+ * @param args - The wavebuffer data, and information for how to decode it.
+ */
+void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
+ auto& voice_state{*args.voice_state};
+ auto remaining_sample_count{args.sample_count};
+ auto fraction{voice_state.fraction};
+
+ const auto sample_rate_ratio{
+ (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
+ args.pitch};
+ const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
+
+ if (size_required < 0) {
+ return;
+ }
+
+ auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
+ if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
+ return;
+ }
+
+ auto max_remaining_sample_count{
+ ((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio)
+ .to_uint_floor()};
+ max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count);
+
+ auto wavebuffers_consumed{voice_state.wave_buffers_consumed};
+ auto wavebuffer_index{voice_state.wave_buffer_index};
+ auto played_sample_count{voice_state.played_sample_count};
+
+ bool is_buffer_starved{false};
+ u32 offset{voice_state.offset};
+
+ auto output_buffer{args.output};
+ std::vector<s16> temp_buffer(TempBufferSize, 0);
+
+ while (remaining_sample_count > 0) {
+ const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};
+ const auto samples_to_read{
+ (fraction + samples_to_write * sample_rate_ratio).to_uint_floor()};
+
+ u32 temp_buffer_pos{0};
+
+ if (!args.IsVoicePitchAndSrcSkippedSupported) {
+ for (u32 i = 0; i < pitch; i++) {
+ temp_buffer[i] = voice_state.sample_history[i];
+ }
+ temp_buffer_pos = pitch;
+ }
+
+ u32 samples_read{0};
+ while (samples_read < samples_to_read) {
+ if (wavebuffer_index >= MaxWaveBuffers) {
+ LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
+ wavebuffer_index = 0;
+ voice_state.wave_buffer_valid.fill(false);
+ wavebuffers_consumed = MaxWaveBuffers;
+ }
+
+ if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
+ is_buffer_starved = true;
+ break;
+ }
+
+ auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
+
+ if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
+ wavebuffer.context != 0) {
+ memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
+ wavebuffer.context_size);
+ }
+
+ auto start_offset{wavebuffer.start_offset};
+ auto end_offset{wavebuffer.end_offset};
+
+ if (wavebuffer.loop && voice_state.loop_count > 0 &&
+ wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
+ wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
+ start_offset = wavebuffer.loop_start_offset;
+ end_offset = wavebuffer.loop_end_offset;
+ }
+
+ DecodeArg decode_arg{.buffer{wavebuffer.buffer},
+ .buffer_size{wavebuffer.buffer_size},
+ .start_offset{start_offset},
+ .end_offset{end_offset},
+ .channel_count{args.channel_count},
+ .coefficients{},
+ .adpcm_context{nullptr},
+ .target_channel{args.channel},
+ .offset{offset},
+ .samples_to_read{samples_to_read - samples_read}};
+
+ s32 samples_decoded{0};
+
+ switch (args.sample_format) {
+ case SampleFormat::PcmInt16:
+ samples_decoded = DecodePcm<s16>(
+ memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
+ decode_arg);
+ break;
+
+ case SampleFormat::PcmFloat:
+ samples_decoded = DecodePcm<f32>(
+ memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
+ decode_arg);
+ break;
+
+ case SampleFormat::Adpcm: {
+ decode_arg.adpcm_context = &voice_state.adpcm_context;
+ memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
+ samples_decoded = DecodeAdpcm(
+ memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
+ decode_arg);
+ } break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sample format to decode {}",
+ static_cast<u32>(args.sample_format));
+ samples_decoded = 0;
+ break;
+ }
+
+ played_sample_count += samples_decoded;
+ samples_read += samples_decoded;
+ temp_buffer_pos += samples_decoded;
+ offset += samples_decoded;
+
+ if (samples_decoded == 0 || offset >= end_offset - start_offset) {
+ offset = 0;
+ if (!wavebuffer.loop) {
+ voice_state.wave_buffer_valid[wavebuffer_index] = false;
+ voice_state.loop_count = 0;
+
+ if (wavebuffer.stream_ended) {
+ played_sample_count = 0;
+ }
+
+ wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
+ wavebuffers_consumed++;
+ } else {
+ voice_state.loop_count++;
+ if (wavebuffer.loop_count > 0 &&
+ (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
+ voice_state.wave_buffer_valid[wavebuffer_index] = false;
+ voice_state.loop_count = 0;
+
+ if (wavebuffer.stream_ended) {
+ played_sample_count = 0;
+ }
+
+ wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
+ wavebuffers_consumed++;
+ }
+
+ if (samples_decoded == 0) {
+ is_buffer_starved = true;
+ break;
+ }
+
+ if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
+ played_sample_count = 0;
+ }
+ }
+ }
+ }
+
+ if (args.IsVoicePitchAndSrcSkippedSupported) {
+ if (samples_read > output_buffer.size()) {
+ LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!");
+ }
+ for (u32 i = 0; i < samples_read; i++) {
+ output_buffer[i] = temp_buffer[i];
+ }
+ } else {
+ std::memset(&temp_buffer[temp_buffer_pos], 0,
+ (samples_to_read - samples_read) * sizeof(s16));
+
+ Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write,
+ args.src_quality);
+
+ std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read],
+ pitch * sizeof(s16));
+ }
+
+ remaining_sample_count -= samples_to_write;
+ if (remaining_sample_count != 0 && is_buffer_starved) {
+ LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??");
+ break;
+ }
+
+ output_buffer = output_buffer.subspan(samples_to_write);
+ }
+
+ voice_state.wave_buffers_consumed = wavebuffers_consumed;
+ voice_state.played_sample_count = played_sample_count;
+ voice_state.wave_buffer_index = wavebuffer_index;
+ voice_state.offset = offset;
+ voice_state.fraction = fraction;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/decode.h b/src/audio_core/renderer/command/data_source/decode.h
new file mode 100644
index 000000000..4d63d6fa8
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/decode.h
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "audio_core/common/wave_buffer.h"
+#include "audio_core/renderer/voice/voice_state.h"
+#include "common/common_types.h"
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace AudioCore::AudioRenderer {
+
+struct DecodeFromWaveBuffersArgs {
+ SampleFormat sample_format;
+ std::span<s32> output;
+ VoiceState* voice_state;
+ std::span<WaveBufferVersion2> wave_buffers;
+ s8 channel;
+ s8 channel_count;
+ SrcQuality src_quality;
+ f32 pitch;
+ u32 source_sample_rate;
+ u32 target_sample_rate;
+ u32 sample_count;
+ CpuAddr data_address;
+ u64 data_size;
+ bool IsVoicePlayedSampleCountResetAtLoopPointSupported;
+ bool IsVoicePitchAndSrcSkippedSupported;
+};
+
+struct DecodeArg {
+ CpuAddr buffer;
+ u64 buffer_size;
+ u32 start_offset;
+ u32 end_offset;
+ s8 channel_count;
+ std::array<s16, 16> coefficients;
+ VoiceState::AdpcmContext* adpcm_context;
+ s8 target_channel;
+ u32 offset;
+ u32 samples_to_read;
+};
+
+/**
+ * Decode wavebuffers according to the given args.
+ *
+ * @param memory - Core memory to read data from.
+ * @param args - The wavebuffer data, and information for how to decode it.
+ */
+void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args);
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_float.cpp b/src/audio_core/renderer/command/data_source/pcm_float.cpp
new file mode 100644
index 000000000..be77fab69
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_float.cpp
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/data_source/decode.h"
+#include "audio_core/renderer/command/data_source/pcm_float.h"
+
+namespace AudioCore::AudioRenderer {
+
+void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string +=
+ fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} "
+ "channel count {} source sample rate {} target sample rate {} src quality {}\n",
+ output_index, channel_index, channel_count, sample_rate,
+ processor.target_sample_rate, src_quality);
+}
+
+void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count);
+
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::PcmFloat},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{channel_index},
+ .channel_count{channel_count},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{0},
+ .data_size{0},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string +=
+ fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} "
+ "channel count {} source sample rate {} target sample rate {} src quality {}\n",
+ output_index, channel_index, channel_count, sample_rate,
+ processor.target_sample_rate, src_quality);
+}
+
+void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count);
+
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::PcmFloat},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{channel_index},
+ .channel_count{channel_count},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{0},
+ .data_size{0},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_float.h b/src/audio_core/renderer/command/data_source/pcm_float.h
new file mode 100644
index 000000000..e4af77c20
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_float.h
@@ -0,0 +1,113 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/common/wave_buffer.h"
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct PcmFloatDataSourceVersion1Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Target channel to read within the wavebuffer
+ s8 channel_index;
+ /// Number of channels within the wavebuffer
+ s8 channel_count;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+};
+
+/**
+ * AudioRenderer command to decode PCM float-encoded version 2 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct PcmFloatDataSourceVersion2Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Target channel to read within the wavebuffer
+ s8 channel_index;
+ /// Number of channels within the wavebuffer
+ s8 channel_count;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.cpp b/src/audio_core/renderer/command/data_source/pcm_int16.cpp
new file mode 100644
index 000000000..7a27463e4
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_int16.cpp
@@ -0,0 +1,87 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <span>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/data_source/decode.h"
+#include "audio_core/renderer/command/data_source/pcm_int16.h"
+
+namespace AudioCore::AudioRenderer {
+
+void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string +=
+ fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} "
+ "channel count {} source sample rate {} target sample rate {} src quality {}\n",
+ output_index, channel_index, channel_count, sample_rate,
+ processor.target_sample_rate, src_quality);
+}
+
+void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count);
+
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::PcmInt16},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{channel_index},
+ .channel_count{channel_count},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{0},
+ .data_size{0},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string +=
+ fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} "
+ "channel count {} source sample rate {} target sample rate {} src quality {}\n",
+ output_index, channel_index, channel_count, sample_rate,
+ processor.target_sample_rate, src_quality);
+}
+
+void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+ auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count);
+ DecodeFromWaveBuffersArgs args{
+ .sample_format{SampleFormat::PcmInt16},
+ .output{out_buffer},
+ .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
+ .wave_buffers{wave_buffers},
+ .channel{channel_index},
+ .channel_count{channel_count},
+ .src_quality{src_quality},
+ .pitch{pitch},
+ .source_sample_rate{sample_rate},
+ .target_sample_rate{processor.target_sample_rate},
+ .sample_count{processor.sample_count},
+ .data_address{0},
+ .data_size{0},
+ .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
+ .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
+ };
+
+ DecodeFromWaveBuffers(*processor.memory, args);
+}
+
+bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.h b/src/audio_core/renderer/command/data_source/pcm_int16.h
new file mode 100644
index 000000000..5de1ad60d
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_int16.h
@@ -0,0 +1,110 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/common/wave_buffer.h"
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct PcmInt16DataSourceVersion1Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Target channel to read within the wavebuffer
+ s8 channel_index;
+ /// Number of channels within the wavebuffer
+ s8 channel_count;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+};
+
+/**
+ * AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers
+ * into the output_index mix buffer.
+ */
+struct PcmInt16DataSourceVersion2Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Quality used for sample rate conversion
+ SrcQuality src_quality;
+ /// Mix buffer index for decoded samples
+ s16 output_index;
+ /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
+ u16 flags;
+ /// Wavebuffer sample rate
+ u32 sample_rate;
+ /// Pitch used for sample rate conversion
+ f32 pitch;
+ /// Target channel to read within the wavebuffer
+ s8 channel_index;
+ /// Number of channels within the wavebuffer
+ s8 channel_count;
+ /// Wavebuffers containing the wavebuffer address, context address, looping information etc
+ std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
+ /// Voice state, updated each call and written back to game
+ CpuAddr voice_state;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/aux_.cpp b/src/audio_core/renderer/command/effect/aux_.cpp
new file mode 100644
index 000000000..e76db893f
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/aux_.cpp
@@ -0,0 +1,207 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/aux_.h"
+#include "audio_core/renderer/effect/aux_.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Reset an AuxBuffer.
+ *
+ * @param memory - Core memory for writing.
+ * @param aux_info - Memory address pointing to the AuxInfo to reset.
+ */
+static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
+ if (aux_info == 0) {
+ LOG_ERROR(Service_Audio, "Aux info is 0!");
+ return;
+ }
+
+ auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))};
+ info->read_offset = 0;
+ info->write_offset = 0;
+ info->total_sample_count = 0;
+}
+
+/**
+ * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
+ * update_count is set, to notify the game that an update happened.
+ *
+ * @param memory - Core memory for writing.
+ * @param send_info_ - Meta information for where to write the mix buffer.
+ * @param sample_count - Unused.
+ * @param send_buffer - Memory address to write the mix buffer to.
+ * @param count_max - Maximum number of samples in the receiving buffer.
+ * @param input - Input mix buffer to write.
+ * @param write_count_ - Number of samples to write.
+ * @param write_offset - Current offset to begin writing the receiving buffer at.
+ * @param update_count - If non-zero, send_info_ will be updated.
+ * @return Number of samples written.
+ */
+static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
+ [[maybe_unused]] u32 sample_count, const CpuAddr send_buffer,
+ const u32 count_max, std::span<const s32> input,
+ const u32 write_count_, const u32 write_offset,
+ const u32 update_count) {
+ if (write_count_ > count_max) {
+ LOG_ERROR(Service_Audio,
+ "write_count must be smaller than count_max! write_count {}, count_max {}",
+ write_count_, count_max);
+ return 0;
+ }
+
+ if (input.empty()) {
+ LOG_ERROR(Service_Audio, "input buffer is empty!");
+ return 0;
+ }
+
+ if (send_buffer == 0) {
+ LOG_ERROR(Service_Audio, "send_buffer is 0!");
+ return 0;
+ }
+
+ if (count_max == 0) {
+ return 0;
+ }
+
+ AuxInfo::AuxInfoDsp send_info{};
+ memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
+
+ u32 target_write_offset{send_info.write_offset + write_offset};
+ if (target_write_offset > count_max || write_count_ == 0) {
+ return 0;
+ }
+
+ u32 write_count{write_count_};
+ u32 write_pos{0};
+ while (write_count > 0) {
+ u32 to_write{std::min(count_max - target_write_offset, write_count)};
+
+ if (to_write > 0) {
+ memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
+ &input[write_pos], to_write * sizeof(s32));
+ }
+
+ target_write_offset = (target_write_offset + to_write) % count_max;
+ write_count -= to_write;
+ write_pos += to_write;
+ }
+
+ if (update_count) {
+ send_info.write_offset = (send_info.write_offset + update_count) % count_max;
+ }
+
+ memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
+
+ return write_count_;
+}
+
+/**
+ * Read the given memory at return_buffer into the output mix buffer, and update return_info_ if
+ * update_count is set, to notify the game that an update happened.
+ *
+ * @param memory - Core memory for writing.
+ * @param return_info_ - Meta information for where to read the mix buffer.
+ * @param return_buffer - Memory address to read the samples from.
+ * @param count_max - Maximum number of samples in the receiving buffer.
+ * @param output - Output mix buffer which will receive the samples.
+ * @param count_ - Number of samples to read.
+ * @param read_offset - Current offset to begin reading the return_buffer at.
+ * @param update_count - If non-zero, send_info_ will be updated.
+ * @return Number of samples read.
+ */
+static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_,
+ const CpuAddr return_buffer, const u32 count_max, std::span<s32> output,
+ const u32 count_, const u32 read_offset, const u32 update_count) {
+ if (count_max == 0) {
+ return 0;
+ }
+
+ if (count_ > count_max) {
+ LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}",
+ count_, count_max);
+ return 0;
+ }
+
+ if (output.empty()) {
+ LOG_ERROR(Service_Audio, "output buffer is empty!");
+ return 0;
+ }
+
+ if (return_buffer == 0) {
+ LOG_ERROR(Service_Audio, "return_buffer is 0!");
+ return 0;
+ }
+
+ AuxInfo::AuxInfoDsp return_info{};
+ memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
+
+ u32 target_read_offset{return_info.read_offset + read_offset};
+ if (target_read_offset > count_max) {
+ return 0;
+ }
+
+ u32 read_count{count_};
+ u32 read_pos{0};
+ while (read_count > 0) {
+ u32 to_read{std::min(count_max - target_read_offset, read_count)};
+
+ if (to_read > 0) {
+ memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32),
+ &output[read_pos], to_read * sizeof(s32));
+ }
+
+ target_read_offset = (target_read_offset + to_read) % count_max;
+ read_count -= to_read;
+ read_pos += to_read;
+ }
+
+ if (update_count) {
+ return_info.read_offset = (return_info.read_offset + update_count) % count_max;
+ }
+
+ memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
+
+ return count_;
+}
+
+void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled,
+ input, output);
+}
+
+void AuxCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto input_buffer{
+ processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
+ auto output_buffer{
+ processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
+
+ if (effect_enabled) {
+ WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer,
+ count_max, input_buffer, processor.sample_count, write_offset,
+ update_count);
+
+ auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max,
+ output_buffer, processor.sample_count, write_offset,
+ update_count)};
+
+ if (read != processor.sample_count) {
+ std::memset(&output_buffer[read], 0, processor.sample_count - read);
+ }
+ } else {
+ ResetAuxBufferDsp(*processor.memory, send_buffer_info);
+ ResetAuxBufferDsp(*processor.memory, return_buffer_info);
+ if (input != output) {
+ std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes());
+ }
+ }
+}
+
+bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/aux_.h b/src/audio_core/renderer/command/effect/aux_.h
new file mode 100644
index 000000000..825c93732
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/aux_.h
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game
+ * memory, and reading into the output buffer from game memory.
+ */
+struct AuxCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer index
+ s16 input;
+ /// Output mix buffer index
+ s16 output;
+ /// Meta info for writing
+ CpuAddr send_buffer_info;
+ /// Meta info for reading
+ CpuAddr return_buffer_info;
+ /// Game memory write buffer
+ CpuAddr send_buffer;
+ /// Game memory read buffer
+ CpuAddr return_buffer;
+ /// Max samples to read/write
+ u32 count_max;
+ /// Current read/write offset
+ u32 write_offset;
+ /// Number of samples to update per call
+ u32 update_count;
+ /// is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp
new file mode 100644
index 000000000..1baae74fd
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp
@@ -0,0 +1,118 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/biquad_filter.h"
+#include "audio_core/renderer/voice/voice_state.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Biquad filter float implementation.
+ *
+ * @param output - Output container for filtered samples.
+ * @param input - Input container for samples to be filtered.
+ * @param b - Feedforward coefficients.
+ * @param a - Feedback coefficients.
+ * @param state - State to track previous samples between calls.
+ * @param sample_count - Number of samples to process.
+ */
+void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
+ std::array<s16, 3>& b_, std::array<s16, 2>& a_,
+ VoiceState::BiquadFilterState& state, const u32 sample_count) {
+ constexpr s64 min{std::numeric_limits<s32>::min()};
+ constexpr s64 max{std::numeric_limits<s32>::max()};
+ std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(),
+ Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(),
+ Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()};
+ std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(),
+ Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()};
+ std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(),
+ state.s3.to_double()};
+
+ for (u32 i = 0; i < sample_count; i++) {
+ f64 in_sample{static_cast<f64>(input[i])};
+ auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]};
+
+ output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max));
+
+ s[1] = s[0];
+ s[0] = in_sample;
+ s[3] = s[2];
+ s[2] = sample;
+ }
+
+ state.s0 = s[0];
+ state.s1 = s[1];
+ state.s2 = s[2];
+ state.s3 = s[3];
+}
+
+/**
+ * Biquad filter s32 implementation.
+ *
+ * @param output - Output container for filtered samples.
+ * @param input - Input container for samples to be filtered.
+ * @param b - Feedforward coefficients.
+ * @param a - Feedback coefficients.
+ * @param state - State to track previous samples between calls.
+ * @param sample_count - Number of samples to process.
+ */
+static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input,
+ std::array<s16, 3>& b_, std::array<s16, 2>& a_,
+ VoiceState::BiquadFilterState& state, const u32 sample_count) {
+ constexpr s64 min{std::numeric_limits<s32>::min()};
+ constexpr s64 max{std::numeric_limits<s32>::max()};
+ std::array<Common::FixedPoint<50, 14>, 3> b{
+ Common::FixedPoint<50, 14>::from_base(b_[0]),
+ Common::FixedPoint<50, 14>::from_base(b_[1]),
+ Common::FixedPoint<50, 14>::from_base(b_[2]),
+ };
+ std::array<Common::FixedPoint<50, 14>, 3> a{
+ Common::FixedPoint<50, 14>::from_base(a_[0]),
+ Common::FixedPoint<50, 14>::from_base(a_[1]),
+ };
+
+ for (u32 i = 0; i < sample_count; i++) {
+ s64 in_sample{input[i]};
+ auto sample{in_sample * b[0] + state.s0};
+ const auto out_sample{std::clamp(sample.to_long(), min, max)};
+
+ output[i] = static_cast<s32>(out_sample);
+
+ state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample;
+ state.s1 = 0 + b[2] * in_sample + a[1] * out_sample;
+ }
+}
+
+void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format(
+ "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n",
+ input, output, needs_init, use_float_processing);
+}
+
+void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)};
+ if (needs_init) {
+ std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState));
+ }
+
+ auto input_buffer{
+ processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
+ auto output_buffer{
+ processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
+
+ if (use_float_processing) {
+ ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
+ processor.sample_count);
+ } else {
+ ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
+ processor.sample_count);
+ }
+}
+
+bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/biquad_filter.h b/src/audio_core/renderer/command/effect/biquad_filter.h
new file mode 100644
index 000000000..4c9c42d29
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/biquad_filter.h
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "audio_core/renderer/voice/voice_state.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to
+ * the output mix buffer.
+ */
+struct BiquadFilterCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer index
+ s16 input;
+ /// Output mix buffer index
+ s16 output;
+ /// Input parameters for biquad
+ VoiceInfo::BiquadFilterParameter biquad;
+ /// Biquad state, updated each call
+ CpuAddr state;
+ /// If true, reset the state
+ bool needs_init;
+ /// If true, use float processing rather than int
+ bool use_float_processing;
+};
+
+/**
+ * Biquad filter float implementation.
+ *
+ * @param output - Output container for filtered samples.
+ * @param input - Input container for samples to be filtered.
+ * @param b - Feedforward coefficients.
+ * @param a - Feedback coefficients.
+ * @param state - State to track previous samples.
+ * @param sample_count - Number of samples to process.
+ */
+void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
+ std::array<s16, 3>& b, std::array<s16, 2>& a,
+ VoiceState::BiquadFilterState& state, const u32 sample_count);
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/capture.cpp b/src/audio_core/renderer/command/effect/capture.cpp
new file mode 100644
index 000000000..042fd286e
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/capture.cpp
@@ -0,0 +1,142 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/capture.h"
+#include "audio_core/renderer/effect/aux_.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Reset an AuxBuffer.
+ *
+ * @param memory - Core memory for writing.
+ * @param aux_info - Memory address pointing to the AuxInfo to reset.
+ */
+static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
+ if (aux_info == 0) {
+ LOG_ERROR(Service_Audio, "Aux info is 0!");
+ return;
+ }
+
+ memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0);
+ memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0);
+ memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0);
+}
+
+/**
+ * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
+ * update_count is set, to notify the game that an update happened.
+ *
+ * @param memory - Core memory for writing.
+ * @param send_info_ - Header information for where to write the mix buffer.
+ * @param send_buffer - Memory address to write the mix buffer to.
+ * @param count_max - Maximum number of samples in the receiving buffer.
+ * @param input - Input mix buffer to write.
+ * @param write_count_ - Number of samples to write.
+ * @param write_offset - Current offset to begin writing the receiving buffer at.
+ * @param update_count - If non-zero, send_info_ will be updated.
+ * @return Number of samples written.
+ */
+static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
+ const CpuAddr send_buffer, u32 count_max, std::span<const s32> input,
+ const u32 write_count_, const u32 write_offset,
+ const u32 update_count) {
+ if (write_count_ > count_max) {
+ LOG_ERROR(Service_Audio,
+ "write_count must be smaller than count_max! write_count {}, count_max {}",
+ write_count_, count_max);
+ return 0;
+ }
+
+ if (send_info_ == 0) {
+ LOG_ERROR(Service_Audio, "send_info is 0!");
+ return 0;
+ }
+
+ if (input.empty()) {
+ LOG_ERROR(Service_Audio, "input buffer is empty!");
+ return 0;
+ }
+
+ if (send_buffer == 0) {
+ LOG_ERROR(Service_Audio, "send_buffer is 0!");
+ return 0;
+ }
+
+ if (count_max == 0) {
+ return 0;
+ }
+
+ AuxInfo::AuxBufferInfo send_info{};
+ memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
+
+ u32 target_write_offset{send_info.dsp_info.write_offset + write_offset};
+ if (target_write_offset > count_max || write_count_ == 0) {
+ return 0;
+ }
+
+ u32 write_count{write_count_};
+ u32 write_pos{0};
+ while (write_count > 0) {
+ u32 to_write{std::min(count_max - target_write_offset, write_count)};
+
+ if (to_write > 0) {
+ memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
+ &input[write_pos], to_write * sizeof(s32));
+ }
+
+ target_write_offset = (target_write_offset + to_write) % count_max;
+ write_count -= to_write;
+ write_pos += to_write;
+ }
+
+ if (update_count) {
+ const auto count_diff{send_info.dsp_info.total_sample_count -
+ send_info.cpu_info.total_sample_count};
+ if (count_diff >= count_max) {
+ auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count};
+ if (dsp_lost_count - send_info.cpu_info.lost_sample_count <
+ send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) {
+ dsp_lost_count = send_info.cpu_info.lost_sample_count - 1;
+ }
+ send_info.dsp_info.lost_sample_count = dsp_lost_count;
+ }
+
+ send_info.dsp_info.write_offset =
+ (send_info.dsp_info.write_offset + update_count + count_max) % count_max;
+
+ auto new_sample_count{send_info.dsp_info.total_sample_count + update_count};
+ if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) {
+ new_sample_count = send_info.cpu_info.total_sample_count - 1;
+ }
+ send_info.dsp_info.total_sample_count = new_sample_count;
+ }
+
+ memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
+
+ return write_count_;
+}
+
+void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled,
+ input, output);
+}
+
+void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) {
+ if (effect_enabled) {
+ auto input_buffer{
+ processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
+ WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer,
+ processor.sample_count, write_offset, update_count);
+ } else {
+ ResetAuxBufferDsp(*processor.memory, send_buffer_info);
+ }
+}
+
+bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/capture.h b/src/audio_core/renderer/command/effect/capture.h
new file mode 100644
index 000000000..8670acb24
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/capture.h
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory
+ * address.
+ */
+struct CaptureCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer index
+ s16 input;
+ /// Output mix buffer index
+ s16 output;
+ /// Meta info for writing
+ CpuAddr send_buffer_info;
+ /// Game memory write buffer
+ CpuAddr send_buffer;
+ /// Max samples to read/write
+ u32 count_max;
+ /// Current read/write offset
+ u32 write_offset;
+ /// Number of samples to update per call
+ u32 update_count;
+ /// is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp
new file mode 100644
index 000000000..2ebc140f1
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/compressor.cpp
@@ -0,0 +1,156 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <cmath>
+#include <span>
+#include <vector>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/compressor.h"
+#include "audio_core/renderer/effect/compressor.h"
+
+namespace AudioCore::AudioRenderer {
+
+static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params,
+ CompressorInfo::State& state) {
+ const auto ratio{1.0f / params.compressor_ratio};
+ auto makeup_gain{0.0f};
+ if (params.makeup_gain_enabled) {
+ makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f;
+ }
+ state.makeup_gain = makeup_gain;
+ state.unk_18 = params.unk_28;
+
+ const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f};
+ const auto b{(a - std::trunc(a)) * 0.69315f};
+ const auto c{std::pow(2.0f, b)};
+
+ state.unk_0C = (1.0f - ratio) / 6.0f;
+ state.unk_14 = params.threshold + 1.5f;
+ state.unk_10 = params.threshold - 1.5f;
+ state.unk_20 = c;
+}
+
+static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params,
+ CompressorInfo::State& state) {
+ std::memset(&state, 0, sizeof(CompressorInfo::State));
+
+ state.unk_00 = 0;
+ state.unk_04 = 1.0f;
+ state.unk_08 = 1.0f;
+
+ SetCompressorEffectParameter(params, state);
+}
+
+static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params,
+ CompressorInfo::State& state, bool enabled,
+ std::vector<std::span<const s32>> input_buffers,
+ std::vector<std::span<s32>> output_buffers, u32 sample_count) {
+ if (enabled) {
+ auto state_00{state.unk_00};
+ auto state_04{state.unk_04};
+ auto state_08{state.unk_08};
+ auto state_18{state.unk_18};
+
+ for (u32 i = 0; i < sample_count; i++) {
+ auto a{0.0f};
+ for (s16 channel = 0; channel < params.channel_count; channel++) {
+ const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])};
+ a += (input_sample * input_sample).to_float();
+ }
+
+ state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00);
+
+ auto b{-100.0f};
+ auto c{0.0f};
+ if (state_00 >= 1.0e-10) {
+ b = std::log10(state_00) * 10.0f;
+ c = 1.0f;
+ }
+
+ if (b >= state.unk_10) {
+ const auto d{b >= state.unk_14
+ ? ((1.0f / params.compressor_ratio) - 1.0f) *
+ (b - params.threshold)
+ : (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C};
+ const auto e{d / 20.0f * 3.3219f};
+ const auto f{(e - std::trunc(e)) * 0.69315f};
+ c = std::pow(2.0f, f);
+ }
+
+ state_18 = params.unk_28;
+ auto tmp{c};
+ if ((state_04 - c) <= 0.08f) {
+ state_18 = params.unk_2C;
+ if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) {
+ tmp = state_04;
+ }
+ }
+
+ state_04 = tmp;
+ state_08 += (c - state_08) * state_18;
+
+ for (s16 channel = 0; channel < params.channel_count; channel++) {
+ output_buffers[channel][i] = static_cast<s32>(
+ static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20);
+ }
+ }
+
+ state.unk_00 = state_00;
+ state.unk_04 = state_04;
+ state.unk_08 = state_08;
+ state.unk_18 = state_18;
+ } else {
+ for (s16 channel = 0; channel < params.channel_count; channel++) {
+ if (params.inputs[channel] != params.outputs[channel]) {
+ std::memcpy((char*)output_buffers[channel].data(),
+ (char*)input_buffers[channel].data(),
+ output_buffers[channel].size_bytes());
+ }
+ }
+ }
+}
+
+void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
+ for (s16 i = 0; i < parameter.channel_count; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (s16 i = 0; i < parameter.channel_count; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (s16 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<CompressorInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == CompressorInfo::ParameterState::Updating) {
+ SetCompressorEffectParameter(parameter, *state_);
+ } else if (parameter.state == CompressorInfo::ParameterState::Initialized) {
+ InitializeCompressorEffect(parameter, *state_);
+ }
+ }
+
+ ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count);
+}
+
+bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/compressor.h b/src/audio_core/renderer/command/effect/compressor.h
new file mode 100644
index 000000000..f8e96cb43
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/compressor.h
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/effect/compressor.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for limiting volume between a high and low threshold.
+ * Version 1.
+ */
+struct CompressorCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ CompressorInfo::ParameterVersion2 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/delay.cpp b/src/audio_core/renderer/command/effect/delay.cpp
new file mode 100644
index 000000000..a4e408d40
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/delay.cpp
@@ -0,0 +1,238 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/delay.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Update the DelayInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ */
+static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params,
+ DelayInfo::State& state) {
+ auto channel_spread{params.channel_spread};
+ state.feedback_gain = params.feedback_gain * 0.97998046875f;
+ state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread);
+ if (params.channel_count == 4 || params.channel_count == 6) {
+ channel_spread >>= 1;
+ }
+ state.delay_feedback_cross_gain = channel_spread * state.feedback_gain;
+ state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f;
+ state.lowpass_gain = 1.0f - state.lowpass_feedback_gain;
+}
+
+/**
+ * Initialize a new DelayInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ * @param workbuffer - Game-supplied memory for the state. (Unused)
+ */
+static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
+ DelayInfo::State& state,
+ [[maybe_unused]] const CpuAddr workbuffer) {
+ state = {};
+
+ for (u32 channel = 0; channel < params.channel_count; channel++) {
+ Common::FixedPoint<32, 32> sample_count_max{0.064f};
+ sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max;
+
+ Common::FixedPoint<18, 14> delay_time{params.delay_time};
+ delay_time *= params.sample_rate / 1000;
+ Common::FixedPoint<32, 32> sample_count{delay_time};
+
+ if (sample_count > sample_count_max) {
+ sample_count = sample_count_max;
+ }
+
+ state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor();
+ state.delay_lines[channel].sample_count = sample_count.to_int_floor();
+ state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0);
+ if (state.delay_lines[channel].buffer.size() == 0) {
+ state.delay_lines[channel].buffer.push_back(0);
+ }
+ state.delay_lines[channel].buffer_pos = 0;
+ state.delay_lines[channel].decay_rate = 1.0f;
+ }
+
+ SetDelayEffectParameter(params, state);
+}
+
+/**
+ * Delay effect impl, according to the parameters and current state, on the input mix buffers,
+ * saving the results to the output mix buffers.
+ *
+ * @tparam NumChannels - Number of channels to process. 1-6.
+ * @param params - Input parameters to use.
+ * @param state - State to use, must be initialized (see InitializeDelayEffect).
+ * @param inputs - Input mix buffers to performan the delay on.
+ * @param outputs - Output mix buffers to receive the delayed samples.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t NumChannels>
+static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
+ std::vector<std::span<const s32>>& inputs,
+ std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+ for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
+ std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{};
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ input_samples[channel] = inputs[channel][sample_index] * 64;
+ }
+
+ std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{};
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ delay_samples[channel] = state.delay_lines[channel].Read();
+ }
+
+ // clang-format off
+ std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{};
+ if constexpr (NumChannels == 1) {
+ matrix = {{
+ {state.feedback_gain},
+ }};
+ } else if constexpr (NumChannels == 2) {
+ matrix = {{
+ {state.delay_feedback_gain, state.delay_feedback_cross_gain},
+ {state.delay_feedback_cross_gain, state.delay_feedback_gain},
+ }};
+ } else if constexpr (NumChannels == 4) {
+ matrix = {{
+ {state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f},
+ {state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain},
+ {state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
+ {0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain},
+ }};
+ } else if constexpr (NumChannels == 6) {
+ matrix = {{
+ {state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f},
+ {0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain},
+ {state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f},
+ {0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f},
+ {state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
+ {0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain},
+ }};
+ }
+ // clang-format on
+
+ std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{};
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ Common::FixedPoint<50, 14> delay{};
+ for (u32 j = 0; j < NumChannels; j++) {
+ delay += delay_samples[j] * matrix[j][channel];
+ }
+ gained_samples[channel] = input_samples[channel] * params.in_gain + delay;
+ }
+
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain +
+ state.lowpass_z[channel] * state.lowpass_feedback_gain;
+ state.delay_lines[channel].Write(state.lowpass_z[channel]);
+ }
+
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain +
+ delay_samples[channel] * params.wet_gain)
+ .to_int_floor() /
+ 64;
+ }
+ }
+}
+
+/**
+ * Apply a delay effect if enabled, according to the parameters and current state, on the input mix
+ * buffers, saving the results to the output mix buffers.
+ *
+ * @param params - Input parameters to use.
+ * @param state - State to use, must be initialized (see InitializeDelayEffect).
+ * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
+ * @param inputs - Input mix buffers to performan the delay on.
+ * @param outputs - Output mix buffers to receive the delayed samples.
+ * @param sample_count - Number of samples to process.
+ */
+static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
+ const bool enabled, std::vector<std::span<const s32>>& inputs,
+ std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+
+ if (!IsChannelCountValid(params.channel_count)) {
+ LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count);
+ return;
+ }
+
+ if (enabled) {
+ switch (params.channel_count) {
+ case 1:
+ ApplyDelay<1>(params, state, inputs, outputs, sample_count);
+ break;
+ case 2:
+ ApplyDelay<2>(params, state, inputs, outputs, sample_count);
+ break;
+ case 4:
+ ApplyDelay<4>(params, state, inputs, outputs, sample_count);
+ break;
+ case 6:
+ ApplyDelay<6>(params, state, inputs, outputs, sample_count);
+ break;
+ default:
+ for (u32 channel = 0; channel < params.channel_count; channel++) {
+ if (inputs[channel].data() != outputs[channel].data()) {
+ std::memcpy(outputs[channel].data(), inputs[channel].data(),
+ sample_count * sizeof(s32));
+ }
+ }
+ break;
+ }
+ } else {
+ for (u32 channel = 0; channel < params.channel_count; channel++) {
+ if (inputs[channel].data() != outputs[channel].data()) {
+ std::memcpy(outputs[channel].data(), inputs[channel].data(),
+ sample_count * sizeof(s32));
+ }
+ }
+ }
+}
+
+void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (s16 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<DelayInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == DelayInfo::ParameterState::Updating) {
+ SetDelayEffectParameter(parameter, *state_);
+ } else if (parameter.state == DelayInfo::ParameterState::Initialized) {
+ InitializeDelayEffect(parameter, *state_, workbuffer);
+ }
+ }
+ ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count);
+}
+
+bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/delay.h b/src/audio_core/renderer/command/effect/delay.h
new file mode 100644
index 000000000..b7a15ae6b
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/delay.h
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/effect/delay.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters
+ * and state, outputs receives the delayed samples.
+ */
+struct DelayCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ DelayInfo::ParameterVersion1 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp
new file mode 100644
index 000000000..c4bf3943a
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp
@@ -0,0 +1,437 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <numbers>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
+
+namespace AudioCore::AudioRenderer {
+
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{
+ 5.0f,
+ 6.0f,
+ 13.0f,
+ 14.0f,
+};
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MaxDelayLineTimes{
+ 45.7042007446f,
+ 82.7817001343f,
+ 149.938293457f,
+ 271.575805664f,
+};
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay0MaxDelayLineTimes{17.0f, 13.0f,
+ 9.0f, 7.0f};
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay1MaxDelayLineTimes{19.0f, 11.0f,
+ 10.0f, 6.0f};
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyTapTimes{
+ 0.0171360000968f,
+ 0.0591540001333f,
+ 0.161733001471f,
+ 0.390186011791f,
+ 0.425262004137f,
+ 0.455410987139f,
+ 0.689737021923f,
+ 0.74590998888f,
+ 0.833844006062f,
+ 0.859502017498f,
+ 0.0f,
+ 0.0750240013003f,
+ 0.168788000941f,
+ 0.299901008606f,
+ 0.337442994118f,
+ 0.371903002262f,
+ 0.599011003971f,
+ 0.716741025448f,
+ 0.817858994007f,
+ 0.85166400671f,
+};
+
+constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyGains{
+ 0.67096f, 0.61027f, 1.0f, 0.3568f, 0.68361f, 0.65978f, 0.51939f,
+ 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.3827f,
+ 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
+
+/**
+ * Update the I3dl2ReverbInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ * @param reset - If enabled, the state buffers will be reset. Only set this on initialize.
+ */
+static void UpdateI3dl2ReverbEffectParameter(const I3dl2ReverbInfo::ParameterVersion1& params,
+ I3dl2ReverbInfo::State& state, const bool reset) {
+ const auto pow_10 = [](f32 val) -> f32 {
+ return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
+ };
+ const auto sin = [](f32 degrees) -> f32 {
+ return std::sin(degrees * std::numbers::pi_v<f32> / 180.0f);
+ };
+ const auto cos = [](f32 degrees) -> f32 {
+ return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
+ };
+
+ Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000.0f};
+
+ state.dry_gain = params.dry_gain;
+ Common::FixedPoint<50, 14> early_gain{
+ std::min(params.room_gain + params.reflection_gain, 5000.0f) / 2000.0f};
+ state.early_gain = pow_10(early_gain.to_float());
+ Common::FixedPoint<50, 14> late_gain{std::min(params.room_gain + params.reverb_gain, 5000.0f) /
+ 2000.0f};
+ state.late_gain = pow_10(late_gain.to_float());
+
+ Common::FixedPoint<50, 14> hf_gain{pow_10(params.room_HF_gain / 2000.0f)};
+ if (hf_gain >= 1.0f) {
+ state.lowpass_1 = 0.0f;
+ state.lowpass_2 = 1.0f;
+ } else {
+ const auto reference_hf{(params.reference_HF * 256.0f) /
+ static_cast<f32>(params.sample_rate)};
+ const Common::FixedPoint<50, 14> a{1.0f - hf_gain.to_float()};
+ const Common::FixedPoint<50, 14> b{2.0f + (-cos(reference_hf) * (hf_gain * 2.0f))};
+ const Common::FixedPoint<50, 14> c{
+ std::sqrt(std::pow(b.to_float(), 2.0f) + (std::pow(a.to_float(), 2.0f) * -4.0f))};
+
+ state.lowpass_1 = std::min(((b - c) / (a * 2.0f)).to_float(), 0.99723f);
+ state.lowpass_2 = 1.0f - state.lowpass_1;
+ }
+
+ state.early_to_late_taps =
+ (((params.reflection_delay + params.late_reverb_delay_time) * 1000.0f) * delay).to_int();
+ state.last_reverb_echo = params.late_reverb_diffusion * 0.6f * 0.01f;
+
+ for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
+ auto curr_delay{
+ ((MinDelayLineTimes[i] + (params.late_reverb_density / 100.0f) *
+ (MaxDelayLineTimes[i] - MinDelayLineTimes[i])) *
+ delay)
+ .to_int()};
+ state.fdn_delay_lines[i].SetDelay(curr_delay);
+
+ const auto a{
+ (static_cast<f32>(state.fdn_delay_lines[i].delay + state.decay_delay_lines0[i].delay +
+ state.decay_delay_lines1[i].delay) *
+ -60.0f) /
+ (params.late_reverb_decay_time * static_cast<f32>(params.sample_rate))};
+ const auto b{a / params.late_reverb_HF_decay_ratio};
+ const auto c{
+ cos(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate)) /
+ sin(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate))};
+ const auto d{pow_10((b - a) / 40.0f)};
+ const auto e{pow_10((b + a) / 40.0f) * 0.7071f};
+
+ state.lowpass_coeff[i][0] = ((c * d + 1.0f) * e) / (c + d);
+ state.lowpass_coeff[i][1] = ((1.0f - (c * d)) * e) / (c + d);
+ state.lowpass_coeff[i][2] = (c - d) / (c + d);
+
+ state.decay_delay_lines0[i].wet_gain = state.last_reverb_echo;
+ state.decay_delay_lines1[i].wet_gain = state.last_reverb_echo * -0.9f;
+ }
+
+ if (reset) {
+ state.shelf_filter.fill(0.0f);
+ state.lowpass_0 = 0.0f;
+ for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
+ std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
+ std::ranges::fill(state.decay_delay_lines0[i].buffer, 0);
+ std::ranges::fill(state.decay_delay_lines1[i].buffer, 0);
+ }
+ std::ranges::fill(state.center_delay_line.buffer, 0);
+ std::ranges::fill(state.early_delay_line.buffer, 0);
+ }
+
+ const auto reflection_time{(params.late_reverb_delay_time * 0.9998f + 0.02f) * 1000.0f};
+ const auto reflection_delay{params.reflection_delay * 1000.0f};
+ for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayTaps; i++) {
+ auto length{((reflection_delay + reflection_time * EarlyTapTimes[i]) * delay).to_int()};
+ if (length >= state.early_delay_line.max_delay) {
+ length = state.early_delay_line.max_delay;
+ }
+ state.early_tap_steps[i] = length;
+ }
+}
+
+/**
+ * Initialize a new I3dl2ReverbInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ * @param workbuffer - Game-supplied memory for the state. (Unused)
+ */
+static void InitializeI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
+ I3dl2ReverbInfo::State& state, const CpuAddr workbuffer) {
+ state = {};
+ Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000};
+
+ for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
+ auto fdn_delay_time{(MaxDelayLineTimes[i] * delay).to_uint_floor()};
+ state.fdn_delay_lines[i].Initialize(fdn_delay_time);
+
+ auto decay0_delay_time{(Decay0MaxDelayLineTimes[i] * delay).to_uint_floor()};
+ state.decay_delay_lines0[i].Initialize(decay0_delay_time);
+
+ auto decay1_delay_time{(Decay1MaxDelayLineTimes[i] * delay).to_uint_floor()};
+ state.decay_delay_lines1[i].Initialize(decay1_delay_time);
+ }
+
+ const auto center_delay_time{(5 * delay).to_uint_floor()};
+ state.center_delay_line.Initialize(center_delay_time);
+
+ const auto early_delay_time{(400 * delay).to_uint_floor()};
+ state.early_delay_line.Initialize(early_delay_time);
+
+ UpdateI3dl2ReverbEffectParameter(params, state, true);
+}
+
+/**
+ * Pass-through the effect, copying input to output directly, with no reverb applied.
+ *
+ * @param inputs - Array of input mix buffers to copy.
+ * @param outputs - Array of output mix buffers to receive copy.
+ * @param channel_count - Number of channels in inputs and outputs.
+ * @param sample_count - Number of samples within each channel (unused).
+ */
+static void ApplyI3dl2ReverbEffectBypass(std::span<std::span<const s32>> inputs,
+ std::span<std::span<s32>> outputs, const u32 channel_count,
+ [[maybe_unused]] const u32 sample_count) {
+ for (u32 i = 0; i < channel_count; i++) {
+ if (inputs[i].data() != outputs[i].data()) {
+ std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
+ }
+ }
+}
+
+/**
+ * Tick the delay lines, reading and returning their current output, and writing a new decaying
+ * sample (mix).
+ *
+ * @param decay0 - The first decay line.
+ * @param decay1 - The second decay line.
+ * @param fdn - Feedback delay network.
+ * @param mix - The new calculated sample to be written and decayed.
+ * @return The next delayed and decayed sample.
+ */
+static Common::FixedPoint<50, 14> Axfx2AllPassTick(I3dl2ReverbInfo::I3dl2DelayLine& decay0,
+ I3dl2ReverbInfo::I3dl2DelayLine& decay1,
+ I3dl2ReverbInfo::I3dl2DelayLine& fdn,
+ const Common::FixedPoint<50, 14> mix) {
+ auto val{decay0.Read()};
+ auto mixed{mix - (val * decay0.wet_gain)};
+ auto out{decay0.Tick(mixed) + (mixed * decay0.wet_gain)};
+
+ val = decay1.Read();
+ mixed = out - (val * decay1.wet_gain);
+ out = decay1.Tick(mixed) + (mixed * decay1.wet_gain);
+
+ fdn.Tick(out);
+ return out;
+}
+
+/**
+ * Impl. Apply a I3DL2 reverb according to the current state, on the input mix buffers,
+ * saving the results to the output mix buffers.
+ *
+ * @tparam NumChannels - Number of channels to process. 1-6.
+ Inputs/outputs should have this many buffers.
+ * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
+ * @param inputs - Input mix buffers to perform the reverb on.
+ * @param outputs - Output mix buffers to receive the reverbed samples.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t NumChannels>
+static void ApplyI3dl2ReverbEffect(I3dl2ReverbInfo::State& state,
+ std::span<std::span<const s32>> inputs,
+ std::span<std::span<s32>> outputs, const u32 sample_count) {
+ constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
+ 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
+ };
+ constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
+ 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3,
+ };
+ constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
+ 2, 0, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5,
+ };
+
+ std::span<const u8> tap_indexes{};
+ if constexpr (NumChannels == 1) {
+ tap_indexes = OutTapIndexes1Ch;
+ } else if constexpr (NumChannels == 2) {
+ tap_indexes = OutTapIndexes2Ch;
+ } else if constexpr (NumChannels == 4) {
+ tap_indexes = OutTapIndexes4Ch;
+ } else if constexpr (NumChannels == 6) {
+ tap_indexes = OutTapIndexes6Ch;
+ }
+
+ for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
+ Common::FixedPoint<50, 14> early_to_late_tap{
+ state.early_delay_line.TapOut(state.early_to_late_taps)};
+ std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
+
+ for (u32 early_tap = 0; early_tap < I3dl2ReverbInfo::MaxDelayTaps; early_tap++) {
+ output_samples[tap_indexes[early_tap]] +=
+ state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
+ EarlyGains[early_tap];
+ if constexpr (NumChannels == 6) {
+ output_samples[static_cast<u32>(Channels::LFE)] +=
+ state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
+ EarlyGains[early_tap];
+ }
+ }
+
+ Common::FixedPoint<50, 14> current_sample{};
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ current_sample += inputs[channel][sample_index];
+ }
+
+ state.lowpass_0 =
+ (current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1).to_float();
+ state.early_delay_line.Tick(state.lowpass_0);
+
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ output_samples[channel] *= state.early_gain;
+ }
+
+ std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> filtered_samples{};
+ for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
+ filtered_samples[delay_line] =
+ state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][0] +
+ state.shelf_filter[delay_line];
+ state.shelf_filter[delay_line] =
+ (filtered_samples[delay_line] * state.lowpass_coeff[delay_line][2] +
+ state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][1])
+ .to_float();
+ }
+
+ const std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> mix_matrix{
+ filtered_samples[1] + filtered_samples[2] + early_to_late_tap * state.late_gain,
+ -filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
+ filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
+ filtered_samples[1] - filtered_samples[2] + early_to_late_tap * state.late_gain,
+ };
+
+ std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> allpass_samples{};
+ for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
+ allpass_samples[delay_line] = Axfx2AllPassTick(
+ state.decay_delay_lines0[delay_line], state.decay_delay_lines1[delay_line],
+ state.fdn_delay_lines[delay_line], mix_matrix[delay_line]);
+ }
+
+ if constexpr (NumChannels == 6) {
+ const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
+ allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
+ allpass_samples[3], allpass_samples[2], allpass_samples[3],
+ };
+
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ Common::FixedPoint<50, 14> allpass{};
+
+ if (channel == static_cast<u32>(Channels::Center)) {
+ allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
+ } else {
+ allpass = allpass_outputs[channel];
+ }
+
+ auto out_sample{output_samples[channel] + allpass +
+ state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
+
+ outputs[channel][sample_index] =
+ static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
+ }
+ } else {
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ auto out_sample{output_samples[channel] + allpass_samples[channel] +
+ state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
+ outputs[channel][sample_index] =
+ static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
+ }
+ }
+ }
+}
+
+/**
+ * Apply a I3DL2 reverb if enabled, according to the current state, on the input mix buffers,
+ * saving the results to the output mix buffers.
+ *
+ * @param params - Input parameters to use.
+ * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
+ * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
+ * @param inputs - Input mix buffers to performan the delay on.
+ * @param outputs - Output mix buffers to receive the delayed samples.
+ * @param sample_count - Number of samples to process.
+ */
+static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
+ I3dl2ReverbInfo::State& state, const bool enabled,
+ std::span<std::span<const s32>> inputs,
+ std::span<std::span<s32>> outputs, const u32 sample_count) {
+ if (enabled) {
+ switch (params.channel_count) {
+ case 0:
+ return;
+ case 1:
+ ApplyI3dl2ReverbEffect<1>(state, inputs, outputs, sample_count);
+ break;
+ case 2:
+ ApplyI3dl2ReverbEffect<2>(state, inputs, outputs, sample_count);
+ break;
+ case 4:
+ ApplyI3dl2ReverbEffect<4>(state, inputs, outputs, sample_count);
+ break;
+ case 6:
+ ApplyI3dl2ReverbEffect<6>(state, inputs, outputs, sample_count);
+ break;
+ default:
+ ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
+ break;
+ }
+ } else {
+ ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
+ }
+}
+
+void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<I3dl2ReverbInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == I3dl2ReverbInfo::ParameterState::Updating) {
+ UpdateI3dl2ReverbEffectParameter(parameter, *state_, false);
+ } else if (parameter.state == I3dl2ReverbInfo::ParameterState::Initialized) {
+ InitializeI3dl2ReverbEffect(parameter, *state_, workbuffer);
+ }
+ }
+ ApplyI3dl2ReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count);
+}
+
+bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.h b/src/audio_core/renderer/command/effect/i3dl2_reverb.h
new file mode 100644
index 000000000..243877056
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.h
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/effect/i3dl2.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to
+ * the I3DL2 spec, outputs receives the results.
+ */
+struct I3dl2ReverbCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ I3dl2ReverbInfo::ParameterVersion1 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/light_limiter.cpp b/src/audio_core/renderer/command/effect/light_limiter.cpp
new file mode 100644
index 000000000..e8fb0e2fc
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/light_limiter.cpp
@@ -0,0 +1,222 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/light_limiter.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Update the LightLimiterInfo state according to the given parameters.
+ * A no-op.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ */
+static void UpdateLightLimiterEffectParameter(const LightLimiterInfo::ParameterVersion2& params,
+ LightLimiterInfo::State& state) {}
+
+/**
+ * Initialize a new LightLimiterInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ * @param workbuffer - Game-supplied memory for the state. (Unused)
+ */
+static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
+ LightLimiterInfo::State& state, const CpuAddr workbuffer) {
+ state = {};
+ state.samples_average.fill(0.0f);
+ state.compression_gain.fill(1.0f);
+ state.look_ahead_sample_offsets.fill(0);
+ for (u32 i = 0; i < params.channel_count; i++) {
+ state.look_ahead_sample_buffers[i].resize(params.look_ahead_samples_max, 0.0f);
+ }
+}
+
+/**
+ * Apply a light limiter effect if enabled, according to the current state, on the input mix
+ * buffers, saving the results to the output mix buffers.
+ *
+ * @param params - Input parameters to use.
+ * @param state - State to use, must be initialized (see InitializeLightLimiterEffect).
+ * @param enabled - If enabled, limiter will be applied, otherwise input is copied to output.
+ * @param inputs - Input mix buffers to perform the limiter on.
+ * @param outputs - Output mix buffers to receive the limited samples.
+ * @param sample_count - Number of samples to process.
+ * @params statistics - Optional output statistics, only used with version 2.
+ */
+static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
+ LightLimiterInfo::State& state, const bool enabled,
+ std::vector<std::span<const s32>>& inputs,
+ std::vector<std::span<s32>>& outputs, const u32 sample_count,
+ LightLimiterInfo::StatisticsInternal* statistics) {
+ constexpr s64 min{std::numeric_limits<s32>::min()};
+ constexpr s64 max{std::numeric_limits<s32>::max()};
+
+ const auto recip_estimate = [](f64 a) -> f64 {
+ s32 q, s;
+ f64 r;
+ q = (s32)(a * 512.0); /* a in units of 1/512 rounded down */
+ r = 1.0 / (((f64)q + 0.5) / 512.0); /* reciprocal r */
+ s = (s32)(256.0 * r + 0.5); /* r in units of 1/256 rounded to nearest */
+ return ((f64)s / 256.0);
+ };
+
+ if (enabled) {
+ if (statistics && params.statistics_reset_required) {
+ for (u32 i = 0; i < params.channel_count; i++) {
+ statistics->channel_compression_gain_min[i] = 1.0f;
+ statistics->channel_max_sample[i] = 0;
+ }
+ }
+
+ for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
+ for (u32 channel = 0; channel < params.channel_count; channel++) {
+ auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) /
+ Common::FixedPoint<49, 15>::one) *
+ params.input_gain};
+ auto abs_sample{sample};
+ if (sample < 0.0f) {
+ abs_sample = -sample;
+ }
+ auto coeff{abs_sample > state.samples_average[channel] ? params.attack_coeff
+ : params.release_coeff};
+ state.samples_average[channel] +=
+ ((abs_sample - state.samples_average[channel]) * coeff).to_float();
+
+ // Reciprocal estimate
+ auto new_average_sample{Common::FixedPoint<49, 15>(
+ recip_estimate(state.samples_average[channel].to_double()))};
+ if (params.processing_mode != LightLimiterInfo::ProcessingMode::Mode1) {
+ // Two Newton-Raphson steps
+ auto temp{2.0 - (state.samples_average[channel] * new_average_sample)};
+ new_average_sample = 2.0 - (state.samples_average[channel] * temp);
+ }
+
+ auto above_threshold{state.samples_average[channel] > params.threshold};
+ auto attenuation{above_threshold ? params.threshold * new_average_sample : 1.0f};
+ coeff = attenuation < state.compression_gain[channel] ? params.attack_coeff
+ : params.release_coeff;
+ state.compression_gain[channel] +=
+ (attenuation - state.compression_gain[channel]) * coeff;
+
+ auto lookahead_sample{
+ state.look_ahead_sample_buffers[channel]
+ [state.look_ahead_sample_offsets[channel]]};
+
+ state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] =
+ sample;
+ state.look_ahead_sample_offsets[channel] =
+ (state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min;
+
+ outputs[channel][sample_index] = static_cast<s32>(
+ std::clamp((lookahead_sample * state.compression_gain[channel] *
+ params.output_gain * Common::FixedPoint<49, 15>::one)
+ .to_long(),
+ min, max));
+
+ if (statistics) {
+ statistics->channel_max_sample[channel] =
+ std::max(statistics->channel_max_sample[channel], abs_sample.to_float());
+ statistics->channel_compression_gain_min[channel] =
+ std::min(statistics->channel_compression_gain_min[channel],
+ state.compression_gain[channel].to_float());
+ }
+ }
+ }
+ } else {
+ for (u32 i = 0; i < params.channel_count; i++) {
+ if (params.inputs[i] != params.outputs[i]) {
+ std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
+ }
+ }
+ }
+}
+
+void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("LightLimiterVersion1Command\n\tinputs: ");
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
+ UpdateLightLimiterEffectParameter(parameter, *state_);
+ } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
+ InitializeLightLimiterEffect(parameter, *state_, workbuffer);
+ }
+ }
+
+ LightLimiterInfo::StatisticsInternal* statistics{nullptr};
+ ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count, statistics);
+}
+
+bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n");
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
+ UpdateLightLimiterEffectParameter(parameter, *state_);
+ } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
+ InitializeLightLimiterEffect(parameter, *state_, workbuffer);
+ }
+ }
+
+ auto statistics{reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(result_state)};
+ ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count, statistics);
+}
+
+bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/light_limiter.h b/src/audio_core/renderer/command/effect/light_limiter.h
new file mode 100644
index 000000000..5d98272c7
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/light_limiter.h
@@ -0,0 +1,103 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/effect/light_limiter.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for limiting volume between a high and low threshold.
+ * Version 1.
+ */
+struct LightLimiterVersion1Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ LightLimiterInfo::ParameterVersion2 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Is this effect enabled?
+ bool effect_enabled;
+};
+
+/**
+ * AudioRenderer command for limiting volume between a high and low threshold.
+ * Version 2 with output statistics.
+ */
+struct LightLimiterVersion2Command : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ LightLimiterInfo::ParameterVersion2 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Optional statistics, sent back to the sysmodule
+ CpuAddr result_state;
+ /// Is this effect enabled?
+ bool effect_enabled;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp
new file mode 100644
index 000000000..b3c3ba4ba
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/biquad_filter.h"
+#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
+
+namespace AudioCore::AudioRenderer {
+
+void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format(
+ "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n",
+ input, output, needs_init[0], needs_init[1]);
+}
+
+void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
+ if (filter_tap_count > MaxBiquadFilters) {
+ LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count);
+ filter_tap_count = MaxBiquadFilters;
+ }
+
+ auto input_buffer{
+ processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
+ auto output_buffer{
+ processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
+
+ // TODO: Fix this, currently just applies the filter to the input twice,
+ // and doesn't chain the biquads together at all.
+ for (u32 i = 0; i < filter_tap_count; i++) {
+ auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(states[i])};
+ if (needs_init[i]) {
+ std::memset(state, 0, sizeof(VoiceState::BiquadFilterState));
+ }
+
+ ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state,
+ processor.sample_count);
+ }
+}
+
+bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h
new file mode 100644
index 000000000..99c2c0830
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for applying multiple biquads at once.
+ */
+struct MultiTapBiquadFilterCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer index
+ s16 input;
+ /// Output mix buffer index
+ s16 output;
+ /// Biquad parameters
+ std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads;
+ /// Biquad states, updated each call
+ std::array<CpuAddr, MaxBiquadFilters> states;
+ /// If each biquad needs initialisation
+ std::array<bool, MaxBiquadFilters> needs_init;
+ /// Number of active biquads
+ u8 filter_tap_count;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/reverb.cpp b/src/audio_core/renderer/command/effect/reverb.cpp
new file mode 100644
index 000000000..fe2b1eb43
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/reverb.cpp
@@ -0,0 +1,440 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <numbers>
+#include <ranges>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/effect/reverb.h"
+
+namespace AudioCore::AudioRenderer {
+
+constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = {
+ 53.9532470703125f,
+ 79.19256591796875f,
+ 116.23876953125f,
+ 170.61529541015625f,
+};
+
+constexpr std::array<f32, ReverbInfo::MaxDelayLines> DecayMaxDelayLineTimes = {
+ 7.0f,
+ 9.0f,
+ 13.0f,
+ 17.0f,
+};
+
+constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps + 1>, ReverbInfo::NumEarlyModes>
+ EarlyDelayTimes = {
+ {{0.000000f, 3.500000f, 2.799988f, 3.899963f, 2.699951f, 13.399963f, 7.899963f, 8.399963f,
+ 9.899963f, 12.000000f, 12.500000f},
+ {0.000000f, 11.799988f, 5.500000f, 11.199951f, 10.399963f, 38.099976f, 22.199951f,
+ 29.599976f, 21.199951f, 24.799988f, 40.000000f},
+ {0.000000f, 41.500000f, 20.500000f, 41.299988f, 0.000000f, 29.500000f, 33.799988f,
+ 45.199951f, 46.799988f, 0.000000f, 50.000000f},
+ {33.099976f, 43.299988f, 22.799988f, 37.899963f, 14.899963f, 35.299988f, 17.899963f,
+ 34.199951f, 0.000000f, 43.299988f, 50.000000f},
+ {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
+ 0.000000f, 0.000000f, 0.000000f}},
+};
+
+constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps>, ReverbInfo::NumEarlyModes>
+ EarlyDelayGains = {{
+ {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f,
+ 0.679993f, 0.679993f},
+ {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.679993f, 0.679993f,
+ 0.679993f, 0.679993f},
+ {0.500000f, 0.699951f, 0.699951f, 0.679993f, 0.500000f, 0.679993f, 0.679993f, 0.699951f,
+ 0.679993f, 0.000000f},
+ {0.929993f, 0.919983f, 0.869995f, 0.859985f, 0.939941f, 0.809998f, 0.799988f, 0.769958f,
+ 0.759949f, 0.649963f},
+ {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
+ 0.000000f, 0.000000f},
+ }};
+
+constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
+ FdnDelayTimes = {{
+ {53.953247f, 79.192566f, 116.238770f, 130.615295f},
+ {53.953247f, 79.192566f, 116.238770f, 170.615295f},
+ {5.000000f, 10.000000f, 5.000000f, 10.000000f},
+ {47.029968f, 71.000000f, 103.000000f, 170.000000f},
+ {53.953247f, 79.192566f, 116.238770f, 170.615295f},
+ }};
+
+constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
+ DecayDelayTimes = {{
+ {7.000000f, 9.000000f, 13.000000f, 17.000000f},
+ {7.000000f, 9.000000f, 13.000000f, 17.000000f},
+ {1.000000f, 1.000000f, 1.000000f, 1.000000f},
+ {7.000000f, 7.000000f, 13.000000f, 9.000000f},
+ {7.000000f, 9.000000f, 13.000000f, 17.000000f},
+ }};
+
+/**
+ * Update the ReverbInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ */
+static void UpdateReverbEffectParameter(const ReverbInfo::ParameterVersion2& params,
+ ReverbInfo::State& state) {
+ const auto pow_10 = [](f32 val) -> f32 {
+ return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
+ };
+ const auto cos = [](f32 degrees) -> f32 {
+ return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
+ };
+
+ static bool unk_initialized{false};
+ static Common::FixedPoint<50, 14> unk_value{};
+
+ const auto sample_rate{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
+ const auto pre_delay_time{Common::FixedPoint<50, 14>::from_base(params.pre_delay)};
+
+ for (u32 i = 0; i < ReverbInfo::MaxDelayTaps; i++) {
+ auto early_delay{
+ ((pre_delay_time + EarlyDelayTimes[params.early_mode][i]) * sample_rate).to_int()};
+ early_delay = std::min(early_delay, state.pre_delay_line.sample_count_max);
+ state.early_delay_times[i] = early_delay + 1;
+ state.early_gains[i] = Common::FixedPoint<50, 14>::from_base(params.early_gain) *
+ EarlyDelayGains[params.early_mode][i];
+ }
+
+ if (params.channel_count == 2) {
+ state.early_gains[4] * 0.5f;
+ state.early_gains[5] * 0.5f;
+ }
+
+ auto pre_time{
+ ((pre_delay_time + EarlyDelayTimes[params.early_mode][10]) * sample_rate).to_int()};
+ state.pre_delay_time = std::min(pre_time, state.pre_delay_line.sample_count_max);
+
+ if (!unk_initialized) {
+ unk_value = cos((1280.0f / sample_rate).to_float());
+ unk_initialized = true;
+ }
+
+ for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
+ const auto fdn_delay{(FdnDelayTimes[params.late_mode][i] * sample_rate).to_int()};
+ state.fdn_delay_lines[i].sample_count =
+ std::min(fdn_delay, state.fdn_delay_lines[i].sample_count_max);
+ state.fdn_delay_lines[i].buffer_end =
+ &state.fdn_delay_lines[i].buffer[state.fdn_delay_lines[i].sample_count - 1];
+
+ const auto decay_delay{(DecayDelayTimes[params.late_mode][i] * sample_rate).to_int()};
+ state.decay_delay_lines[i].sample_count =
+ std::min(decay_delay, state.decay_delay_lines[i].sample_count_max);
+ state.decay_delay_lines[i].buffer_end =
+ &state.decay_delay_lines[i].buffer[state.decay_delay_lines[i].sample_count - 1];
+
+ state.decay_delay_lines[i].decay =
+ 0.5999755859375f * (1.0f - Common::FixedPoint<50, 14>::from_base(params.colouration));
+
+ auto a{(Common::FixedPoint<50, 14>(state.fdn_delay_lines[i].sample_count_max) +
+ state.decay_delay_lines[i].sample_count_max) *
+ -3};
+ auto b{a / (Common::FixedPoint<50, 14>::from_base(params.decay_time) * sample_rate)};
+ Common::FixedPoint<50, 14> c{0.0f};
+ Common::FixedPoint<50, 14> d{0.0f};
+ auto hf_decay_ratio{Common::FixedPoint<50, 14>::from_base(params.high_freq_decay_ratio)};
+
+ if (hf_decay_ratio > 0.99493408203125f) {
+ c = 0.0f;
+ d = 1.0f;
+ } else {
+ const auto e{
+ pow_10(((((1.0f / hf_decay_ratio) - 1.0f) * 2) / 100 * (b / 10)).to_float())};
+ const auto f{1.0f - e};
+ const auto g{2.0f - (unk_value * e * 2)};
+ const auto h{std::sqrt(std::pow(g.to_float(), 2.0f) - (std::pow(f, 2.0f) * 4))};
+
+ c = (g - h) / (f * 2.0f);
+ d = 1.0f - c;
+ }
+
+ state.hf_decay_prev_gain[i] = c;
+ state.hf_decay_gain[i] = pow_10((b / 1000).to_float()) * d * 0.70709228515625f;
+ state.prev_feedback_output[i] = 0;
+ }
+}
+
+/**
+ * Initialize a new ReverbInfo state according to the given parameters.
+ *
+ * @param params - Input parameters to update the state.
+ * @param state - State to be updated.
+ * @param workbuffer - Game-supplied memory for the state. (Unused)
+ * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb begins.
+ */
+static void InitializeReverbEffect(const ReverbInfo::ParameterVersion2& params,
+ ReverbInfo::State& state, const CpuAddr workbuffer,
+ const bool long_size_pre_delay_supported) {
+ state = {};
+
+ auto delay{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
+
+ for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
+ auto fdn_delay_time{(FdnMaxDelayLineTimes[i] * delay).to_uint_floor()};
+ state.fdn_delay_lines[i].Initialize(fdn_delay_time, 1.0f);
+
+ auto decay_delay_time{(DecayMaxDelayLineTimes[i] * delay).to_uint_floor()};
+ state.decay_delay_lines[i].Initialize(decay_delay_time, 0.0f);
+ }
+
+ const auto pre_delay{long_size_pre_delay_supported ? 350.0f : 150.0f};
+ const auto pre_delay_line{(pre_delay * delay).to_uint_floor()};
+ state.pre_delay_line.Initialize(pre_delay_line, 1.0f);
+
+ const auto center_delay_time{(5 * delay).to_uint_floor()};
+ state.center_delay_line.Initialize(center_delay_time, 1.0f);
+
+ UpdateReverbEffectParameter(params, state);
+
+ for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
+ std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
+ std::ranges::fill(state.decay_delay_lines[i].buffer, 0);
+ }
+ std::ranges::fill(state.center_delay_line.buffer, 0);
+ std::ranges::fill(state.pre_delay_line.buffer, 0);
+}
+
+/**
+ * Pass-through the effect, copying input to output directly, with no reverb applied.
+ *
+ * @param inputs - Array of input mix buffers to copy.
+ * @param outputs - Array of output mix buffers to receive copy.
+ * @param channel_count - Number of channels in inputs and outputs.
+ * @param sample_count - Number of samples within each channel.
+ */
+static void ApplyReverbEffectBypass(std::span<std::span<const s32>> inputs,
+ std::span<std::span<s32>> outputs, const u32 channel_count,
+ const u32 sample_count) {
+ for (u32 i = 0; i < channel_count; i++) {
+ if (inputs[i].data() != outputs[i].data()) {
+ std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
+ }
+ }
+}
+
+/**
+ * Tick the delay lines, reading and returning their current output, and writing a new decaying
+ * sample (mix).
+ *
+ * @param decay - The decay line.
+ * @param fdn - Feedback delay network.
+ * @param mix - The new calculated sample to be written and decayed.
+ * @return The next delayed and decayed sample.
+ */
+static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine& decay,
+ ReverbInfo::ReverbDelayLine& fdn,
+ const Common::FixedPoint<50, 14> mix) {
+ const auto val{decay.Read()};
+ const auto mixed{mix - (val * decay.decay)};
+ const auto out{decay.Tick(mixed) + (mixed * decay.decay)};
+
+ fdn.Tick(out);
+ return out;
+}
+
+/**
+ * Impl. Apply a Reverb according to the current state, on the input mix buffers,
+ * saving the results to the output mix buffers.
+ *
+ * @tparam NumChannels - Number of channels to process. 1-6.
+ Inputs/outputs should have this many buffers.
+ * @param params - Input parameters to update the state.
+ * @param state - State to use, must be initialized (see InitializeReverbEffect).
+ * @param inputs - Input mix buffers to perform the reverb on.
+ * @param outputs - Output mix buffers to receive the reverbed samples.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t NumChannels>
+static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
+ std::vector<std::span<const s32>>& inputs,
+ std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+ constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
+ 0, 0, 1, 1, 0, 1, 0, 0, 1, 1,
+ };
+ constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
+ 0, 0, 1, 1, 0, 1, 2, 2, 3, 3,
+ };
+ constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
+ 0, 0, 1, 1, 2, 2, 4, 4, 5, 5,
+ };
+
+ std::span<const u8> tap_indexes{};
+ if constexpr (NumChannels == 1) {
+ tap_indexes = OutTapIndexes1Ch;
+ } else if constexpr (NumChannels == 2) {
+ tap_indexes = OutTapIndexes2Ch;
+ } else if constexpr (NumChannels == 4) {
+ tap_indexes = OutTapIndexes4Ch;
+ } else if constexpr (NumChannels == 6) {
+ tap_indexes = OutTapIndexes6Ch;
+ }
+
+ for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
+ std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
+
+ for (u32 early_tap = 0; early_tap < ReverbInfo::MaxDelayTaps; early_tap++) {
+ const auto sample{state.pre_delay_line.TapOut(state.early_delay_times[early_tap]) *
+ state.early_gains[early_tap]};
+ output_samples[tap_indexes[early_tap]] += sample;
+ if constexpr (NumChannels == 6) {
+ output_samples[static_cast<u32>(Channels::LFE)] += sample;
+ }
+ }
+
+ if constexpr (NumChannels == 6) {
+ output_samples[static_cast<u32>(Channels::LFE)] *= 0.2f;
+ }
+
+ Common::FixedPoint<50, 14> input_sample{};
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ input_sample += inputs[channel][sample_index];
+ }
+
+ input_sample *= 64;
+ input_sample *= Common::FixedPoint<50, 14>::from_base(params.base_gain);
+ state.pre_delay_line.Write(input_sample);
+
+ for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
+ state.prev_feedback_output[i] =
+ state.prev_feedback_output[i] * state.hf_decay_prev_gain[i] +
+ state.fdn_delay_lines[i].Read() * state.hf_decay_gain[i];
+ }
+
+ Common::FixedPoint<50, 14> pre_delay_sample{
+ state.pre_delay_line.Read() * Common::FixedPoint<50, 14>::from_base(params.late_gain)};
+
+ std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> mix_matrix{
+ state.prev_feedback_output[2] + state.prev_feedback_output[1] + pre_delay_sample,
+ -state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
+ state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
+ state.prev_feedback_output[1] - state.prev_feedback_output[2] + pre_delay_sample,
+ };
+
+ std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> allpass_samples{};
+ for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
+ allpass_samples[i] = Axfx2AllPassTick(state.decay_delay_lines[i],
+ state.fdn_delay_lines[i], mix_matrix[i]);
+ }
+
+ const auto dry_gain{Common::FixedPoint<50, 14>::from_base(params.dry_gain)};
+ const auto wet_gain{Common::FixedPoint<50, 14>::from_base(params.wet_gain)};
+
+ if constexpr (NumChannels == 6) {
+ const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
+ allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
+ allpass_samples[3], allpass_samples[2], allpass_samples[3],
+ };
+
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ auto in_sample{inputs[channel][sample_index] * dry_gain};
+
+ Common::FixedPoint<50, 14> allpass{};
+ if (channel == static_cast<u32>(Channels::Center)) {
+ allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
+ } else {
+ allpass = allpass_outputs[channel];
+ }
+
+ auto out_sample{((output_samples[channel] + allpass) * wet_gain) / 64};
+ outputs[channel][sample_index] = (in_sample + out_sample).to_int();
+ }
+ } else {
+ for (u32 channel = 0; channel < NumChannels; channel++) {
+ auto in_sample{inputs[channel][sample_index] * dry_gain};
+ auto out_sample{((output_samples[channel] + allpass_samples[channel]) * wet_gain) /
+ 64};
+ outputs[channel][sample_index] = (in_sample + out_sample).to_int();
+ }
+ }
+ }
+}
+
+/**
+ * Apply a Reverb if enabled, according to the current state, on the input mix buffers,
+ * saving the results to the output mix buffers.
+ *
+ * @param params - Input parameters to use.
+ * @param state - State to use, must be initialized (see InitializeReverbEffect).
+ * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
+ * @param inputs - Input mix buffers to performan the reverb on.
+ * @param outputs - Output mix buffers to receive the reverbed samples.
+ * @param sample_count - Number of samples to process.
+ */
+static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
+ const bool enabled, std::vector<std::span<const s32>>& inputs,
+ std::vector<std::span<s32>>& outputs, const u32 sample_count) {
+ if (enabled) {
+ switch (params.channel_count) {
+ case 0:
+ return;
+ case 1:
+ ApplyReverbEffect<1>(params, state, inputs, outputs, sample_count);
+ break;
+ case 2:
+ ApplyReverbEffect<2>(params, state, inputs, outputs, sample_count);
+ break;
+ case 4:
+ ApplyReverbEffect<4>(params, state, inputs, outputs, sample_count);
+ break;
+ case 6:
+ ApplyReverbEffect<6>(params, state, inputs, outputs, sample_count);
+ break;
+ default:
+ ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
+ break;
+ }
+ } else {
+ ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
+ }
+}
+
+void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format(
+ "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled,
+ long_size_pre_delay_supported);
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
+ std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
+ std::vector<std::span<s32>> output_buffers(parameter.channel_count);
+
+ for (u32 i = 0; i < parameter.channel_count; i++) {
+ input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count);
+ output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count);
+ }
+
+ auto state_{reinterpret_cast<ReverbInfo::State*>(state)};
+
+ if (effect_enabled) {
+ if (parameter.state == ReverbInfo::ParameterState::Updating) {
+ UpdateReverbEffectParameter(parameter, *state_);
+ } else if (parameter.state == ReverbInfo::ParameterState::Initialized) {
+ InitializeReverbEffect(parameter, *state_, workbuffer, long_size_pre_delay_supported);
+ }
+ }
+ ApplyReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
+ processor.sample_count);
+}
+
+bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/reverb.h b/src/audio_core/renderer/command/effect/reverb.h
new file mode 100644
index 000000000..328756150
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/reverb.h
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/effect/reverb.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives
+ * the results.
+ */
+struct ReverbCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Input parameters
+ ReverbInfo::ParameterVersion2 parameter;
+ /// State, updated each call
+ CpuAddr state;
+ /// Game-supplied workbuffer (Unused)
+ CpuAddr workbuffer;
+ /// Is this effect enabled?
+ bool effect_enabled;
+ /// Is a longer pre-delay time supported?
+ bool long_size_pre_delay_supported;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/icommand.h b/src/audio_core/renderer/command/icommand.h
new file mode 100644
index 000000000..f2dd41254
--- /dev/null
+++ b/src/audio_core/renderer/command/icommand.h
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+enum class CommandId : u8 {
+ /* 0x00 */ Invalid,
+ /* 0x01 */ DataSourcePcmInt16Version1,
+ /* 0x02 */ DataSourcePcmInt16Version2,
+ /* 0x03 */ DataSourcePcmFloatVersion1,
+ /* 0x04 */ DataSourcePcmFloatVersion2,
+ /* 0x05 */ DataSourceAdpcmVersion1,
+ /* 0x06 */ DataSourceAdpcmVersion2,
+ /* 0x07 */ Volume,
+ /* 0x08 */ VolumeRamp,
+ /* 0x09 */ BiquadFilter,
+ /* 0x0A */ Mix,
+ /* 0x0B */ MixRamp,
+ /* 0x0C */ MixRampGrouped,
+ /* 0x0D */ DepopPrepare,
+ /* 0x0E */ DepopForMixBuffers,
+ /* 0x0F */ Delay,
+ /* 0x10 */ Upsample,
+ /* 0x11 */ DownMix6chTo2ch,
+ /* 0x12 */ Aux,
+ /* 0x13 */ DeviceSink,
+ /* 0x14 */ CircularBufferSink,
+ /* 0x15 */ Reverb,
+ /* 0x16 */ I3dl2Reverb,
+ /* 0x17 */ Performance,
+ /* 0x18 */ ClearMixBuffer,
+ /* 0x19 */ CopyMixBuffer,
+ /* 0x1A */ LightLimiterVersion1,
+ /* 0x1B */ LightLimiterVersion2,
+ /* 0x1C */ MultiTapBiquadFilter,
+ /* 0x1D */ Capture,
+ /* 0x1E */ Compressor,
+};
+
+constexpr u32 CommandMagic{0xCAFEBABE};
+
+/**
+ * A command, generated by the host, and processed by the ADSP's AudioRenderer.
+ */
+struct ICommand {
+ virtual ~ICommand() = default;
+
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ virtual void Process(const ADSP::CommandListProcessor& processor) = 0;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0;
+
+ /// Command magic 0xCAFEBABE
+ u32 magic{};
+ /// Command enabled
+ bool enabled{};
+ /// Type of this command (see CommandId)
+ CommandId type{};
+ /// Size of this command
+ s16 size{};
+ /// Estimated processing time for this command
+ u32 estimated_process_time{};
+ /// Node id of the voice or mix this command was generated from
+ u32 node_id{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/clear_mix.cpp b/src/audio_core/renderer/command/mix/clear_mix.cpp
new file mode 100644
index 000000000..4f649d6a8
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/clear_mix.cpp
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <string>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/clear_mix.h"
+
+namespace AudioCore::AudioRenderer {
+
+void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("ClearMixBufferCommand\n");
+}
+
+void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
+ memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes());
+}
+
+bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/clear_mix.h b/src/audio_core/renderer/command/mix/clear_mix.h
new file mode 100644
index 000000000..956ec0b65
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/clear_mix.h
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for a clearing the mix buffers.
+ * Used at the start of each command list.
+ */
+struct ClearMixBufferCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/copy_mix.cpp b/src/audio_core/renderer/command/mix/copy_mix.cpp
new file mode 100644
index 000000000..1d49f1644
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/copy_mix.cpp
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/copy_mix.h"
+
+namespace AudioCore::AudioRenderer {
+
+void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index,
+ output_index);
+}
+
+void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+ auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
+ processor.sample_count)};
+ std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32));
+}
+
+bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/copy_mix.h b/src/audio_core/renderer/command/mix/copy_mix.h
new file mode 100644
index 000000000..a59007fb6
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/copy_mix.h
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for a copying a mix buffer from input to output.
+ */
+struct CopyMixBufferCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer index
+ s16 input_index;
+ /// Output mix buffer index
+ s16 output_index;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp
new file mode 100644
index 000000000..c2bc10061
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp
@@ -0,0 +1,64 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time
+ * according to decay.
+ *
+ * @param output - Output buffer to be depopped.
+ * @param depop_sample - Depopped sample to apply to output samples.
+ * @param decay_ - Amount to decay the depopped sample for every output sample.
+ * @param sample_count - Samples to process.
+ * @return Final decayed depop sample.
+ */
+static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample,
+ Common::FixedPoint<49, 15>& decay_, const u32 sample_count) {
+ auto sample{std::abs(depop_sample)};
+ auto decay{decay_.to_raw()};
+
+ if (depop_sample <= 0) {
+ for (u32 i = 0; i < sample_count; i++) {
+ sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
+ output[i] -= sample;
+ }
+ return -sample;
+ } else {
+ for (u32 i = 0; i < sample_count; i++) {
+ sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
+ output[i] += sample;
+ }
+ return sample;
+ }
+}
+
+void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input,
+ count, decay.to_float());
+}
+
+void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto end_index{std::min(processor.buffer_count, input + count)};
+ std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index};
+
+ for (u32 index = input; index < end_index; index++) {
+ const auto depop_sample{depop_buff[index]};
+ if (depop_sample != 0) {
+ auto input_buffer{processor.mix_buffers.subspan(index * processor.sample_count,
+ processor.sample_count)};
+ depop_buff[index] =
+ ApplyDepopMix(input_buffer, depop_sample, decay, processor.sample_count);
+ }
+ }
+}
+
+bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h
new file mode 100644
index 000000000..e7268ff27
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for depopping a mix buffer.
+ * Adds a cumulation of previous samples to the current mix buffer with a decay.
+ */
+struct DepopForMixBuffersCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Starting input mix buffer index
+ u32 input;
+ /// Number of mix buffers to depop
+ u32 count;
+ /// Amount to decay the depop sample for each new sample
+ Common::FixedPoint<49, 15> decay;
+ /// Address of the depop buffer, holding the last sample for every mix buffer
+ CpuAddr depop_buffer;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_prepare.cpp b/src/audio_core/renderer/command/mix/depop_prepare.cpp
new file mode 100644
index 000000000..2ee076ef6
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_prepare.cpp
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/depop_prepare.h"
+#include "audio_core/renderer/voice/voice_state.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("DepopPrepareCommand\n\tinputs: ");
+ for (u32 i = 0; i < buffer_count; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n";
+}
+
+void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto samples{reinterpret_cast<s32*>(previous_samples)};
+ auto buffer{std::span(reinterpret_cast<s32*>(depop_buffer), buffer_count)};
+
+ for (u32 i = 0; i < buffer_count; i++) {
+ if (samples[i]) {
+ buffer[inputs[i]] += samples[i];
+ samples[i] = 0;
+ }
+ }
+}
+
+bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_prepare.h b/src/audio_core/renderer/command/mix/depop_prepare.h
new file mode 100644
index 000000000..a5465da9a
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_prepare.h
@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for preparing depop.
+ * Adds the previusly output last samples to the depop buffer.
+ */
+struct DepopPrepareCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Depop buffer offset for each mix buffer
+ std::array<s16, MaxMixBuffers> inputs;
+ /// Pointer to the previous mix buffer samples
+ CpuAddr previous_samples;
+ /// Number of mix buffers to use
+ u32 buffer_count;
+ /// Pointer to the current depop values
+ CpuAddr depop_buffer;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix.cpp b/src/audio_core/renderer/command/mix/mix.cpp
new file mode 100644
index 000000000..8ecf9b05a
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix.cpp
@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <limits>
+#include <span>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/mix.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Mix input mix buffer into output mix buffer, with volume applied to the input.
+ *
+ * @tparam Q - Number of bits for fixed point operations.
+ * @param output - Output mix buffer.
+ * @param input - Input mix buffer.
+ * @param volume - Volume applied to the input.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t Q>
+static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f32 volume_,
+ const u32 sample_count) {
+ const Common::FixedPoint<64 - Q, Q> volume{volume_};
+ for (u32 i = 0; i < sample_count; i++) {
+ output[i] = (output[i] + input[i] * volume).to_int();
+ }
+}
+
+void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("MixCommand");
+ string += fmt::format("\n\tinput {:02X}", input_index);
+ string += fmt::format("\n\toutput {:02X}", output_index);
+ string += fmt::format("\n\tvolume {:.8f}", volume);
+ string += "\n";
+}
+
+void MixCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+ auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
+ processor.sample_count)};
+
+ // If volume is 0, nothing will be added to the output, so just skip.
+ if (volume == 0.0f) {
+ return;
+ }
+
+ switch (precision) {
+ case 15:
+ ApplyMix<15>(output, input, volume, processor.sample_count);
+ break;
+
+ case 23:
+ ApplyMix<23>(output, input, volume, processor.sample_count);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
+ break;
+ }
+}
+
+bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix.h b/src/audio_core/renderer/command/mix/mix.h
new file mode 100644
index 000000000..0201cf171
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix.h
@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
+ * applied to the input.
+ */
+struct MixCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Fixed point precision
+ u8 precision;
+ /// Input mix buffer index
+ s16 input_index;
+ /// Output mix buffer index
+ s16 output_index;
+ /// Mix volume applied to the input
+ f32 volume;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp
new file mode 100644
index 000000000..ffdafa1c8
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp
@@ -0,0 +1,94 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/mix_ramp.h"
+#include "common/fixed_point.h"
+#include "common/logging/log.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Mix input mix buffer into output mix buffer, with volume applied to the input.
+ *
+ * @tparam Q - Number of bits for fixed point operations.
+ * @param output - Output mix buffer.
+ * @param input - Input mix buffer.
+ * @param volume - Volume applied to the input.
+ * @param ramp - Ramp applied to volume every sample.
+ * @param sample_count - Number of samples to process.
+ * @return The final gained input sample, used for depopping.
+ */
+template <size_t Q>
+s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
+ const f32 ramp_, const u32 sample_count) {
+ Common::FixedPoint<64 - Q, Q> volume{volume_};
+ Common::FixedPoint<64 - Q, Q> sample{0};
+
+ if (ramp_ == 0.0f) {
+ for (u32 i = 0; i < sample_count; i++) {
+ sample = input[i] * volume;
+ output[i] = (output[i] + sample).to_int();
+ }
+ } else {
+ Common::FixedPoint<64 - Q, Q> ramp{ramp_};
+ for (u32 i = 0; i < sample_count; i++) {
+ sample = input[i] * volume;
+ output[i] = (output[i] + sample).to_int();
+ volume += ramp;
+ }
+ }
+ return sample.to_int();
+}
+
+template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32,
+ const u32);
+template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32,
+ const u32);
+
+void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
+ const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
+ string += fmt::format("MixRampCommand");
+ string += fmt::format("\n\tinput {:02X}", input_index);
+ string += fmt::format("\n\toutput {:02X}", output_index);
+ string += fmt::format("\n\tvolume {:.8f}", volume);
+ string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
+ string += fmt::format("\n\tramp {:.8f}", ramp);
+ string += "\n";
+}
+
+void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+ auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
+ processor.sample_count)};
+ const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
+ auto prev_sample_ptr{reinterpret_cast<s32*>(previous_sample)};
+
+ // If previous volume and ramp are both 0, nothing will be added to the output, so just skip.
+ if (prev_volume == 0.0f && ramp == 0.0f) {
+ *prev_sample_ptr = 0;
+ return;
+ }
+
+ switch (precision) {
+ case 15:
+ *prev_sample_ptr =
+ ApplyMixRamp<15>(output, input, prev_volume, ramp, processor.sample_count);
+ break;
+
+ case 23:
+ *prev_sample_ptr =
+ ApplyMixRamp<23>(output, input, prev_volume, ramp, processor.sample_count);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
+ break;
+ }
+}
+
+bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h
new file mode 100644
index 000000000..770f57e80
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp.h
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
+ * applied to the input, and volume ramping to smooth out the transition.
+ */
+struct MixRampCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Fixed point precision
+ u8 precision;
+ /// Input mix buffer index
+ s16 input_index;
+ /// Output mix buffer index
+ s16 output_index;
+ /// Previous mix volume
+ f32 prev_volume;
+ /// Current mix volume
+ f32 volume;
+ /// Pointer to the previous sample buffer, used for depopping
+ CpuAddr previous_sample;
+};
+
+/**
+ * Mix input mix buffer into output mix buffer, with volume applied to the input.
+ * @tparam Q - Number of bits for fixed point operations.
+ * @param output - Output mix buffer.
+ * @param input - Input mix buffer.
+ * @param volume - Volume applied to the input.
+ * @param ramp - Ramp applied to volume every sample.
+ * @param sample_count - Number of samples to process.
+ * @return The final gained input sample, used for depopping.
+ */
+template <size_t Q>
+s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
+ const f32 ramp_, const u32 sample_count);
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp
new file mode 100644
index 000000000..43dbef9fc
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/mix_ramp.h"
+#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
+
+namespace AudioCore::AudioRenderer {
+
+void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
+ string += "MixRampGroupedCommand";
+ for (u32 i = 0; i < buffer_count; i++) {
+ string += fmt::format("\n\t{}", i);
+ const auto ramp{(volumes[i] - prev_volumes[i]) / static_cast<f32>(processor.sample_count)};
+ string += fmt::format("\n\t\tinput {:02X}", inputs[i]);
+ string += fmt::format("\n\t\toutput {:02X}", outputs[i]);
+ string += fmt::format("\n\t\tvolume {:.8f}", volumes[i]);
+ string += fmt::format("\n\t\tprev_volume {:.8f}", prev_volumes[i]);
+ string += fmt::format("\n\t\tramp {:.8f}", ramp);
+ string += "\n";
+ }
+}
+
+void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) {
+ std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers};
+
+ for (u32 i = 0; i < buffer_count; i++) {
+ auto last_sample{0};
+ if (prev_volumes[i] != 0.0f || volumes[i] != 0.0f) {
+ const auto output{processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
+ processor.sample_count)};
+ const auto input{processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
+ processor.sample_count)};
+ const auto ramp{(volumes[i] - prev_volumes[i]) /
+ static_cast<f32>(processor.sample_count)};
+
+ if (prev_volumes[i] == 0.0f && ramp == 0.0f) {
+ prev_samples[i] = 0;
+ continue;
+ }
+
+ switch (precision) {
+ case 15:
+ last_sample =
+ ApplyMixRamp<15>(output, input, prev_volumes[i], ramp, processor.sample_count);
+ break;
+ case 23:
+ last_sample =
+ ApplyMixRamp<23>(output, input, prev_volumes[i], ramp, processor.sample_count);
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
+ break;
+ }
+ }
+
+ prev_samples[i] = last_sample;
+ }
+}
+
+bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
new file mode 100644
index 000000000..027276e5a
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with
+ * a volume applied to the input, and volume ramping to smooth out the transition.
+ */
+struct MixRampGroupedCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Fixed point precision
+ u8 precision;
+ /// Number of mix buffers to mix
+ u32 buffer_count;
+ /// Input mix buffer indexes for each mix buffer
+ std::array<s16, MaxMixBuffers> inputs;
+ /// Output mix buffer indexes for each mix buffer
+ std::array<s16, MaxMixBuffers> outputs;
+ /// Previous mix vloumes for each mix buffer
+ std::array<f32, MaxMixBuffers> prev_volumes;
+ /// Current mix vloumes for each mix buffer
+ std::array<f32, MaxMixBuffers> volumes;
+ /// Pointer to the previous sample buffer, used for depop
+ CpuAddr previous_samples;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume.cpp b/src/audio_core/renderer/command/mix/volume.cpp
new file mode 100644
index 000000000..b045fb062
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume.cpp
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/volume.h"
+#include "common/fixed_point.h"
+#include "common/logging/log.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Apply volume to the input mix buffer, saving to the output buffer.
+ *
+ * @tparam Q - Number of bits for fixed point operations.
+ * @param output - Output mix buffer.
+ * @param input - Input mix buffer.
+ * @param volume - Volume applied to the input.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t Q>
+static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input, const f32 volume,
+ const u32 sample_count) {
+ if (volume == 1.0f) {
+ std::memcpy(output.data(), input.data(), input.size_bytes());
+ } else {
+ const Common::FixedPoint<64 - Q, Q> gain{volume};
+ for (u32 i = 0; i < sample_count; i++) {
+ output[i] = (input[i] * gain).to_int();
+ }
+ }
+}
+
+void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("VolumeCommand");
+ string += fmt::format("\n\tinput {:02X}", input_index);
+ string += fmt::format("\n\toutput {:02X}", output_index);
+ string += fmt::format("\n\tvolume {:.8f}", volume);
+ string += "\n";
+}
+
+void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) {
+ // If input and output buffers are the same, and the volume is 1.0f, this won't do
+ // anything, so just skip.
+ if (input_index == output_index && volume == 1.0f) {
+ return;
+ }
+
+ auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+ auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
+ processor.sample_count)};
+
+ switch (precision) {
+ case 15:
+ ApplyUniformGain<15>(output, input, volume, processor.sample_count);
+ break;
+
+ case 23:
+ ApplyUniformGain<23>(output, input, volume, processor.sample_count);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
+ break;
+ }
+}
+
+bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume.h b/src/audio_core/renderer/command/mix/volume.h
new file mode 100644
index 000000000..6ae9fb794
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume.h
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for applying volume to a mix buffer.
+ */
+struct VolumeCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Fixed point precision
+ u8 precision;
+ /// Input mix buffer index
+ s16 input_index;
+ /// Output mix buffer index
+ s16 output_index;
+ /// Mix volume applied to the input
+ f32 volume;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume_ramp.cpp b/src/audio_core/renderer/command/mix/volume_ramp.cpp
new file mode 100644
index 000000000..424307148
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume_ramp.cpp
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/mix/volume_ramp.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Apply volume with ramping to the input mix buffer, saving to the output buffer.
+ *
+ * @tparam Q - Number of bits for fixed point operations.
+ * @param output - Output mix buffers.
+ * @param input - Input mix buffers.
+ * @param volume - Volume applied to the input.
+ * @param ramp - Ramp applied to volume every sample.
+ * @param sample_count - Number of samples to process.
+ */
+template <size_t Q>
+static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32> input,
+ const f32 volume, const f32 ramp_, const u32 sample_count) {
+ if (volume == 0.0f && ramp_ == 0.0f) {
+ std::memset(output.data(), 0, output.size_bytes());
+ } else if (volume == 1.0f && ramp_ == 0.0f) {
+ std::memcpy(output.data(), input.data(), output.size_bytes());
+ } else if (ramp_ == 0.0f) {
+ const Common::FixedPoint<64 - Q, Q> gain{volume};
+ for (u32 i = 0; i < sample_count; i++) {
+ output[i] = (input[i] * gain).to_int();
+ }
+ } else {
+ Common::FixedPoint<64 - Q, Q> gain{volume};
+ const Common::FixedPoint<64 - Q, Q> ramp{ramp_};
+ for (u32 i = 0; i < sample_count; i++) {
+ output[i] = (input[i] * gain).to_int();
+ gain += ramp;
+ }
+ }
+}
+
+void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
+ const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
+ string += fmt::format("VolumeRampCommand");
+ string += fmt::format("\n\tinput {:02X}", input_index);
+ string += fmt::format("\n\toutput {:02X}", output_index);
+ string += fmt::format("\n\tvolume {:.8f}", volume);
+ string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
+ string += fmt::format("\n\tramp {:.8f}", ramp);
+ string += "\n";
+}
+
+void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
+ processor.sample_count)};
+ auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
+ processor.sample_count)};
+ const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
+
+ // If input and output buffers are the same, and the volume is 1.0f, and there's no ramping,
+ // this won't do anything, so just skip.
+ if (input_index == output_index && prev_volume == 1.0f && ramp == 0.0f) {
+ return;
+ }
+
+ switch (precision) {
+ case 15:
+ ApplyLinearEnvelopeGain<15>(output, input, prev_volume, ramp, processor.sample_count);
+ break;
+
+ case 23:
+ ApplyLinearEnvelopeGain<23>(output, input, prev_volume, ramp, processor.sample_count);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
+ break;
+ }
+}
+
+bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume_ramp.h b/src/audio_core/renderer/command/mix/volume_ramp.h
new file mode 100644
index 000000000..77b61547e
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume_ramp.h
@@ -0,0 +1,56 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth
+ * out the transition.
+ */
+struct VolumeRampCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Fixed point precision
+ u8 precision;
+ /// Input mix buffer index
+ s16 input_index;
+ /// Output mix buffer index
+ s16 output_index;
+ /// Previous mix volume applied to the input
+ f32 prev_volume;
+ /// Current mix volume applied to the input
+ f32 volume;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/performance/performance.cpp b/src/audio_core/renderer/command/performance/performance.cpp
new file mode 100644
index 000000000..985958b03
--- /dev/null
+++ b/src/audio_core/renderer/command/performance/performance.cpp
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/performance/performance.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+
+namespace AudioCore::AudioRenderer {
+
+void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state));
+}
+
+void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto base{entry_address.translated_address};
+ if (state == PerformanceState::Start) {
+ auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
+ *start_time_ptr = static_cast<u32>(
+ Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
+ processor.start_time - processor.current_processing_time)
+ .count());
+ } else if (state == PerformanceState::Stop) {
+ auto processed_time_ptr{
+ reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
+ auto entry_count_ptr{
+ reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
+
+ *processed_time_ptr = static_cast<u32>(
+ Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
+ processor.start_time - processor.current_processing_time)
+ .count());
+ (*entry_count_ptr)++;
+ }
+}
+
+bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/performance/performance.h b/src/audio_core/renderer/command/performance/performance.h
new file mode 100644
index 000000000..11a7d6c08
--- /dev/null
+++ b/src/audio_core/renderer/command/performance/performance.h
@@ -0,0 +1,51 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "audio_core/renderer/performance/performance_entry_addresses.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule.
+ */
+struct PerformanceCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// State of the performance
+ PerformanceState state;
+ /// Pointers to be written
+ PerformanceEntryAddresses entry_address;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp
new file mode 100644
index 000000000..1fd90308a
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
+
+namespace AudioCore::AudioRenderer {
+
+void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("DownMix6chTo2chCommand\n\tinputs: ");
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n\toutputs: ";
+ for (u32 i = 0; i < MaxChannels; i++) {
+ string += fmt::format("{:02X}, ", outputs[i]);
+ }
+ string += "\n";
+}
+
+void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) {
+ auto in_front_left{
+ processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)};
+ auto in_front_right{
+ processor.mix_buffers.subspan(inputs[1] * processor.sample_count, processor.sample_count)};
+ auto in_center{
+ processor.mix_buffers.subspan(inputs[2] * processor.sample_count, processor.sample_count)};
+ auto in_lfe{
+ processor.mix_buffers.subspan(inputs[3] * processor.sample_count, processor.sample_count)};
+ auto in_back_left{
+ processor.mix_buffers.subspan(inputs[4] * processor.sample_count, processor.sample_count)};
+ auto in_back_right{
+ processor.mix_buffers.subspan(inputs[5] * processor.sample_count, processor.sample_count)};
+
+ auto out_front_left{
+ processor.mix_buffers.subspan(outputs[0] * processor.sample_count, processor.sample_count)};
+ auto out_front_right{
+ processor.mix_buffers.subspan(outputs[1] * processor.sample_count, processor.sample_count)};
+ auto out_center{
+ processor.mix_buffers.subspan(outputs[2] * processor.sample_count, processor.sample_count)};
+ auto out_lfe{
+ processor.mix_buffers.subspan(outputs[3] * processor.sample_count, processor.sample_count)};
+ auto out_back_left{
+ processor.mix_buffers.subspan(outputs[4] * processor.sample_count, processor.sample_count)};
+ auto out_back_right{
+ processor.mix_buffers.subspan(outputs[5] * processor.sample_count, processor.sample_count)};
+
+ for (u32 i = 0; i < processor.sample_count; i++) {
+ const auto left_sample{(in_front_left[i] * down_mix_coeff[0] +
+ in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
+ in_back_left[i] * down_mix_coeff[3])
+ .to_int()};
+
+ const auto right_sample{(in_front_right[i] * down_mix_coeff[0] +
+ in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
+ in_back_right[i] * down_mix_coeff[3])
+ .to_int()};
+
+ out_front_left[i] = left_sample;
+ out_front_right[i] = right_sample;
+ }
+
+ std::memset(out_center.data(), 0, out_center.size_bytes());
+ std::memset(out_lfe.data(), 0, out_lfe.size_bytes());
+ std::memset(out_back_left.data(), 0, out_back_left.size_bytes());
+ std::memset(out_back_right.data(), 0, out_back_right.size_bytes());
+}
+
+bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h
new file mode 100644
index 000000000..dc133a73b
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for downmixing 6 channels to 2.
+ * Channel layout (SMPTE):
+ * 0 - front left
+ * 1 - front right
+ * 2 - center
+ * 3 - lfe
+ * 4 - back left
+ * 5 - back right
+ */
+struct DownMix6chTo2chCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Input mix buffer offsets for each channel
+ std::array<s16, MaxChannels> inputs;
+ /// Output mix buffer offsets for each channel
+ std::array<s16, MaxChannels> outputs;
+ /// Coefficients used for downmixing
+ std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/resample.cpp b/src/audio_core/renderer/command/resample/resample.cpp
new file mode 100644
index 000000000..070c9d2b8
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/resample.cpp
@@ -0,0 +1,883 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/command/resample/resample.h"
+
+namespace AudioCore::AudioRenderer {
+
+static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input,
+ const Common::FixedPoint<49, 15>& sample_rate_ratio,
+ Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
+ if (sample_rate_ratio == 1.0f) {
+ for (u32 i = 0; i < samples_to_write; i++) {
+ output[i] = input[i];
+ }
+ } else {
+ u32 read_index{0};
+ for (u32 i = 0; i < samples_to_write; i++) {
+ output[i] = input[read_index + (fraction >= 0.5f)];
+ fraction += sample_rate_ratio;
+ read_index += static_cast<u32>(fraction.to_int_floor());
+ fraction.clear_int();
+ }
+ }
+}
+
+static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> input,
+ const Common::FixedPoint<49, 15>& sample_rate_ratio,
+ Common::FixedPoint<49, 15>& fraction,
+ const u32 samples_to_write) {
+ static constexpr std::array<f32, 512> lut0 = {
+ 0.20141602f, 0.59283447f, 0.20513916f, 0.00009155f, 0.19772339f, 0.59277344f, 0.20889282f,
+ 0.00027466f, 0.19406128f, 0.59262085f, 0.21264648f, 0.00045776f, 0.19039917f, 0.59240723f,
+ 0.21646118f, 0.00067139f, 0.18679810f, 0.59213257f, 0.22030640f, 0.00085449f, 0.18322754f,
+ 0.59176636f, 0.22415161f, 0.00103760f, 0.17968750f, 0.59133911f, 0.22802734f, 0.00125122f,
+ 0.17617798f, 0.59085083f, 0.23193359f, 0.00146484f, 0.17269897f, 0.59027100f, 0.23583984f,
+ 0.00167847f, 0.16925049f, 0.58963013f, 0.23977661f, 0.00189209f, 0.16583252f, 0.58892822f,
+ 0.24374390f, 0.00210571f, 0.16244507f, 0.58816528f, 0.24774170f, 0.00234985f, 0.15908813f,
+ 0.58731079f, 0.25173950f, 0.00256348f, 0.15576172f, 0.58639526f, 0.25576782f, 0.00280762f,
+ 0.15249634f, 0.58541870f, 0.25979614f, 0.00308228f, 0.14923096f, 0.58435059f, 0.26385498f,
+ 0.00332642f, 0.14602661f, 0.58325195f, 0.26794434f, 0.00360107f, 0.14285278f, 0.58206177f,
+ 0.27203369f, 0.00387573f, 0.13973999f, 0.58078003f, 0.27612305f, 0.00418091f, 0.13662720f,
+ 0.57946777f, 0.28024292f, 0.00448608f, 0.13357544f, 0.57806396f, 0.28436279f, 0.00479126f,
+ 0.13052368f, 0.57662964f, 0.28851318f, 0.00512695f, 0.12753296f, 0.57510376f, 0.29266357f,
+ 0.00546265f, 0.12460327f, 0.57351685f, 0.29681396f, 0.00579834f, 0.12167358f, 0.57183838f,
+ 0.30099487f, 0.00616455f, 0.11880493f, 0.57012939f, 0.30517578f, 0.00656128f, 0.11596680f,
+ 0.56835938f, 0.30935669f, 0.00695801f, 0.11318970f, 0.56649780f, 0.31353760f, 0.00735474f,
+ 0.11041260f, 0.56457520f, 0.31771851f, 0.00778198f, 0.10769653f, 0.56262207f, 0.32192993f,
+ 0.00823975f, 0.10501099f, 0.56057739f, 0.32614136f, 0.00869751f, 0.10238647f, 0.55847168f,
+ 0.33032227f, 0.00915527f, 0.09976196f, 0.55633545f, 0.33453369f, 0.00967407f, 0.09722900f,
+ 0.55410767f, 0.33874512f, 0.01019287f, 0.09469604f, 0.55181885f, 0.34295654f, 0.01071167f,
+ 0.09222412f, 0.54949951f, 0.34713745f, 0.01126099f, 0.08978271f, 0.54708862f, 0.35134888f,
+ 0.01184082f, 0.08737183f, 0.54464722f, 0.35552979f, 0.01245117f, 0.08499146f, 0.54214478f,
+ 0.35974121f, 0.01306152f, 0.08267212f, 0.53958130f, 0.36392212f, 0.01370239f, 0.08041382f,
+ 0.53695679f, 0.36810303f, 0.01437378f, 0.07815552f, 0.53427124f, 0.37225342f, 0.01507568f,
+ 0.07595825f, 0.53155518f, 0.37640381f, 0.01577759f, 0.07379150f, 0.52877808f, 0.38055420f,
+ 0.01651001f, 0.07165527f, 0.52593994f, 0.38470459f, 0.01727295f, 0.06958008f, 0.52307129f,
+ 0.38882446f, 0.01806641f, 0.06753540f, 0.52014160f, 0.39294434f, 0.01889038f, 0.06552124f,
+ 0.51715088f, 0.39703369f, 0.01974487f, 0.06356812f, 0.51409912f, 0.40112305f, 0.02059937f,
+ 0.06164551f, 0.51101685f, 0.40518188f, 0.02148438f, 0.05975342f, 0.50790405f, 0.40921021f,
+ 0.02243042f, 0.05789185f, 0.50473022f, 0.41323853f, 0.02337646f, 0.05609131f, 0.50152588f,
+ 0.41726685f, 0.02435303f, 0.05432129f, 0.49826050f, 0.42123413f, 0.02539062f, 0.05258179f,
+ 0.49493408f, 0.42520142f, 0.02642822f, 0.05087280f, 0.49160767f, 0.42913818f, 0.02749634f,
+ 0.04922485f, 0.48822021f, 0.43307495f, 0.02859497f, 0.04760742f, 0.48477173f, 0.43695068f,
+ 0.02975464f, 0.04602051f, 0.48132324f, 0.44082642f, 0.03091431f, 0.04446411f, 0.47781372f,
+ 0.44467163f, 0.03210449f, 0.04293823f, 0.47424316f, 0.44845581f, 0.03335571f, 0.04147339f,
+ 0.47067261f, 0.45223999f, 0.03460693f, 0.04003906f, 0.46704102f, 0.45599365f, 0.03591919f,
+ 0.03863525f, 0.46340942f, 0.45971680f, 0.03726196f, 0.03726196f, 0.45971680f, 0.46340942f,
+ 0.03863525f, 0.03591919f, 0.45599365f, 0.46704102f, 0.04003906f, 0.03460693f, 0.45223999f,
+ 0.47067261f, 0.04147339f, 0.03335571f, 0.44845581f, 0.47424316f, 0.04293823f, 0.03210449f,
+ 0.44467163f, 0.47781372f, 0.04446411f, 0.03091431f, 0.44082642f, 0.48132324f, 0.04602051f,
+ 0.02975464f, 0.43695068f, 0.48477173f, 0.04760742f, 0.02859497f, 0.43307495f, 0.48822021f,
+ 0.04922485f, 0.02749634f, 0.42913818f, 0.49160767f, 0.05087280f, 0.02642822f, 0.42520142f,
+ 0.49493408f, 0.05258179f, 0.02539062f, 0.42123413f, 0.49826050f, 0.05432129f, 0.02435303f,
+ 0.41726685f, 0.50152588f, 0.05609131f, 0.02337646f, 0.41323853f, 0.50473022f, 0.05789185f,
+ 0.02243042f, 0.40921021f, 0.50790405f, 0.05975342f, 0.02148438f, 0.40518188f, 0.51101685f,
+ 0.06164551f, 0.02059937f, 0.40112305f, 0.51409912f, 0.06356812f, 0.01974487f, 0.39703369f,
+ 0.51715088f, 0.06552124f, 0.01889038f, 0.39294434f, 0.52014160f, 0.06753540f, 0.01806641f,
+ 0.38882446f, 0.52307129f, 0.06958008f, 0.01727295f, 0.38470459f, 0.52593994f, 0.07165527f,
+ 0.01651001f, 0.38055420f, 0.52877808f, 0.07379150f, 0.01577759f, 0.37640381f, 0.53155518f,
+ 0.07595825f, 0.01507568f, 0.37225342f, 0.53427124f, 0.07815552f, 0.01437378f, 0.36810303f,
+ 0.53695679f, 0.08041382f, 0.01370239f, 0.36392212f, 0.53958130f, 0.08267212f, 0.01306152f,
+ 0.35974121f, 0.54214478f, 0.08499146f, 0.01245117f, 0.35552979f, 0.54464722f, 0.08737183f,
+ 0.01184082f, 0.35134888f, 0.54708862f, 0.08978271f, 0.01126099f, 0.34713745f, 0.54949951f,
+ 0.09222412f, 0.01071167f, 0.34295654f, 0.55181885f, 0.09469604f, 0.01019287f, 0.33874512f,
+ 0.55410767f, 0.09722900f, 0.00967407f, 0.33453369f, 0.55633545f, 0.09976196f, 0.00915527f,
+ 0.33032227f, 0.55847168f, 0.10238647f, 0.00869751f, 0.32614136f, 0.56057739f, 0.10501099f,
+ 0.00823975f, 0.32192993f, 0.56262207f, 0.10769653f, 0.00778198f, 0.31771851f, 0.56457520f,
+ 0.11041260f, 0.00735474f, 0.31353760f, 0.56649780f, 0.11318970f, 0.00695801f, 0.30935669f,
+ 0.56835938f, 0.11596680f, 0.00656128f, 0.30517578f, 0.57012939f, 0.11880493f, 0.00616455f,
+ 0.30099487f, 0.57183838f, 0.12167358f, 0.00579834f, 0.29681396f, 0.57351685f, 0.12460327f,
+ 0.00546265f, 0.29266357f, 0.57510376f, 0.12753296f, 0.00512695f, 0.28851318f, 0.57662964f,
+ 0.13052368f, 0.00479126f, 0.28436279f, 0.57806396f, 0.13357544f, 0.00448608f, 0.28024292f,
+ 0.57946777f, 0.13662720f, 0.00418091f, 0.27612305f, 0.58078003f, 0.13973999f, 0.00387573f,
+ 0.27203369f, 0.58206177f, 0.14285278f, 0.00360107f, 0.26794434f, 0.58325195f, 0.14602661f,
+ 0.00332642f, 0.26385498f, 0.58435059f, 0.14923096f, 0.00308228f, 0.25979614f, 0.58541870f,
+ 0.15249634f, 0.00280762f, 0.25576782f, 0.58639526f, 0.15576172f, 0.00256348f, 0.25173950f,
+ 0.58731079f, 0.15908813f, 0.00234985f, 0.24774170f, 0.58816528f, 0.16244507f, 0.00210571f,
+ 0.24374390f, 0.58892822f, 0.16583252f, 0.00189209f, 0.23977661f, 0.58963013f, 0.16925049f,
+ 0.00167847f, 0.23583984f, 0.59027100f, 0.17269897f, 0.00146484f, 0.23193359f, 0.59085083f,
+ 0.17617798f, 0.00125122f, 0.22802734f, 0.59133911f, 0.17968750f, 0.00103760f, 0.22415161f,
+ 0.59176636f, 0.18322754f, 0.00085449f, 0.22030640f, 0.59213257f, 0.18679810f, 0.00067139f,
+ 0.21646118f, 0.59240723f, 0.19039917f, 0.00045776f, 0.21264648f, 0.59262085f, 0.19406128f,
+ 0.00027466f, 0.20889282f, 0.59277344f, 0.19772339f, 0.00009155f, 0.20513916f, 0.59283447f,
+ 0.20141602f,
+ };
+
+ static constexpr std::array<f32, 512> lut1 = {
+ 0.00207520f, 0.99606323f, 0.00210571f, -0.00015259f, -0.00610352f, 0.99578857f,
+ 0.00646973f, -0.00045776f, -0.01000977f, 0.99526978f, 0.01095581f, -0.00079346f,
+ -0.01373291f, 0.99444580f, 0.01562500f, -0.00109863f, -0.01733398f, 0.99337769f,
+ 0.02041626f, -0.00143433f, -0.02075195f, 0.99203491f, 0.02539062f, -0.00177002f,
+ -0.02404785f, 0.99041748f, 0.03051758f, -0.00210571f, -0.02719116f, 0.98855591f,
+ 0.03582764f, -0.00244141f, -0.03021240f, 0.98641968f, 0.04125977f, -0.00280762f,
+ -0.03308105f, 0.98400879f, 0.04687500f, -0.00314331f, -0.03579712f, 0.98135376f,
+ 0.05261230f, -0.00350952f, -0.03839111f, 0.97842407f, 0.05856323f, -0.00390625f,
+ -0.04083252f, 0.97521973f, 0.06463623f, -0.00427246f, -0.04315186f, 0.97180176f,
+ 0.07086182f, -0.00466919f, -0.04534912f, 0.96810913f, 0.07727051f, -0.00509644f,
+ -0.04742432f, 0.96414185f, 0.08383179f, -0.00549316f, -0.04934692f, 0.95996094f,
+ 0.09054565f, -0.00592041f, -0.05114746f, 0.95550537f, 0.09741211f, -0.00637817f,
+ -0.05285645f, 0.95083618f, 0.10443115f, -0.00683594f, -0.05441284f, 0.94589233f,
+ 0.11160278f, -0.00732422f, -0.05584717f, 0.94073486f, 0.11892700f, -0.00781250f,
+ -0.05718994f, 0.93533325f, 0.12643433f, -0.00830078f, -0.05841064f, 0.92968750f,
+ 0.13406372f, -0.00881958f, -0.05953979f, 0.92382812f, 0.14184570f, -0.00936890f,
+ -0.06054688f, 0.91772461f, 0.14978027f, -0.00991821f, -0.06146240f, 0.91143799f,
+ 0.15783691f, -0.01046753f, -0.06225586f, 0.90490723f, 0.16607666f, -0.01104736f,
+ -0.06295776f, 0.89816284f, 0.17443848f, -0.01165771f, -0.06356812f, 0.89120483f,
+ 0.18292236f, -0.01229858f, -0.06408691f, 0.88403320f, 0.19155884f, -0.01293945f,
+ -0.06451416f, 0.87667847f, 0.20034790f, -0.01358032f, -0.06484985f, 0.86914062f,
+ 0.20925903f, -0.01428223f, -0.06509399f, 0.86138916f, 0.21829224f, -0.01495361f,
+ -0.06527710f, 0.85345459f, 0.22744751f, -0.01568604f, -0.06536865f, 0.84533691f,
+ 0.23675537f, -0.01641846f, -0.06536865f, 0.83703613f, 0.24615479f, -0.01718140f,
+ -0.06533813f, 0.82858276f, 0.25567627f, -0.01794434f, -0.06518555f, 0.81991577f,
+ 0.26531982f, -0.01873779f, -0.06500244f, 0.81112671f, 0.27505493f, -0.01956177f,
+ -0.06472778f, 0.80215454f, 0.28491211f, -0.02038574f, -0.06442261f, 0.79306030f,
+ 0.29489136f, -0.02124023f, -0.06402588f, 0.78378296f, 0.30496216f, -0.02209473f,
+ -0.06359863f, 0.77438354f, 0.31512451f, -0.02297974f, -0.06307983f, 0.76486206f,
+ 0.32537842f, -0.02389526f, -0.06253052f, 0.75518799f, 0.33569336f, -0.02481079f,
+ -0.06195068f, 0.74539185f, 0.34613037f, -0.02575684f, -0.06130981f, 0.73547363f,
+ 0.35662842f, -0.02670288f, -0.06060791f, 0.72543335f, 0.36721802f, -0.02767944f,
+ -0.05987549f, 0.71527100f, 0.37786865f, -0.02865601f, -0.05911255f, 0.70504761f,
+ 0.38858032f, -0.02966309f, -0.05831909f, 0.69470215f, 0.39935303f, -0.03067017f,
+ -0.05746460f, 0.68426514f, 0.41018677f, -0.03170776f, -0.05661011f, 0.67373657f,
+ 0.42108154f, -0.03271484f, -0.05569458f, 0.66311646f, 0.43200684f, -0.03378296f,
+ -0.05477905f, 0.65246582f, 0.44299316f, -0.03482056f, -0.05383301f, 0.64169312f,
+ 0.45401001f, -0.03588867f, -0.05285645f, 0.63088989f, 0.46505737f, -0.03695679f,
+ -0.05187988f, 0.62002563f, 0.47613525f, -0.03802490f, -0.05087280f, 0.60910034f,
+ 0.48721313f, -0.03912354f, -0.04983521f, 0.59814453f, 0.49832153f, -0.04019165f,
+ -0.04879761f, 0.58712769f, 0.50946045f, -0.04129028f, -0.04772949f, 0.57611084f,
+ 0.52056885f, -0.04235840f, -0.04669189f, 0.56503296f, 0.53170776f, -0.04345703f,
+ -0.04562378f, 0.55392456f, 0.54281616f, -0.04452515f, -0.04452515f, 0.54281616f,
+ 0.55392456f, -0.04562378f, -0.04345703f, 0.53170776f, 0.56503296f, -0.04669189f,
+ -0.04235840f, 0.52056885f, 0.57611084f, -0.04772949f, -0.04129028f, 0.50946045f,
+ 0.58712769f, -0.04879761f, -0.04019165f, 0.49832153f, 0.59814453f, -0.04983521f,
+ -0.03912354f, 0.48721313f, 0.60910034f, -0.05087280f, -0.03802490f, 0.47613525f,
+ 0.62002563f, -0.05187988f, -0.03695679f, 0.46505737f, 0.63088989f, -0.05285645f,
+ -0.03588867f, 0.45401001f, 0.64169312f, -0.05383301f, -0.03482056f, 0.44299316f,
+ 0.65246582f, -0.05477905f, -0.03378296f, 0.43200684f, 0.66311646f, -0.05569458f,
+ -0.03271484f, 0.42108154f, 0.67373657f, -0.05661011f, -0.03170776f, 0.41018677f,
+ 0.68426514f, -0.05746460f, -0.03067017f, 0.39935303f, 0.69470215f, -0.05831909f,
+ -0.02966309f, 0.38858032f, 0.70504761f, -0.05911255f, -0.02865601f, 0.37786865f,
+ 0.71527100f, -0.05987549f, -0.02767944f, 0.36721802f, 0.72543335f, -0.06060791f,
+ -0.02670288f, 0.35662842f, 0.73547363f, -0.06130981f, -0.02575684f, 0.34613037f,
+ 0.74539185f, -0.06195068f, -0.02481079f, 0.33569336f, 0.75518799f, -0.06253052f,
+ -0.02389526f, 0.32537842f, 0.76486206f, -0.06307983f, -0.02297974f, 0.31512451f,
+ 0.77438354f, -0.06359863f, -0.02209473f, 0.30496216f, 0.78378296f, -0.06402588f,
+ -0.02124023f, 0.29489136f, 0.79306030f, -0.06442261f, -0.02038574f, 0.28491211f,
+ 0.80215454f, -0.06472778f, -0.01956177f, 0.27505493f, 0.81112671f, -0.06500244f,
+ -0.01873779f, 0.26531982f, 0.81991577f, -0.06518555f, -0.01794434f, 0.25567627f,
+ 0.82858276f, -0.06533813f, -0.01718140f, 0.24615479f, 0.83703613f, -0.06536865f,
+ -0.01641846f, 0.23675537f, 0.84533691f, -0.06536865f, -0.01568604f, 0.22744751f,
+ 0.85345459f, -0.06527710f, -0.01495361f, 0.21829224f, 0.86138916f, -0.06509399f,
+ -0.01428223f, 0.20925903f, 0.86914062f, -0.06484985f, -0.01358032f, 0.20034790f,
+ 0.87667847f, -0.06451416f, -0.01293945f, 0.19155884f, 0.88403320f, -0.06408691f,
+ -0.01229858f, 0.18292236f, 0.89120483f, -0.06356812f, -0.01165771f, 0.17443848f,
+ 0.89816284f, -0.06295776f, -0.01104736f, 0.16607666f, 0.90490723f, -0.06225586f,
+ -0.01046753f, 0.15783691f, 0.91143799f, -0.06146240f, -0.00991821f, 0.14978027f,
+ 0.91772461f, -0.06054688f, -0.00936890f, 0.14184570f, 0.92382812f, -0.05953979f,
+ -0.00881958f, 0.13406372f, 0.92968750f, -0.05841064f, -0.00830078f, 0.12643433f,
+ 0.93533325f, -0.05718994f, -0.00781250f, 0.11892700f, 0.94073486f, -0.05584717f,
+ -0.00732422f, 0.11160278f, 0.94589233f, -0.05441284f, -0.00683594f, 0.10443115f,
+ 0.95083618f, -0.05285645f, -0.00637817f, 0.09741211f, 0.95550537f, -0.05114746f,
+ -0.00592041f, 0.09054565f, 0.95996094f, -0.04934692f, -0.00549316f, 0.08383179f,
+ 0.96414185f, -0.04742432f, -0.00509644f, 0.07727051f, 0.96810913f, -0.04534912f,
+ -0.00466919f, 0.07086182f, 0.97180176f, -0.04315186f, -0.00427246f, 0.06463623f,
+ 0.97521973f, -0.04083252f, -0.00390625f, 0.05856323f, 0.97842407f, -0.03839111f,
+ -0.00350952f, 0.05261230f, 0.98135376f, -0.03579712f, -0.00314331f, 0.04687500f,
+ 0.98400879f, -0.03308105f, -0.00280762f, 0.04125977f, 0.98641968f, -0.03021240f,
+ -0.00244141f, 0.03582764f, 0.98855591f, -0.02719116f, -0.00210571f, 0.03051758f,
+ 0.99041748f, -0.02404785f, -0.00177002f, 0.02539062f, 0.99203491f, -0.02075195f,
+ -0.00143433f, 0.02041626f, 0.99337769f, -0.01733398f, -0.00109863f, 0.01562500f,
+ 0.99444580f, -0.01373291f, -0.00079346f, 0.01095581f, 0.99526978f, -0.01000977f,
+ -0.00045776f, 0.00646973f, 0.99578857f, -0.00610352f, -0.00015259f, 0.00210571f,
+ 0.99606323f, -0.00207520f,
+ };
+
+ static constexpr std::array<f32, 512> lut2 = {
+ 0.09750366f, 0.80221558f, 0.10159302f, -0.00097656f, 0.09350586f, 0.80203247f,
+ 0.10580444f, -0.00103760f, 0.08959961f, 0.80169678f, 0.11010742f, -0.00115967f,
+ 0.08578491f, 0.80117798f, 0.11447144f, -0.00128174f, 0.08203125f, 0.80047607f,
+ 0.11892700f, -0.00140381f, 0.07836914f, 0.79962158f, 0.12347412f, -0.00152588f,
+ 0.07479858f, 0.79861450f, 0.12814331f, -0.00164795f, 0.07135010f, 0.79742432f,
+ 0.13287354f, -0.00177002f, 0.06796265f, 0.79605103f, 0.13769531f, -0.00192261f,
+ 0.06469727f, 0.79452515f, 0.14260864f, -0.00204468f, 0.06149292f, 0.79284668f,
+ 0.14761353f, -0.00219727f, 0.05834961f, 0.79098511f, 0.15270996f, -0.00231934f,
+ 0.05532837f, 0.78894043f, 0.15789795f, -0.00247192f, 0.05236816f, 0.78674316f,
+ 0.16317749f, -0.00265503f, 0.04949951f, 0.78442383f, 0.16851807f, -0.00280762f,
+ 0.04672241f, 0.78189087f, 0.17398071f, -0.00299072f, 0.04400635f, 0.77920532f,
+ 0.17950439f, -0.00314331f, 0.04141235f, 0.77636719f, 0.18511963f, -0.00332642f,
+ 0.03887939f, 0.77337646f, 0.19082642f, -0.00350952f, 0.03640747f, 0.77023315f,
+ 0.19659424f, -0.00369263f, 0.03402710f, 0.76693726f, 0.20248413f, -0.00387573f,
+ 0.03173828f, 0.76348877f, 0.20843506f, -0.00405884f, 0.02951050f, 0.75985718f,
+ 0.21444702f, -0.00427246f, 0.02737427f, 0.75610352f, 0.22055054f, -0.00445557f,
+ 0.02529907f, 0.75219727f, 0.22674561f, -0.00466919f, 0.02331543f, 0.74816895f,
+ 0.23300171f, -0.00485229f, 0.02139282f, 0.74398804f, 0.23931885f, -0.00506592f,
+ 0.01956177f, 0.73965454f, 0.24572754f, -0.00531006f, 0.01779175f, 0.73519897f,
+ 0.25219727f, -0.00552368f, 0.01605225f, 0.73059082f, 0.25872803f, -0.00570679f,
+ 0.01440430f, 0.72586060f, 0.26535034f, -0.00592041f, 0.01281738f, 0.72100830f,
+ 0.27203369f, -0.00616455f, 0.01132202f, 0.71600342f, 0.27877808f, -0.00637817f,
+ 0.00988770f, 0.71090698f, 0.28558350f, -0.00656128f, 0.00851440f, 0.70565796f,
+ 0.29244995f, -0.00677490f, 0.00720215f, 0.70031738f, 0.29934692f, -0.00701904f,
+ 0.00592041f, 0.69485474f, 0.30633545f, -0.00723267f, 0.00469971f, 0.68927002f,
+ 0.31338501f, -0.00741577f, 0.00357056f, 0.68356323f, 0.32046509f, -0.00762939f,
+ 0.00247192f, 0.67773438f, 0.32760620f, -0.00787354f, 0.00143433f, 0.67184448f,
+ 0.33477783f, -0.00808716f, 0.00045776f, 0.66583252f, 0.34197998f, -0.00827026f,
+ -0.00048828f, 0.65972900f, 0.34924316f, -0.00845337f, -0.00134277f, 0.65353394f,
+ 0.35656738f, -0.00863647f, -0.00216675f, 0.64721680f, 0.36389160f, -0.00885010f,
+ -0.00296021f, 0.64083862f, 0.37127686f, -0.00903320f, -0.00369263f, 0.63433838f,
+ 0.37869263f, -0.00921631f, -0.00436401f, 0.62777710f, 0.38613892f, -0.00933838f,
+ -0.00497437f, 0.62115479f, 0.39361572f, -0.00949097f, -0.00558472f, 0.61444092f,
+ 0.40109253f, -0.00964355f, -0.00613403f, 0.60763550f, 0.40859985f, -0.00979614f,
+ -0.00665283f, 0.60076904f, 0.41610718f, -0.00991821f, -0.00714111f, 0.59384155f,
+ 0.42364502f, -0.01000977f, -0.00756836f, 0.58685303f, 0.43121338f, -0.01013184f,
+ -0.00796509f, 0.57977295f, 0.43875122f, -0.01022339f, -0.00833130f, 0.57266235f,
+ 0.44631958f, -0.01028442f, -0.00866699f, 0.56552124f, 0.45388794f, -0.01034546f,
+ -0.00897217f, 0.55831909f, 0.46145630f, -0.01040649f, -0.00921631f, 0.55105591f,
+ 0.46902466f, -0.01040649f, -0.00946045f, 0.54373169f, 0.47659302f, -0.01040649f,
+ -0.00967407f, 0.53640747f, 0.48413086f, -0.01037598f, -0.00985718f, 0.52902222f,
+ 0.49166870f, -0.01037598f, -0.01000977f, 0.52160645f, 0.49917603f, -0.01031494f,
+ -0.01013184f, 0.51416016f, 0.50668335f, -0.01025391f, -0.01025391f, 0.50668335f,
+ 0.51416016f, -0.01013184f, -0.01031494f, 0.49917603f, 0.52160645f, -0.01000977f,
+ -0.01037598f, 0.49166870f, 0.52902222f, -0.00985718f, -0.01037598f, 0.48413086f,
+ 0.53640747f, -0.00967407f, -0.01040649f, 0.47659302f, 0.54373169f, -0.00946045f,
+ -0.01040649f, 0.46902466f, 0.55105591f, -0.00921631f, -0.01040649f, 0.46145630f,
+ 0.55831909f, -0.00897217f, -0.01034546f, 0.45388794f, 0.56552124f, -0.00866699f,
+ -0.01028442f, 0.44631958f, 0.57266235f, -0.00833130f, -0.01022339f, 0.43875122f,
+ 0.57977295f, -0.00796509f, -0.01013184f, 0.43121338f, 0.58685303f, -0.00756836f,
+ -0.01000977f, 0.42364502f, 0.59384155f, -0.00714111f, -0.00991821f, 0.41610718f,
+ 0.60076904f, -0.00665283f, -0.00979614f, 0.40859985f, 0.60763550f, -0.00613403f,
+ -0.00964355f, 0.40109253f, 0.61444092f, -0.00558472f, -0.00949097f, 0.39361572f,
+ 0.62115479f, -0.00497437f, -0.00933838f, 0.38613892f, 0.62777710f, -0.00436401f,
+ -0.00921631f, 0.37869263f, 0.63433838f, -0.00369263f, -0.00903320f, 0.37127686f,
+ 0.64083862f, -0.00296021f, -0.00885010f, 0.36389160f, 0.64721680f, -0.00216675f,
+ -0.00863647f, 0.35656738f, 0.65353394f, -0.00134277f, -0.00845337f, 0.34924316f,
+ 0.65972900f, -0.00048828f, -0.00827026f, 0.34197998f, 0.66583252f, 0.00045776f,
+ -0.00808716f, 0.33477783f, 0.67184448f, 0.00143433f, -0.00787354f, 0.32760620f,
+ 0.67773438f, 0.00247192f, -0.00762939f, 0.32046509f, 0.68356323f, 0.00357056f,
+ -0.00741577f, 0.31338501f, 0.68927002f, 0.00469971f, -0.00723267f, 0.30633545f,
+ 0.69485474f, 0.00592041f, -0.00701904f, 0.29934692f, 0.70031738f, 0.00720215f,
+ -0.00677490f, 0.29244995f, 0.70565796f, 0.00851440f, -0.00656128f, 0.28558350f,
+ 0.71090698f, 0.00988770f, -0.00637817f, 0.27877808f, 0.71600342f, 0.01132202f,
+ -0.00616455f, 0.27203369f, 0.72100830f, 0.01281738f, -0.00592041f, 0.26535034f,
+ 0.72586060f, 0.01440430f, -0.00570679f, 0.25872803f, 0.73059082f, 0.01605225f,
+ -0.00552368f, 0.25219727f, 0.73519897f, 0.01779175f, -0.00531006f, 0.24572754f,
+ 0.73965454f, 0.01956177f, -0.00506592f, 0.23931885f, 0.74398804f, 0.02139282f,
+ -0.00485229f, 0.23300171f, 0.74816895f, 0.02331543f, -0.00466919f, 0.22674561f,
+ 0.75219727f, 0.02529907f, -0.00445557f, 0.22055054f, 0.75610352f, 0.02737427f,
+ -0.00427246f, 0.21444702f, 0.75985718f, 0.02951050f, -0.00405884f, 0.20843506f,
+ 0.76348877f, 0.03173828f, -0.00387573f, 0.20248413f, 0.76693726f, 0.03402710f,
+ -0.00369263f, 0.19659424f, 0.77023315f, 0.03640747f, -0.00350952f, 0.19082642f,
+ 0.77337646f, 0.03887939f, -0.00332642f, 0.18511963f, 0.77636719f, 0.04141235f,
+ -0.00314331f, 0.17950439f, 0.77920532f, 0.04400635f, -0.00299072f, 0.17398071f,
+ 0.78189087f, 0.04672241f, -0.00280762f, 0.16851807f, 0.78442383f, 0.04949951f,
+ -0.00265503f, 0.16317749f, 0.78674316f, 0.05236816f, -0.00247192f, 0.15789795f,
+ 0.78894043f, 0.05532837f, -0.00231934f, 0.15270996f, 0.79098511f, 0.05834961f,
+ -0.00219727f, 0.14761353f, 0.79284668f, 0.06149292f, -0.00204468f, 0.14260864f,
+ 0.79452515f, 0.06469727f, -0.00192261f, 0.13769531f, 0.79605103f, 0.06796265f,
+ -0.00177002f, 0.13287354f, 0.79742432f, 0.07135010f, -0.00164795f, 0.12814331f,
+ 0.79861450f, 0.07479858f, -0.00152588f, 0.12347412f, 0.79962158f, 0.07836914f,
+ -0.00140381f, 0.11892700f, 0.80047607f, 0.08203125f, -0.00128174f, 0.11447144f,
+ 0.80117798f, 0.08578491f, -0.00115967f, 0.11010742f, 0.80169678f, 0.08959961f,
+ -0.00103760f, 0.10580444f, 0.80203247f, 0.09350586f, -0.00097656f, 0.10159302f,
+ 0.80221558f, 0.09750366f,
+ };
+
+ const auto get_lut = [&]() -> std::span<const f32> {
+ if (sample_rate_ratio <= 1.0f) {
+ return std::span<const f32>(lut2.data(), lut2.size());
+ } else if (sample_rate_ratio < 1.3f) {
+ return std::span<const f32>(lut1.data(), lut1.size());
+ } else {
+ return std::span<const f32>(lut0.data(), lut0.size());
+ }
+ };
+
+ auto lut{get_lut()};
+ u32 read_index{0};
+ for (u32 i = 0; i < samples_to_write; i++) {
+ const auto lut_index{(fraction.get_frac() >> 8) * 4};
+ const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
+ const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
+ const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
+ const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]};
+ output[i] = (sample0 + sample1 + sample2 + sample3).to_int_floor();
+ fraction += sample_rate_ratio;
+ read_index += static_cast<u32>(fraction.to_int_floor());
+ fraction.clear_int();
+ }
+}
+
+static void ResampleHighQuality(std::span<s32> output, std::span<const s16> input,
+ const Common::FixedPoint<49, 15>& sample_rate_ratio,
+ Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
+ static constexpr std::array<f32, 1024> lut0 = {
+ -0.01776123f, -0.00070190f, 0.26672363f, 0.50006104f, 0.26956177f, 0.00024414f,
+ -0.01800537f, 0.00000000f, -0.01748657f, -0.00164795f, 0.26388550f, 0.50003052f,
+ 0.27236938f, 0.00122070f, -0.01824951f, -0.00003052f, -0.01724243f, -0.00256348f,
+ 0.26107788f, 0.49996948f, 0.27520752f, 0.00219727f, -0.01849365f, -0.00003052f,
+ -0.01699829f, -0.00344849f, 0.25823975f, 0.49984741f, 0.27801514f, 0.00320435f,
+ -0.01873779f, -0.00006104f, -0.01675415f, -0.00433350f, 0.25543213f, 0.49972534f,
+ 0.28085327f, 0.00424194f, -0.01898193f, -0.00006104f, -0.01651001f, -0.00518799f,
+ 0.25259399f, 0.49954224f, 0.28366089f, 0.00527954f, -0.01922607f, -0.00009155f,
+ -0.01626587f, -0.00604248f, 0.24978638f, 0.49932861f, 0.28646851f, 0.00634766f,
+ -0.01947021f, -0.00012207f, -0.01602173f, -0.00686646f, 0.24697876f, 0.49908447f,
+ 0.28930664f, 0.00744629f, -0.01971436f, -0.00015259f, -0.01574707f, -0.00765991f,
+ 0.24414062f, 0.49877930f, 0.29211426f, 0.00854492f, -0.01995850f, -0.00015259f,
+ -0.01550293f, -0.00845337f, 0.24133301f, 0.49847412f, 0.29492188f, 0.00967407f,
+ -0.02020264f, -0.00018311f, -0.01525879f, -0.00921631f, 0.23852539f, 0.49810791f,
+ 0.29772949f, 0.01083374f, -0.02044678f, -0.00021362f, -0.01501465f, -0.00997925f,
+ 0.23571777f, 0.49774170f, 0.30050659f, 0.01199341f, -0.02069092f, -0.00024414f,
+ -0.01477051f, -0.01071167f, 0.23291016f, 0.49731445f, 0.30331421f, 0.01318359f,
+ -0.02093506f, -0.00027466f, -0.01452637f, -0.01141357f, 0.23010254f, 0.49685669f,
+ 0.30609131f, 0.01437378f, -0.02117920f, -0.00030518f, -0.01428223f, -0.01211548f,
+ 0.22732544f, 0.49636841f, 0.30886841f, 0.01559448f, -0.02142334f, -0.00033569f,
+ -0.01403809f, -0.01278687f, 0.22451782f, 0.49581909f, 0.31164551f, 0.01684570f,
+ -0.02163696f, -0.00039673f, -0.01379395f, -0.01345825f, 0.22174072f, 0.49526978f,
+ 0.31442261f, 0.01809692f, -0.02188110f, -0.00042725f, -0.01358032f, -0.01409912f,
+ 0.21896362f, 0.49465942f, 0.31719971f, 0.01937866f, -0.02209473f, -0.00045776f,
+ -0.01333618f, -0.01473999f, 0.21618652f, 0.49404907f, 0.31994629f, 0.02069092f,
+ -0.02233887f, -0.00048828f, -0.01309204f, -0.01535034f, 0.21343994f, 0.49337769f,
+ 0.32269287f, 0.02203369f, -0.02255249f, -0.00054932f, -0.01284790f, -0.01596069f,
+ 0.21066284f, 0.49267578f, 0.32543945f, 0.02337646f, -0.02279663f, -0.00057983f,
+ -0.01263428f, -0.01654053f, 0.20791626f, 0.49194336f, 0.32818604f, 0.02471924f,
+ -0.02301025f, -0.00064087f, -0.01239014f, -0.01708984f, 0.20516968f, 0.49118042f,
+ 0.33090210f, 0.02612305f, -0.02322388f, -0.00067139f, -0.01214600f, -0.01763916f,
+ 0.20242310f, 0.49035645f, 0.33361816f, 0.02752686f, -0.02343750f, -0.00073242f,
+ -0.01193237f, -0.01818848f, 0.19970703f, 0.48953247f, 0.33633423f, 0.02896118f,
+ -0.02365112f, -0.00079346f, -0.01168823f, -0.01867676f, 0.19696045f, 0.48864746f,
+ 0.33901978f, 0.03039551f, -0.02386475f, -0.00082397f, -0.01147461f, -0.01919556f,
+ 0.19427490f, 0.48776245f, 0.34170532f, 0.03186035f, -0.02407837f, -0.00088501f,
+ -0.01123047f, -0.01968384f, 0.19155884f, 0.48681641f, 0.34439087f, 0.03335571f,
+ -0.02429199f, -0.00094604f, -0.01101685f, -0.02014160f, 0.18887329f, 0.48583984f,
+ 0.34704590f, 0.03485107f, -0.02447510f, -0.00100708f, -0.01080322f, -0.02059937f,
+ 0.18615723f, 0.48483276f, 0.34970093f, 0.03637695f, -0.02468872f, -0.00106812f,
+ -0.01058960f, -0.02102661f, 0.18350220f, 0.48379517f, 0.35235596f, 0.03793335f,
+ -0.02487183f, -0.00112915f, -0.01034546f, -0.02145386f, 0.18081665f, 0.48272705f,
+ 0.35498047f, 0.03948975f, -0.02505493f, -0.00119019f, -0.01013184f, -0.02188110f,
+ 0.17816162f, 0.48162842f, 0.35760498f, 0.04107666f, -0.02523804f, -0.00125122f,
+ -0.00991821f, -0.02227783f, 0.17550659f, 0.48049927f, 0.36019897f, 0.04269409f,
+ -0.02542114f, -0.00131226f, -0.00970459f, -0.02264404f, 0.17288208f, 0.47933960f,
+ 0.36279297f, 0.04431152f, -0.02560425f, -0.00140381f, -0.00952148f, -0.02301025f,
+ 0.17025757f, 0.47814941f, 0.36538696f, 0.04595947f, -0.02578735f, -0.00146484f,
+ -0.00930786f, -0.02337646f, 0.16763306f, 0.47689819f, 0.36795044f, 0.04763794f,
+ -0.02593994f, -0.00152588f, -0.00909424f, -0.02371216f, 0.16503906f, 0.47564697f,
+ 0.37048340f, 0.04931641f, -0.02609253f, -0.00161743f, -0.00888062f, -0.02401733f,
+ 0.16244507f, 0.47436523f, 0.37304688f, 0.05102539f, -0.02627563f, -0.00170898f,
+ -0.00869751f, -0.02435303f, 0.15988159f, 0.47302246f, 0.37554932f, 0.05276489f,
+ -0.02642822f, -0.00177002f, -0.00848389f, -0.02462769f, 0.15731812f, 0.47167969f,
+ 0.37805176f, 0.05450439f, -0.02658081f, -0.00186157f, -0.00830078f, -0.02493286f,
+ 0.15475464f, 0.47027588f, 0.38055420f, 0.05627441f, -0.02670288f, -0.00195312f,
+ -0.00808716f, -0.02520752f, 0.15222168f, 0.46887207f, 0.38302612f, 0.05804443f,
+ -0.02685547f, -0.00204468f, -0.00790405f, -0.02545166f, 0.14968872f, 0.46743774f,
+ 0.38546753f, 0.05987549f, -0.02697754f, -0.00213623f, -0.00772095f, -0.02569580f,
+ 0.14718628f, 0.46594238f, 0.38790894f, 0.06170654f, -0.02709961f, -0.00222778f,
+ -0.00753784f, -0.02593994f, 0.14468384f, 0.46444702f, 0.39031982f, 0.06353760f,
+ -0.02722168f, -0.00231934f, -0.00735474f, -0.02615356f, 0.14218140f, 0.46289062f,
+ 0.39273071f, 0.06539917f, -0.02734375f, -0.00241089f, -0.00717163f, -0.02636719f,
+ 0.13970947f, 0.46133423f, 0.39511108f, 0.06729126f, -0.02743530f, -0.00250244f,
+ -0.00698853f, -0.02655029f, 0.13726807f, 0.45974731f, 0.39749146f, 0.06918335f,
+ -0.02755737f, -0.00259399f, -0.00680542f, -0.02673340f, 0.13479614f, 0.45812988f,
+ 0.39984131f, 0.07113647f, -0.02764893f, -0.00271606f, -0.00662231f, -0.02691650f,
+ 0.13238525f, 0.45648193f, 0.40216064f, 0.07305908f, -0.02774048f, -0.00280762f,
+ -0.00643921f, -0.02706909f, 0.12997437f, 0.45480347f, 0.40447998f, 0.07504272f,
+ -0.02780151f, -0.00292969f, -0.00628662f, -0.02722168f, 0.12756348f, 0.45309448f,
+ 0.40676880f, 0.07699585f, -0.02789307f, -0.00305176f, -0.00610352f, -0.02734375f,
+ 0.12518311f, 0.45135498f, 0.40902710f, 0.07901001f, -0.02795410f, -0.00314331f,
+ -0.00595093f, -0.02746582f, 0.12280273f, 0.44958496f, 0.41128540f, 0.08102417f,
+ -0.02801514f, -0.00326538f, -0.00579834f, -0.02758789f, 0.12045288f, 0.44778442f,
+ 0.41351318f, 0.08306885f, -0.02804565f, -0.00338745f, -0.00561523f, -0.02770996f,
+ 0.11813354f, 0.44598389f, 0.41571045f, 0.08511353f, -0.02810669f, -0.00350952f,
+ -0.00546265f, -0.02780151f, 0.11581421f, 0.44412231f, 0.41787720f, 0.08718872f,
+ -0.02813721f, -0.00363159f, -0.00531006f, -0.02786255f, 0.11349487f, 0.44226074f,
+ 0.42004395f, 0.08929443f, -0.02816772f, -0.00375366f, -0.00515747f, -0.02795410f,
+ 0.11120605f, 0.44036865f, 0.42218018f, 0.09140015f, -0.02816772f, -0.00387573f,
+ -0.00500488f, -0.02801514f, 0.10894775f, 0.43844604f, 0.42431641f, 0.09353638f,
+ -0.02819824f, -0.00402832f, -0.00485229f, -0.02807617f, 0.10668945f, 0.43649292f,
+ 0.42639160f, 0.09570312f, -0.02819824f, -0.00415039f, -0.00469971f, -0.02810669f,
+ 0.10446167f, 0.43453979f, 0.42846680f, 0.09786987f, -0.02819824f, -0.00427246f,
+ -0.00457764f, -0.02813721f, 0.10223389f, 0.43252563f, 0.43051147f, 0.10003662f,
+ -0.02816772f, -0.00442505f, -0.00442505f, -0.02816772f, 0.10003662f, 0.43051147f,
+ 0.43252563f, 0.10223389f, -0.02813721f, -0.00457764f, -0.00427246f, -0.02819824f,
+ 0.09786987f, 0.42846680f, 0.43453979f, 0.10446167f, -0.02810669f, -0.00469971f,
+ -0.00415039f, -0.02819824f, 0.09570312f, 0.42639160f, 0.43649292f, 0.10668945f,
+ -0.02807617f, -0.00485229f, -0.00402832f, -0.02819824f, 0.09353638f, 0.42431641f,
+ 0.43844604f, 0.10894775f, -0.02801514f, -0.00500488f, -0.00387573f, -0.02816772f,
+ 0.09140015f, 0.42218018f, 0.44036865f, 0.11120605f, -0.02795410f, -0.00515747f,
+ -0.00375366f, -0.02816772f, 0.08929443f, 0.42004395f, 0.44226074f, 0.11349487f,
+ -0.02786255f, -0.00531006f, -0.00363159f, -0.02813721f, 0.08718872f, 0.41787720f,
+ 0.44412231f, 0.11581421f, -0.02780151f, -0.00546265f, -0.00350952f, -0.02810669f,
+ 0.08511353f, 0.41571045f, 0.44598389f, 0.11813354f, -0.02770996f, -0.00561523f,
+ -0.00338745f, -0.02804565f, 0.08306885f, 0.41351318f, 0.44778442f, 0.12045288f,
+ -0.02758789f, -0.00579834f, -0.00326538f, -0.02801514f, 0.08102417f, 0.41128540f,
+ 0.44958496f, 0.12280273f, -0.02746582f, -0.00595093f, -0.00314331f, -0.02795410f,
+ 0.07901001f, 0.40902710f, 0.45135498f, 0.12518311f, -0.02734375f, -0.00610352f,
+ -0.00305176f, -0.02789307f, 0.07699585f, 0.40676880f, 0.45309448f, 0.12756348f,
+ -0.02722168f, -0.00628662f, -0.00292969f, -0.02780151f, 0.07504272f, 0.40447998f,
+ 0.45480347f, 0.12997437f, -0.02706909f, -0.00643921f, -0.00280762f, -0.02774048f,
+ 0.07305908f, 0.40216064f, 0.45648193f, 0.13238525f, -0.02691650f, -0.00662231f,
+ -0.00271606f, -0.02764893f, 0.07113647f, 0.39984131f, 0.45812988f, 0.13479614f,
+ -0.02673340f, -0.00680542f, -0.00259399f, -0.02755737f, 0.06918335f, 0.39749146f,
+ 0.45974731f, 0.13726807f, -0.02655029f, -0.00698853f, -0.00250244f, -0.02743530f,
+ 0.06729126f, 0.39511108f, 0.46133423f, 0.13970947f, -0.02636719f, -0.00717163f,
+ -0.00241089f, -0.02734375f, 0.06539917f, 0.39273071f, 0.46289062f, 0.14218140f,
+ -0.02615356f, -0.00735474f, -0.00231934f, -0.02722168f, 0.06353760f, 0.39031982f,
+ 0.46444702f, 0.14468384f, -0.02593994f, -0.00753784f, -0.00222778f, -0.02709961f,
+ 0.06170654f, 0.38790894f, 0.46594238f, 0.14718628f, -0.02569580f, -0.00772095f,
+ -0.00213623f, -0.02697754f, 0.05987549f, 0.38546753f, 0.46743774f, 0.14968872f,
+ -0.02545166f, -0.00790405f, -0.00204468f, -0.02685547f, 0.05804443f, 0.38302612f,
+ 0.46887207f, 0.15222168f, -0.02520752f, -0.00808716f, -0.00195312f, -0.02670288f,
+ 0.05627441f, 0.38055420f, 0.47027588f, 0.15475464f, -0.02493286f, -0.00830078f,
+ -0.00186157f, -0.02658081f, 0.05450439f, 0.37805176f, 0.47167969f, 0.15731812f,
+ -0.02462769f, -0.00848389f, -0.00177002f, -0.02642822f, 0.05276489f, 0.37554932f,
+ 0.47302246f, 0.15988159f, -0.02435303f, -0.00869751f, -0.00170898f, -0.02627563f,
+ 0.05102539f, 0.37304688f, 0.47436523f, 0.16244507f, -0.02401733f, -0.00888062f,
+ -0.00161743f, -0.02609253f, 0.04931641f, 0.37048340f, 0.47564697f, 0.16503906f,
+ -0.02371216f, -0.00909424f, -0.00152588f, -0.02593994f, 0.04763794f, 0.36795044f,
+ 0.47689819f, 0.16763306f, -0.02337646f, -0.00930786f, -0.00146484f, -0.02578735f,
+ 0.04595947f, 0.36538696f, 0.47814941f, 0.17025757f, -0.02301025f, -0.00952148f,
+ -0.00140381f, -0.02560425f, 0.04431152f, 0.36279297f, 0.47933960f, 0.17288208f,
+ -0.02264404f, -0.00970459f, -0.00131226f, -0.02542114f, 0.04269409f, 0.36019897f,
+ 0.48049927f, 0.17550659f, -0.02227783f, -0.00991821f, -0.00125122f, -0.02523804f,
+ 0.04107666f, 0.35760498f, 0.48162842f, 0.17816162f, -0.02188110f, -0.01013184f,
+ -0.00119019f, -0.02505493f, 0.03948975f, 0.35498047f, 0.48272705f, 0.18081665f,
+ -0.02145386f, -0.01034546f, -0.00112915f, -0.02487183f, 0.03793335f, 0.35235596f,
+ 0.48379517f, 0.18350220f, -0.02102661f, -0.01058960f, -0.00106812f, -0.02468872f,
+ 0.03637695f, 0.34970093f, 0.48483276f, 0.18615723f, -0.02059937f, -0.01080322f,
+ -0.00100708f, -0.02447510f, 0.03485107f, 0.34704590f, 0.48583984f, 0.18887329f,
+ -0.02014160f, -0.01101685f, -0.00094604f, -0.02429199f, 0.03335571f, 0.34439087f,
+ 0.48681641f, 0.19155884f, -0.01968384f, -0.01123047f, -0.00088501f, -0.02407837f,
+ 0.03186035f, 0.34170532f, 0.48776245f, 0.19427490f, -0.01919556f, -0.01147461f,
+ -0.00082397f, -0.02386475f, 0.03039551f, 0.33901978f, 0.48864746f, 0.19696045f,
+ -0.01867676f, -0.01168823f, -0.00079346f, -0.02365112f, 0.02896118f, 0.33633423f,
+ 0.48953247f, 0.19970703f, -0.01818848f, -0.01193237f, -0.00073242f, -0.02343750f,
+ 0.02752686f, 0.33361816f, 0.49035645f, 0.20242310f, -0.01763916f, -0.01214600f,
+ -0.00067139f, -0.02322388f, 0.02612305f, 0.33090210f, 0.49118042f, 0.20516968f,
+ -0.01708984f, -0.01239014f, -0.00064087f, -0.02301025f, 0.02471924f, 0.32818604f,
+ 0.49194336f, 0.20791626f, -0.01654053f, -0.01263428f, -0.00057983f, -0.02279663f,
+ 0.02337646f, 0.32543945f, 0.49267578f, 0.21066284f, -0.01596069f, -0.01284790f,
+ -0.00054932f, -0.02255249f, 0.02203369f, 0.32269287f, 0.49337769f, 0.21343994f,
+ -0.01535034f, -0.01309204f, -0.00048828f, -0.02233887f, 0.02069092f, 0.31994629f,
+ 0.49404907f, 0.21618652f, -0.01473999f, -0.01333618f, -0.00045776f, -0.02209473f,
+ 0.01937866f, 0.31719971f, 0.49465942f, 0.21896362f, -0.01409912f, -0.01358032f,
+ -0.00042725f, -0.02188110f, 0.01809692f, 0.31442261f, 0.49526978f, 0.22174072f,
+ -0.01345825f, -0.01379395f, -0.00039673f, -0.02163696f, 0.01684570f, 0.31164551f,
+ 0.49581909f, 0.22451782f, -0.01278687f, -0.01403809f, -0.00033569f, -0.02142334f,
+ 0.01559448f, 0.30886841f, 0.49636841f, 0.22732544f, -0.01211548f, -0.01428223f,
+ -0.00030518f, -0.02117920f, 0.01437378f, 0.30609131f, 0.49685669f, 0.23010254f,
+ -0.01141357f, -0.01452637f, -0.00027466f, -0.02093506f, 0.01318359f, 0.30331421f,
+ 0.49731445f, 0.23291016f, -0.01071167f, -0.01477051f, -0.00024414f, -0.02069092f,
+ 0.01199341f, 0.30050659f, 0.49774170f, 0.23571777f, -0.00997925f, -0.01501465f,
+ -0.00021362f, -0.02044678f, 0.01083374f, 0.29772949f, 0.49810791f, 0.23852539f,
+ -0.00921631f, -0.01525879f, -0.00018311f, -0.02020264f, 0.00967407f, 0.29492188f,
+ 0.49847412f, 0.24133301f, -0.00845337f, -0.01550293f, -0.00015259f, -0.01995850f,
+ 0.00854492f, 0.29211426f, 0.49877930f, 0.24414062f, -0.00765991f, -0.01574707f,
+ -0.00015259f, -0.01971436f, 0.00744629f, 0.28930664f, 0.49908447f, 0.24697876f,
+ -0.00686646f, -0.01602173f, -0.00012207f, -0.01947021f, 0.00634766f, 0.28646851f,
+ 0.49932861f, 0.24978638f, -0.00604248f, -0.01626587f, -0.00009155f, -0.01922607f,
+ 0.00527954f, 0.28366089f, 0.49954224f, 0.25259399f, -0.00518799f, -0.01651001f,
+ -0.00006104f, -0.01898193f, 0.00424194f, 0.28085327f, 0.49972534f, 0.25543213f,
+ -0.00433350f, -0.01675415f, -0.00006104f, -0.01873779f, 0.00320435f, 0.27801514f,
+ 0.49984741f, 0.25823975f, -0.00344849f, -0.01699829f, -0.00003052f, -0.01849365f,
+ 0.00219727f, 0.27520752f, 0.49996948f, 0.26107788f, -0.00256348f, -0.01724243f,
+ -0.00003052f, -0.01824951f, 0.00122070f, 0.27236938f, 0.50003052f, 0.26388550f,
+ -0.00164795f, -0.01748657f, 0.00000000f, -0.01800537f, 0.00024414f, 0.26956177f,
+ 0.50006104f, 0.26672363f, -0.00070190f, -0.01776123f,
+ };
+
+ static constexpr std::array<f32, 1024> lut1 = {
+ 0.01275635f, -0.07745361f, 0.18670654f, 0.75119019f, 0.19219971f, -0.07821655f,
+ 0.01272583f, 0.00000000f, 0.01281738f, -0.07666016f, 0.18124390f, 0.75106812f,
+ 0.19772339f, -0.07897949f, 0.01266479f, 0.00003052f, 0.01284790f, -0.07583618f,
+ 0.17581177f, 0.75088501f, 0.20330811f, -0.07971191f, 0.01257324f, 0.00006104f,
+ 0.01287842f, -0.07501221f, 0.17044067f, 0.75057983f, 0.20892334f, -0.08041382f,
+ 0.01248169f, 0.00009155f, 0.01290894f, -0.07415771f, 0.16510010f, 0.75018311f,
+ 0.21453857f, -0.08111572f, 0.01239014f, 0.00012207f, 0.01290894f, -0.07330322f,
+ 0.15979004f, 0.74966431f, 0.22021484f, -0.08178711f, 0.01229858f, 0.00015259f,
+ 0.01290894f, -0.07241821f, 0.15454102f, 0.74908447f, 0.22592163f, -0.08242798f,
+ 0.01217651f, 0.00018311f, 0.01290894f, -0.07150269f, 0.14932251f, 0.74838257f,
+ 0.23165894f, -0.08303833f, 0.01205444f, 0.00021362f, 0.01290894f, -0.07058716f,
+ 0.14416504f, 0.74755859f, 0.23742676f, -0.08364868f, 0.01193237f, 0.00024414f,
+ 0.01287842f, -0.06967163f, 0.13903809f, 0.74667358f, 0.24322510f, -0.08419800f,
+ 0.01177979f, 0.00027466f, 0.01284790f, -0.06872559f, 0.13397217f, 0.74566650f,
+ 0.24905396f, -0.08474731f, 0.01162720f, 0.00033569f, 0.01281738f, -0.06777954f,
+ 0.12893677f, 0.74456787f, 0.25491333f, -0.08526611f, 0.01147461f, 0.00036621f,
+ 0.01278687f, -0.06683350f, 0.12396240f, 0.74337769f, 0.26077271f, -0.08575439f,
+ 0.01129150f, 0.00042725f, 0.01275635f, -0.06585693f, 0.11901855f, 0.74206543f,
+ 0.26669312f, -0.08621216f, 0.01110840f, 0.00045776f, 0.01269531f, -0.06488037f,
+ 0.11413574f, 0.74069214f, 0.27261353f, -0.08663940f, 0.01092529f, 0.00051880f,
+ 0.01263428f, -0.06387329f, 0.10931396f, 0.73919678f, 0.27853394f, -0.08700562f,
+ 0.01071167f, 0.00057983f, 0.01257324f, -0.06286621f, 0.10452271f, 0.73760986f,
+ 0.28451538f, -0.08737183f, 0.01049805f, 0.00064087f, 0.01251221f, -0.06185913f,
+ 0.09979248f, 0.73593140f, 0.29049683f, -0.08770752f, 0.01025391f, 0.00067139f,
+ 0.01242065f, -0.06082153f, 0.09512329f, 0.73413086f, 0.29647827f, -0.08801270f,
+ 0.01000977f, 0.00073242f, 0.01232910f, -0.05981445f, 0.09051514f, 0.73226929f,
+ 0.30249023f, -0.08828735f, 0.00973511f, 0.00079346f, 0.01226807f, -0.05877686f,
+ 0.08593750f, 0.73028564f, 0.30853271f, -0.08850098f, 0.00949097f, 0.00088501f,
+ 0.01214600f, -0.05773926f, 0.08142090f, 0.72824097f, 0.31457520f, -0.08871460f,
+ 0.00918579f, 0.00094604f, 0.01205444f, -0.05670166f, 0.07696533f, 0.72607422f,
+ 0.32061768f, -0.08886719f, 0.00891113f, 0.00100708f, 0.01196289f, -0.05563354f,
+ 0.07257080f, 0.72381592f, 0.32669067f, -0.08898926f, 0.00860596f, 0.00106812f,
+ 0.01187134f, -0.05459595f, 0.06820679f, 0.72146606f, 0.33276367f, -0.08908081f,
+ 0.00827026f, 0.00115967f, 0.01174927f, -0.05352783f, 0.06393433f, 0.71902466f,
+ 0.33883667f, -0.08911133f, 0.00796509f, 0.00122070f, 0.01162720f, -0.05245972f,
+ 0.05969238f, 0.71649170f, 0.34494019f, -0.08914185f, 0.00759888f, 0.00131226f,
+ 0.01150513f, -0.05139160f, 0.05551147f, 0.71389771f, 0.35101318f, -0.08911133f,
+ 0.00726318f, 0.00137329f, 0.01138306f, -0.05032349f, 0.05139160f, 0.71118164f,
+ 0.35711670f, -0.08901978f, 0.00686646f, 0.00146484f, 0.01126099f, -0.04928589f,
+ 0.04733276f, 0.70837402f, 0.36322021f, -0.08892822f, 0.00650024f, 0.00155640f,
+ 0.01113892f, -0.04821777f, 0.04333496f, 0.70550537f, 0.36932373f, -0.08877563f,
+ 0.00610352f, 0.00164795f, 0.01101685f, -0.04714966f, 0.03939819f, 0.70251465f,
+ 0.37542725f, -0.08856201f, 0.00567627f, 0.00173950f, 0.01086426f, -0.04608154f,
+ 0.03549194f, 0.69946289f, 0.38153076f, -0.08834839f, 0.00527954f, 0.00183105f,
+ 0.01074219f, -0.04501343f, 0.03167725f, 0.69631958f, 0.38763428f, -0.08804321f,
+ 0.00482178f, 0.00192261f, 0.01058960f, -0.04394531f, 0.02792358f, 0.69308472f,
+ 0.39370728f, -0.08773804f, 0.00436401f, 0.00201416f, 0.01043701f, -0.04287720f,
+ 0.02420044f, 0.68975830f, 0.39981079f, -0.08737183f, 0.00390625f, 0.00210571f,
+ 0.01031494f, -0.04180908f, 0.02056885f, 0.68637085f, 0.40588379f, -0.08694458f,
+ 0.00344849f, 0.00222778f, 0.01016235f, -0.04074097f, 0.01699829f, 0.68289185f,
+ 0.41195679f, -0.08648682f, 0.00296021f, 0.00231934f, 0.01000977f, -0.03970337f,
+ 0.01345825f, 0.67932129f, 0.41802979f, -0.08596802f, 0.00244141f, 0.00244141f,
+ 0.00985718f, -0.03863525f, 0.01000977f, 0.67568970f, 0.42407227f, -0.08541870f,
+ 0.00192261f, 0.00253296f, 0.00970459f, -0.03759766f, 0.00662231f, 0.67196655f,
+ 0.43011475f, -0.08480835f, 0.00140381f, 0.00265503f, 0.00955200f, -0.03652954f,
+ 0.00326538f, 0.66815186f, 0.43612671f, -0.08416748f, 0.00085449f, 0.00277710f,
+ 0.00936890f, -0.03549194f, 0.00000000f, 0.66427612f, 0.44213867f, -0.08346558f,
+ 0.00027466f, 0.00289917f, 0.00921631f, -0.03445435f, -0.00320435f, 0.66030884f,
+ 0.44812012f, -0.08270264f, -0.00027466f, 0.00299072f, 0.00906372f, -0.03344727f,
+ -0.00634766f, 0.65631104f, 0.45407104f, -0.08190918f, -0.00088501f, 0.00311279f,
+ 0.00891113f, -0.03240967f, -0.00946045f, 0.65219116f, 0.46002197f, -0.08105469f,
+ -0.00146484f, 0.00323486f, 0.00872803f, -0.03140259f, -0.01248169f, 0.64801025f,
+ 0.46594238f, -0.08013916f, -0.00210571f, 0.00338745f, 0.00857544f, -0.03039551f,
+ -0.01544189f, 0.64376831f, 0.47183228f, -0.07919312f, -0.00271606f, 0.00350952f,
+ 0.00842285f, -0.02938843f, -0.01834106f, 0.63946533f, 0.47772217f, -0.07818604f,
+ -0.00335693f, 0.00363159f, 0.00823975f, -0.02838135f, -0.02117920f, 0.63507080f,
+ 0.48358154f, -0.07711792f, -0.00402832f, 0.00375366f, 0.00808716f, -0.02740479f,
+ -0.02395630f, 0.63061523f, 0.48937988f, -0.07598877f, -0.00469971f, 0.00390625f,
+ 0.00793457f, -0.02642822f, -0.02667236f, 0.62609863f, 0.49517822f, -0.07482910f,
+ -0.00537109f, 0.00402832f, 0.00775146f, -0.02545166f, -0.02932739f, 0.62152100f,
+ 0.50094604f, -0.07357788f, -0.00607300f, 0.00418091f, 0.00759888f, -0.02450562f,
+ -0.03192139f, 0.61685181f, 0.50665283f, -0.07229614f, -0.00677490f, 0.00430298f,
+ 0.00741577f, -0.02352905f, -0.03445435f, 0.61215210f, 0.51235962f, -0.07098389f,
+ -0.00750732f, 0.00445557f, 0.00726318f, -0.02258301f, -0.03689575f, 0.60736084f,
+ 0.51800537f, -0.06958008f, -0.00823975f, 0.00460815f, 0.00711060f, -0.02166748f,
+ -0.03930664f, 0.60253906f, 0.52362061f, -0.06811523f, -0.00897217f, 0.00476074f,
+ 0.00692749f, -0.02075195f, -0.04165649f, 0.59762573f, 0.52920532f, -0.06661987f,
+ -0.00973511f, 0.00488281f, 0.00677490f, -0.01983643f, -0.04394531f, 0.59268188f,
+ 0.53475952f, -0.06506348f, -0.01052856f, 0.00503540f, 0.00662231f, -0.01892090f,
+ -0.04617310f, 0.58767700f, 0.54025269f, -0.06344604f, -0.01129150f, 0.00518799f,
+ 0.00643921f, -0.01803589f, -0.04830933f, 0.58261108f, 0.54571533f, -0.06173706f,
+ -0.01208496f, 0.00534058f, 0.00628662f, -0.01715088f, -0.05041504f, 0.57748413f,
+ 0.55111694f, -0.05999756f, -0.01290894f, 0.00549316f, 0.00613403f, -0.01626587f,
+ -0.05245972f, 0.57232666f, 0.55648804f, -0.05819702f, -0.01373291f, 0.00564575f,
+ 0.00598145f, -0.01541138f, -0.05444336f, 0.56707764f, 0.56182861f, -0.05636597f,
+ -0.01455688f, 0.00582886f, 0.00582886f, -0.01455688f, -0.05636597f, 0.56182861f,
+ 0.56707764f, -0.05444336f, -0.01541138f, 0.00598145f, 0.00564575f, -0.01373291f,
+ -0.05819702f, 0.55648804f, 0.57232666f, -0.05245972f, -0.01626587f, 0.00613403f,
+ 0.00549316f, -0.01290894f, -0.05999756f, 0.55111694f, 0.57748413f, -0.05041504f,
+ -0.01715088f, 0.00628662f, 0.00534058f, -0.01208496f, -0.06173706f, 0.54571533f,
+ 0.58261108f, -0.04830933f, -0.01803589f, 0.00643921f, 0.00518799f, -0.01129150f,
+ -0.06344604f, 0.54025269f, 0.58767700f, -0.04617310f, -0.01892090f, 0.00662231f,
+ 0.00503540f, -0.01052856f, -0.06506348f, 0.53475952f, 0.59268188f, -0.04394531f,
+ -0.01983643f, 0.00677490f, 0.00488281f, -0.00973511f, -0.06661987f, 0.52920532f,
+ 0.59762573f, -0.04165649f, -0.02075195f, 0.00692749f, 0.00476074f, -0.00897217f,
+ -0.06811523f, 0.52362061f, 0.60253906f, -0.03930664f, -0.02166748f, 0.00711060f,
+ 0.00460815f, -0.00823975f, -0.06958008f, 0.51800537f, 0.60736084f, -0.03689575f,
+ -0.02258301f, 0.00726318f, 0.00445557f, -0.00750732f, -0.07098389f, 0.51235962f,
+ 0.61215210f, -0.03445435f, -0.02352905f, 0.00741577f, 0.00430298f, -0.00677490f,
+ -0.07229614f, 0.50665283f, 0.61685181f, -0.03192139f, -0.02450562f, 0.00759888f,
+ 0.00418091f, -0.00607300f, -0.07357788f, 0.50094604f, 0.62152100f, -0.02932739f,
+ -0.02545166f, 0.00775146f, 0.00402832f, -0.00537109f, -0.07482910f, 0.49517822f,
+ 0.62609863f, -0.02667236f, -0.02642822f, 0.00793457f, 0.00390625f, -0.00469971f,
+ -0.07598877f, 0.48937988f, 0.63061523f, -0.02395630f, -0.02740479f, 0.00808716f,
+ 0.00375366f, -0.00402832f, -0.07711792f, 0.48358154f, 0.63507080f, -0.02117920f,
+ -0.02838135f, 0.00823975f, 0.00363159f, -0.00335693f, -0.07818604f, 0.47772217f,
+ 0.63946533f, -0.01834106f, -0.02938843f, 0.00842285f, 0.00350952f, -0.00271606f,
+ -0.07919312f, 0.47183228f, 0.64376831f, -0.01544189f, -0.03039551f, 0.00857544f,
+ 0.00338745f, -0.00210571f, -0.08013916f, 0.46594238f, 0.64801025f, -0.01248169f,
+ -0.03140259f, 0.00872803f, 0.00323486f, -0.00146484f, -0.08105469f, 0.46002197f,
+ 0.65219116f, -0.00946045f, -0.03240967f, 0.00891113f, 0.00311279f, -0.00088501f,
+ -0.08190918f, 0.45407104f, 0.65631104f, -0.00634766f, -0.03344727f, 0.00906372f,
+ 0.00299072f, -0.00027466f, -0.08270264f, 0.44812012f, 0.66030884f, -0.00320435f,
+ -0.03445435f, 0.00921631f, 0.00289917f, 0.00027466f, -0.08346558f, 0.44213867f,
+ 0.66427612f, 0.00000000f, -0.03549194f, 0.00936890f, 0.00277710f, 0.00085449f,
+ -0.08416748f, 0.43612671f, 0.66815186f, 0.00326538f, -0.03652954f, 0.00955200f,
+ 0.00265503f, 0.00140381f, -0.08480835f, 0.43011475f, 0.67196655f, 0.00662231f,
+ -0.03759766f, 0.00970459f, 0.00253296f, 0.00192261f, -0.08541870f, 0.42407227f,
+ 0.67568970f, 0.01000977f, -0.03863525f, 0.00985718f, 0.00244141f, 0.00244141f,
+ -0.08596802f, 0.41802979f, 0.67932129f, 0.01345825f, -0.03970337f, 0.01000977f,
+ 0.00231934f, 0.00296021f, -0.08648682f, 0.41195679f, 0.68289185f, 0.01699829f,
+ -0.04074097f, 0.01016235f, 0.00222778f, 0.00344849f, -0.08694458f, 0.40588379f,
+ 0.68637085f, 0.02056885f, -0.04180908f, 0.01031494f, 0.00210571f, 0.00390625f,
+ -0.08737183f, 0.39981079f, 0.68975830f, 0.02420044f, -0.04287720f, 0.01043701f,
+ 0.00201416f, 0.00436401f, -0.08773804f, 0.39370728f, 0.69308472f, 0.02792358f,
+ -0.04394531f, 0.01058960f, 0.00192261f, 0.00482178f, -0.08804321f, 0.38763428f,
+ 0.69631958f, 0.03167725f, -0.04501343f, 0.01074219f, 0.00183105f, 0.00527954f,
+ -0.08834839f, 0.38153076f, 0.69946289f, 0.03549194f, -0.04608154f, 0.01086426f,
+ 0.00173950f, 0.00567627f, -0.08856201f, 0.37542725f, 0.70251465f, 0.03939819f,
+ -0.04714966f, 0.01101685f, 0.00164795f, 0.00610352f, -0.08877563f, 0.36932373f,
+ 0.70550537f, 0.04333496f, -0.04821777f, 0.01113892f, 0.00155640f, 0.00650024f,
+ -0.08892822f, 0.36322021f, 0.70837402f, 0.04733276f, -0.04928589f, 0.01126099f,
+ 0.00146484f, 0.00686646f, -0.08901978f, 0.35711670f, 0.71118164f, 0.05139160f,
+ -0.05032349f, 0.01138306f, 0.00137329f, 0.00726318f, -0.08911133f, 0.35101318f,
+ 0.71389771f, 0.05551147f, -0.05139160f, 0.01150513f, 0.00131226f, 0.00759888f,
+ -0.08914185f, 0.34494019f, 0.71649170f, 0.05969238f, -0.05245972f, 0.01162720f,
+ 0.00122070f, 0.00796509f, -0.08911133f, 0.33883667f, 0.71902466f, 0.06393433f,
+ -0.05352783f, 0.01174927f, 0.00115967f, 0.00827026f, -0.08908081f, 0.33276367f,
+ 0.72146606f, 0.06820679f, -0.05459595f, 0.01187134f, 0.00106812f, 0.00860596f,
+ -0.08898926f, 0.32669067f, 0.72381592f, 0.07257080f, -0.05563354f, 0.01196289f,
+ 0.00100708f, 0.00891113f, -0.08886719f, 0.32061768f, 0.72607422f, 0.07696533f,
+ -0.05670166f, 0.01205444f, 0.00094604f, 0.00918579f, -0.08871460f, 0.31457520f,
+ 0.72824097f, 0.08142090f, -0.05773926f, 0.01214600f, 0.00088501f, 0.00949097f,
+ -0.08850098f, 0.30853271f, 0.73028564f, 0.08593750f, -0.05877686f, 0.01226807f,
+ 0.00079346f, 0.00973511f, -0.08828735f, 0.30249023f, 0.73226929f, 0.09051514f,
+ -0.05981445f, 0.01232910f, 0.00073242f, 0.01000977f, -0.08801270f, 0.29647827f,
+ 0.73413086f, 0.09512329f, -0.06082153f, 0.01242065f, 0.00067139f, 0.01025391f,
+ -0.08770752f, 0.29049683f, 0.73593140f, 0.09979248f, -0.06185913f, 0.01251221f,
+ 0.00064087f, 0.01049805f, -0.08737183f, 0.28451538f, 0.73760986f, 0.10452271f,
+ -0.06286621f, 0.01257324f, 0.00057983f, 0.01071167f, -0.08700562f, 0.27853394f,
+ 0.73919678f, 0.10931396f, -0.06387329f, 0.01263428f, 0.00051880f, 0.01092529f,
+ -0.08663940f, 0.27261353f, 0.74069214f, 0.11413574f, -0.06488037f, 0.01269531f,
+ 0.00045776f, 0.01110840f, -0.08621216f, 0.26669312f, 0.74206543f, 0.11901855f,
+ -0.06585693f, 0.01275635f, 0.00042725f, 0.01129150f, -0.08575439f, 0.26077271f,
+ 0.74337769f, 0.12396240f, -0.06683350f, 0.01278687f, 0.00036621f, 0.01147461f,
+ -0.08526611f, 0.25491333f, 0.74456787f, 0.12893677f, -0.06777954f, 0.01281738f,
+ 0.00033569f, 0.01162720f, -0.08474731f, 0.24905396f, 0.74566650f, 0.13397217f,
+ -0.06872559f, 0.01284790f, 0.00027466f, 0.01177979f, -0.08419800f, 0.24322510f,
+ 0.74667358f, 0.13903809f, -0.06967163f, 0.01287842f, 0.00024414f, 0.01193237f,
+ -0.08364868f, 0.23742676f, 0.74755859f, 0.14416504f, -0.07058716f, 0.01290894f,
+ 0.00021362f, 0.01205444f, -0.08303833f, 0.23165894f, 0.74838257f, 0.14932251f,
+ -0.07150269f, 0.01290894f, 0.00018311f, 0.01217651f, -0.08242798f, 0.22592163f,
+ 0.74908447f, 0.15454102f, -0.07241821f, 0.01290894f, 0.00015259f, 0.01229858f,
+ -0.08178711f, 0.22021484f, 0.74966431f, 0.15979004f, -0.07330322f, 0.01290894f,
+ 0.00012207f, 0.01239014f, -0.08111572f, 0.21453857f, 0.75018311f, 0.16510010f,
+ -0.07415771f, 0.01290894f, 0.00009155f, 0.01248169f, -0.08041382f, 0.20892334f,
+ 0.75057983f, 0.17044067f, -0.07501221f, 0.01287842f, 0.00006104f, 0.01257324f,
+ -0.07971191f, 0.20330811f, 0.75088501f, 0.17581177f, -0.07583618f, 0.01284790f,
+ 0.00003052f, 0.01266479f, -0.07897949f, 0.19772339f, 0.75106812f, 0.18124390f,
+ -0.07666016f, 0.01281738f, 0.00000000f, 0.01272583f, -0.07821655f, 0.19219971f,
+ 0.75119019f, 0.18670654f, -0.07745361f, 0.01275635f,
+ };
+
+ static constexpr std::array<f32, 1024> lut2 = {
+ -0.00036621f, 0.00143433f, -0.00408936f, 0.99996948f, 0.00247192f, -0.00048828f,
+ 0.00006104f, 0.00000000f, -0.00079346f, 0.00329590f, -0.01052856f, 0.99975586f,
+ 0.00918579f, -0.00241089f, 0.00051880f, -0.00003052f, -0.00122070f, 0.00512695f,
+ -0.01684570f, 0.99929810f, 0.01605225f, -0.00439453f, 0.00097656f, -0.00006104f,
+ -0.00161743f, 0.00689697f, -0.02297974f, 0.99862671f, 0.02304077f, -0.00640869f,
+ 0.00143433f, -0.00009155f, -0.00201416f, 0.00866699f, -0.02899170f, 0.99774170f,
+ 0.03018188f, -0.00845337f, 0.00192261f, -0.00015259f, -0.00238037f, 0.01037598f,
+ -0.03488159f, 0.99664307f, 0.03741455f, -0.01055908f, 0.00241089f, -0.00018311f,
+ -0.00274658f, 0.01202393f, -0.04061890f, 0.99533081f, 0.04483032f, -0.01266479f,
+ 0.00292969f, -0.00024414f, -0.00308228f, 0.01364136f, -0.04620361f, 0.99377441f,
+ 0.05233765f, -0.01483154f, 0.00344849f, -0.00027466f, -0.00341797f, 0.01522827f,
+ -0.05163574f, 0.99200439f, 0.05999756f, -0.01699829f, 0.00396729f, -0.00033569f,
+ -0.00375366f, 0.01678467f, -0.05691528f, 0.99002075f, 0.06777954f, -0.01922607f,
+ 0.00451660f, -0.00039673f, -0.00405884f, 0.01828003f, -0.06207275f, 0.98782349f,
+ 0.07568359f, -0.02145386f, 0.00506592f, -0.00042725f, -0.00436401f, 0.01971436f,
+ -0.06707764f, 0.98541260f, 0.08370972f, -0.02374268f, 0.00564575f, -0.00048828f,
+ -0.00463867f, 0.02114868f, -0.07192993f, 0.98278809f, 0.09185791f, -0.02603149f,
+ 0.00622559f, -0.00054932f, -0.00494385f, 0.02252197f, -0.07666016f, 0.97991943f,
+ 0.10012817f, -0.02835083f, 0.00680542f, -0.00061035f, -0.00518799f, 0.02383423f,
+ -0.08123779f, 0.97686768f, 0.10848999f, -0.03073120f, 0.00738525f, -0.00070190f,
+ -0.00543213f, 0.02511597f, -0.08566284f, 0.97360229f, 0.11700439f, -0.03308105f,
+ 0.00799561f, -0.00076294f, -0.00567627f, 0.02636719f, -0.08993530f, 0.97012329f,
+ 0.12561035f, -0.03549194f, 0.00860596f, -0.00082397f, -0.00592041f, 0.02755737f,
+ -0.09405518f, 0.96643066f, 0.13436890f, -0.03790283f, 0.00924683f, -0.00091553f,
+ -0.00613403f, 0.02868652f, -0.09805298f, 0.96252441f, 0.14318848f, -0.04034424f,
+ 0.00985718f, -0.00097656f, -0.00631714f, 0.02981567f, -0.10189819f, 0.95843506f,
+ 0.15213013f, -0.04281616f, 0.01049805f, -0.00106812f, -0.00653076f, 0.03085327f,
+ -0.10559082f, 0.95413208f, 0.16119385f, -0.04528809f, 0.01113892f, -0.00112915f,
+ -0.00671387f, 0.03189087f, -0.10916138f, 0.94961548f, 0.17034912f, -0.04779053f,
+ 0.01181030f, -0.00122070f, -0.00686646f, 0.03286743f, -0.11254883f, 0.94491577f,
+ 0.17959595f, -0.05029297f, 0.01248169f, -0.00131226f, -0.00701904f, 0.03378296f,
+ -0.11584473f, 0.94000244f, 0.18893433f, -0.05279541f, 0.01315308f, -0.00140381f,
+ -0.00717163f, 0.03466797f, -0.11895752f, 0.93490601f, 0.19839478f, -0.05532837f,
+ 0.01382446f, -0.00149536f, -0.00732422f, 0.03552246f, -0.12194824f, 0.92962646f,
+ 0.20791626f, -0.05786133f, 0.01449585f, -0.00158691f, -0.00744629f, 0.03631592f,
+ -0.12478638f, 0.92413330f, 0.21752930f, -0.06042480f, 0.01519775f, -0.00167847f,
+ -0.00753784f, 0.03707886f, -0.12750244f, 0.91848755f, 0.22723389f, -0.06298828f,
+ 0.01586914f, -0.00177002f, -0.00765991f, 0.03781128f, -0.13006592f, 0.91262817f,
+ 0.23703003f, -0.06555176f, 0.01657104f, -0.00189209f, -0.00775146f, 0.03848267f,
+ -0.13250732f, 0.90658569f, 0.24691772f, -0.06808472f, 0.01727295f, -0.00198364f,
+ -0.00784302f, 0.03909302f, -0.13479614f, 0.90036011f, 0.25683594f, -0.07064819f,
+ 0.01797485f, -0.00210571f, -0.00790405f, 0.03970337f, -0.13696289f, 0.89395142f,
+ 0.26687622f, -0.07321167f, 0.01870728f, -0.00219727f, -0.00796509f, 0.04025269f,
+ -0.13900757f, 0.88739014f, 0.27694702f, -0.07577515f, 0.01940918f, -0.00231934f,
+ -0.00802612f, 0.04077148f, -0.14089966f, 0.88064575f, 0.28710938f, -0.07833862f,
+ 0.02011108f, -0.00244141f, -0.00808716f, 0.04122925f, -0.14263916f, 0.87374878f,
+ 0.29733276f, -0.08090210f, 0.02084351f, -0.00253296f, -0.00811768f, 0.04165649f,
+ -0.14428711f, 0.86666870f, 0.30761719f, -0.08343506f, 0.02154541f, -0.00265503f,
+ -0.00814819f, 0.04205322f, -0.14578247f, 0.85940552f, 0.31793213f, -0.08596802f,
+ 0.02227783f, -0.00277710f, -0.00814819f, 0.04238892f, -0.14715576f, 0.85202026f,
+ 0.32833862f, -0.08847046f, 0.02297974f, -0.00289917f, -0.00817871f, 0.04272461f,
+ -0.14840698f, 0.84445190f, 0.33874512f, -0.09097290f, 0.02371216f, -0.00302124f,
+ -0.00817871f, 0.04299927f, -0.14953613f, 0.83673096f, 0.34924316f, -0.09347534f,
+ 0.02441406f, -0.00314331f, -0.00817871f, 0.04321289f, -0.15054321f, 0.82888794f,
+ 0.35977173f, -0.09594727f, 0.02514648f, -0.00326538f, -0.00814819f, 0.04342651f,
+ -0.15142822f, 0.82086182f, 0.37033081f, -0.09838867f, 0.02584839f, -0.00341797f,
+ -0.00814819f, 0.04357910f, -0.15219116f, 0.81271362f, 0.38092041f, -0.10079956f,
+ 0.02655029f, -0.00354004f, -0.00811768f, 0.04373169f, -0.15283203f, 0.80441284f,
+ 0.39154053f, -0.10321045f, 0.02725220f, -0.00366211f, -0.00808716f, 0.04382324f,
+ -0.15338135f, 0.79598999f, 0.40219116f, -0.10559082f, 0.02795410f, -0.00381470f,
+ -0.00805664f, 0.04388428f, -0.15377808f, 0.78741455f, 0.41287231f, -0.10794067f,
+ 0.02865601f, -0.00393677f, -0.00799561f, 0.04388428f, -0.15408325f, 0.77871704f,
+ 0.42358398f, -0.11026001f, 0.02935791f, -0.00405884f, -0.00793457f, 0.04388428f,
+ -0.15426636f, 0.76989746f, 0.43429565f, -0.11251831f, 0.03002930f, -0.00421143f,
+ -0.00787354f, 0.04385376f, -0.15435791f, 0.76095581f, 0.44500732f, -0.11477661f,
+ 0.03070068f, -0.00433350f, -0.00781250f, 0.04379272f, -0.15435791f, 0.75192261f,
+ 0.45574951f, -0.11697388f, 0.03137207f, -0.00448608f, -0.00775146f, 0.04367065f,
+ -0.15420532f, 0.74273682f, 0.46649170f, -0.11914062f, 0.03201294f, -0.00460815f,
+ -0.00769043f, 0.04354858f, -0.15399170f, 0.73345947f, 0.47723389f, -0.12127686f,
+ 0.03268433f, -0.00473022f, -0.00759888f, 0.04339600f, -0.15365601f, 0.72406006f,
+ 0.48794556f, -0.12335205f, 0.03329468f, -0.00488281f, -0.00750732f, 0.04321289f,
+ -0.15322876f, 0.71456909f, 0.49868774f, -0.12539673f, 0.03393555f, -0.00500488f,
+ -0.00741577f, 0.04296875f, -0.15270996f, 0.70498657f, 0.50936890f, -0.12738037f,
+ 0.03454590f, -0.00515747f, -0.00732422f, 0.04272461f, -0.15209961f, 0.69528198f,
+ 0.52008057f, -0.12930298f, 0.03515625f, -0.00527954f, -0.00723267f, 0.04248047f,
+ -0.15136719f, 0.68551636f, 0.53076172f, -0.13119507f, 0.03573608f, -0.00543213f,
+ -0.00714111f, 0.04217529f, -0.15057373f, 0.67565918f, 0.54138184f, -0.13299561f,
+ 0.03631592f, -0.00555420f, -0.00701904f, 0.04183960f, -0.14968872f, 0.66571045f,
+ 0.55200195f, -0.13476562f, 0.03689575f, -0.00567627f, -0.00692749f, 0.04150391f,
+ -0.14871216f, 0.65567017f, 0.56259155f, -0.13647461f, 0.03741455f, -0.00582886f,
+ -0.00680542f, 0.04113770f, -0.14767456f, 0.64556885f, 0.57315063f, -0.13812256f,
+ 0.03796387f, -0.00595093f, -0.00668335f, 0.04074097f, -0.14651489f, 0.63540649f,
+ 0.58364868f, -0.13970947f, 0.03845215f, -0.00607300f, -0.00656128f, 0.04031372f,
+ -0.14529419f, 0.62518311f, 0.59411621f, -0.14120483f, 0.03897095f, -0.00619507f,
+ -0.00643921f, 0.03988647f, -0.14401245f, 0.61486816f, 0.60452271f, -0.14263916f,
+ 0.03942871f, -0.00631714f, -0.00631714f, 0.03942871f, -0.14263916f, 0.60452271f,
+ 0.61486816f, -0.14401245f, 0.03988647f, -0.00643921f, -0.00619507f, 0.03897095f,
+ -0.14120483f, 0.59411621f, 0.62518311f, -0.14529419f, 0.04031372f, -0.00656128f,
+ -0.00607300f, 0.03845215f, -0.13970947f, 0.58364868f, 0.63540649f, -0.14651489f,
+ 0.04074097f, -0.00668335f, -0.00595093f, 0.03796387f, -0.13812256f, 0.57315063f,
+ 0.64556885f, -0.14767456f, 0.04113770f, -0.00680542f, -0.00582886f, 0.03741455f,
+ -0.13647461f, 0.56259155f, 0.65567017f, -0.14871216f, 0.04150391f, -0.00692749f,
+ -0.00567627f, 0.03689575f, -0.13476562f, 0.55200195f, 0.66571045f, -0.14968872f,
+ 0.04183960f, -0.00701904f, -0.00555420f, 0.03631592f, -0.13299561f, 0.54138184f,
+ 0.67565918f, -0.15057373f, 0.04217529f, -0.00714111f, -0.00543213f, 0.03573608f,
+ -0.13119507f, 0.53076172f, 0.68551636f, -0.15136719f, 0.04248047f, -0.00723267f,
+ -0.00527954f, 0.03515625f, -0.12930298f, 0.52008057f, 0.69528198f, -0.15209961f,
+ 0.04272461f, -0.00732422f, -0.00515747f, 0.03454590f, -0.12738037f, 0.50936890f,
+ 0.70498657f, -0.15270996f, 0.04296875f, -0.00741577f, -0.00500488f, 0.03393555f,
+ -0.12539673f, 0.49868774f, 0.71456909f, -0.15322876f, 0.04321289f, -0.00750732f,
+ -0.00488281f, 0.03329468f, -0.12335205f, 0.48794556f, 0.72406006f, -0.15365601f,
+ 0.04339600f, -0.00759888f, -0.00473022f, 0.03268433f, -0.12127686f, 0.47723389f,
+ 0.73345947f, -0.15399170f, 0.04354858f, -0.00769043f, -0.00460815f, 0.03201294f,
+ -0.11914062f, 0.46649170f, 0.74273682f, -0.15420532f, 0.04367065f, -0.00775146f,
+ -0.00448608f, 0.03137207f, -0.11697388f, 0.45574951f, 0.75192261f, -0.15435791f,
+ 0.04379272f, -0.00781250f, -0.00433350f, 0.03070068f, -0.11477661f, 0.44500732f,
+ 0.76095581f, -0.15435791f, 0.04385376f, -0.00787354f, -0.00421143f, 0.03002930f,
+ -0.11251831f, 0.43429565f, 0.76989746f, -0.15426636f, 0.04388428f, -0.00793457f,
+ -0.00405884f, 0.02935791f, -0.11026001f, 0.42358398f, 0.77871704f, -0.15408325f,
+ 0.04388428f, -0.00799561f, -0.00393677f, 0.02865601f, -0.10794067f, 0.41287231f,
+ 0.78741455f, -0.15377808f, 0.04388428f, -0.00805664f, -0.00381470f, 0.02795410f,
+ -0.10559082f, 0.40219116f, 0.79598999f, -0.15338135f, 0.04382324f, -0.00808716f,
+ -0.00366211f, 0.02725220f, -0.10321045f, 0.39154053f, 0.80441284f, -0.15283203f,
+ 0.04373169f, -0.00811768f, -0.00354004f, 0.02655029f, -0.10079956f, 0.38092041f,
+ 0.81271362f, -0.15219116f, 0.04357910f, -0.00814819f, -0.00341797f, 0.02584839f,
+ -0.09838867f, 0.37033081f, 0.82086182f, -0.15142822f, 0.04342651f, -0.00814819f,
+ -0.00326538f, 0.02514648f, -0.09594727f, 0.35977173f, 0.82888794f, -0.15054321f,
+ 0.04321289f, -0.00817871f, -0.00314331f, 0.02441406f, -0.09347534f, 0.34924316f,
+ 0.83673096f, -0.14953613f, 0.04299927f, -0.00817871f, -0.00302124f, 0.02371216f,
+ -0.09097290f, 0.33874512f, 0.84445190f, -0.14840698f, 0.04272461f, -0.00817871f,
+ -0.00289917f, 0.02297974f, -0.08847046f, 0.32833862f, 0.85202026f, -0.14715576f,
+ 0.04238892f, -0.00814819f, -0.00277710f, 0.02227783f, -0.08596802f, 0.31793213f,
+ 0.85940552f, -0.14578247f, 0.04205322f, -0.00814819f, -0.00265503f, 0.02154541f,
+ -0.08343506f, 0.30761719f, 0.86666870f, -0.14428711f, 0.04165649f, -0.00811768f,
+ -0.00253296f, 0.02084351f, -0.08090210f, 0.29733276f, 0.87374878f, -0.14263916f,
+ 0.04122925f, -0.00808716f, -0.00244141f, 0.02011108f, -0.07833862f, 0.28710938f,
+ 0.88064575f, -0.14089966f, 0.04077148f, -0.00802612f, -0.00231934f, 0.01940918f,
+ -0.07577515f, 0.27694702f, 0.88739014f, -0.13900757f, 0.04025269f, -0.00796509f,
+ -0.00219727f, 0.01870728f, -0.07321167f, 0.26687622f, 0.89395142f, -0.13696289f,
+ 0.03970337f, -0.00790405f, -0.00210571f, 0.01797485f, -0.07064819f, 0.25683594f,
+ 0.90036011f, -0.13479614f, 0.03909302f, -0.00784302f, -0.00198364f, 0.01727295f,
+ -0.06808472f, 0.24691772f, 0.90658569f, -0.13250732f, 0.03848267f, -0.00775146f,
+ -0.00189209f, 0.01657104f, -0.06555176f, 0.23703003f, 0.91262817f, -0.13006592f,
+ 0.03781128f, -0.00765991f, -0.00177002f, 0.01586914f, -0.06298828f, 0.22723389f,
+ 0.91848755f, -0.12750244f, 0.03707886f, -0.00753784f, -0.00167847f, 0.01519775f,
+ -0.06042480f, 0.21752930f, 0.92413330f, -0.12478638f, 0.03631592f, -0.00744629f,
+ -0.00158691f, 0.01449585f, -0.05786133f, 0.20791626f, 0.92962646f, -0.12194824f,
+ 0.03552246f, -0.00732422f, -0.00149536f, 0.01382446f, -0.05532837f, 0.19839478f,
+ 0.93490601f, -0.11895752f, 0.03466797f, -0.00717163f, -0.00140381f, 0.01315308f,
+ -0.05279541f, 0.18893433f, 0.94000244f, -0.11584473f, 0.03378296f, -0.00701904f,
+ -0.00131226f, 0.01248169f, -0.05029297f, 0.17959595f, 0.94491577f, -0.11254883f,
+ 0.03286743f, -0.00686646f, -0.00122070f, 0.01181030f, -0.04779053f, 0.17034912f,
+ 0.94961548f, -0.10916138f, 0.03189087f, -0.00671387f, -0.00112915f, 0.01113892f,
+ -0.04528809f, 0.16119385f, 0.95413208f, -0.10559082f, 0.03085327f, -0.00653076f,
+ -0.00106812f, 0.01049805f, -0.04281616f, 0.15213013f, 0.95843506f, -0.10189819f,
+ 0.02981567f, -0.00631714f, -0.00097656f, 0.00985718f, -0.04034424f, 0.14318848f,
+ 0.96252441f, -0.09805298f, 0.02868652f, -0.00613403f, -0.00091553f, 0.00924683f,
+ -0.03790283f, 0.13436890f, 0.96643066f, -0.09405518f, 0.02755737f, -0.00592041f,
+ -0.00082397f, 0.00860596f, -0.03549194f, 0.12561035f, 0.97012329f, -0.08993530f,
+ 0.02636719f, -0.00567627f, -0.00076294f, 0.00799561f, -0.03308105f, 0.11700439f,
+ 0.97360229f, -0.08566284f, 0.02511597f, -0.00543213f, -0.00070190f, 0.00738525f,
+ -0.03073120f, 0.10848999f, 0.97686768f, -0.08123779f, 0.02383423f, -0.00518799f,
+ -0.00061035f, 0.00680542f, -0.02835083f, 0.10012817f, 0.97991943f, -0.07666016f,
+ 0.02252197f, -0.00494385f, -0.00054932f, 0.00622559f, -0.02603149f, 0.09185791f,
+ 0.98278809f, -0.07192993f, 0.02114868f, -0.00463867f, -0.00048828f, 0.00564575f,
+ -0.02374268f, 0.08370972f, 0.98541260f, -0.06707764f, 0.01971436f, -0.00436401f,
+ -0.00042725f, 0.00506592f, -0.02145386f, 0.07568359f, 0.98782349f, -0.06207275f,
+ 0.01828003f, -0.00405884f, -0.00039673f, 0.00451660f, -0.01922607f, 0.06777954f,
+ 0.99002075f, -0.05691528f, 0.01678467f, -0.00375366f, -0.00033569f, 0.00396729f,
+ -0.01699829f, 0.05999756f, 0.99200439f, -0.05163574f, 0.01522827f, -0.00341797f,
+ -0.00027466f, 0.00344849f, -0.01483154f, 0.05233765f, 0.99377441f, -0.04620361f,
+ 0.01364136f, -0.00308228f, -0.00024414f, 0.00292969f, -0.01266479f, 0.04483032f,
+ 0.99533081f, -0.04061890f, 0.01202393f, -0.00274658f, -0.00018311f, 0.00241089f,
+ -0.01055908f, 0.03741455f, 0.99664307f, -0.03488159f, 0.01037598f, -0.00238037f,
+ -0.00015259f, 0.00192261f, -0.00845337f, 0.03018188f, 0.99774170f, -0.02899170f,
+ 0.00866699f, -0.00201416f, -0.00009155f, 0.00143433f, -0.00640869f, 0.02304077f,
+ 0.99862671f, -0.02297974f, 0.00689697f, -0.00161743f, -0.00006104f, 0.00097656f,
+ -0.00439453f, 0.01605225f, 0.99929810f, -0.01684570f, 0.00512695f, -0.00122070f,
+ -0.00003052f, 0.00051880f, -0.00241089f, 0.00918579f, 0.99975586f, -0.01052856f,
+ 0.00329590f, -0.00079346f, 0.00000000f, 0.00006104f, -0.00048828f, 0.00247192f,
+ 0.99996948f, -0.00408936f, 0.00143433f, -0.00036621f,
+ };
+
+ const auto get_lut = [&]() -> std::span<const f32> {
+ if (sample_rate_ratio <= 1.0f) {
+ return std::span<const f32>(lut2.data(), lut2.size());
+ } else if (sample_rate_ratio < 1.3f) {
+ return std::span<const f32>(lut1.data(), lut1.size());
+ } else {
+ return std::span<const f32>(lut0.data(), lut0.size());
+ }
+ };
+
+ auto lut{get_lut()};
+ u32 read_index{0};
+ for (u32 i = 0; i < samples_to_write; i++) {
+ const auto lut_index{(fraction.get_frac() >> 8) * 8};
+ const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
+ const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
+ const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
+ const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]};
+ const Common::FixedPoint<56, 8> sample4{input[read_index + 4] * lut[lut_index + 4]};
+ const Common::FixedPoint<56, 8> sample5{input[read_index + 5] * lut[lut_index + 5]};
+ const Common::FixedPoint<56, 8> sample6{input[read_index + 6] * lut[lut_index + 6]};
+ const Common::FixedPoint<56, 8> sample7{input[read_index + 7] * lut[lut_index + 7]};
+ output[i] = (sample0 + sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7)
+ .to_int_floor();
+ fraction += sample_rate_ratio;
+ read_index += static_cast<u32>(fraction.to_int_floor());
+ fraction.clear_int();
+ }
+}
+
+void Resample(std::span<s32> output, std::span<const s16> input,
+ const Common::FixedPoint<49, 15>& sample_rate_ratio,
+ Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write,
+ const SrcQuality src_quality) {
+
+ switch (src_quality) {
+ case SrcQuality::Low:
+ ResampleLowQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
+ break;
+ case SrcQuality::Medium:
+ ResampleNormalQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
+ break;
+ case SrcQuality::High:
+ ResampleHighQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
+ break;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/resample.h b/src/audio_core/renderer/command/resample/resample.h
new file mode 100644
index 000000000..ba9209b82
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/resample.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Resample an input buffer into an output buffer, according to the sample_rate_ratio.
+ *
+ * @param output - Output buffer.
+ * @param input - Input buffer.
+ * @param sample_rate_ratio - Ratio for resampling.
+ e.g 32000/48000 = 0.666 input samples read per output.
+ * @param fraction - Current read fraction, written to and should be passed back in for
+ * multiple calls.
+ * @param samples_to_write - Number of samples to write.
+ * @param src_quality - Resampling quality.
+ */
+void Resample(std::span<s32> output, std::span<const s16> input,
+ const Common::FixedPoint<49, 15>& sample_rate_ratio,
+ Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality);
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/upsample.cpp b/src/audio_core/renderer/command/resample/upsample.cpp
new file mode 100644
index 000000000..6c3ff31f7
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/upsample.cpp
@@ -0,0 +1,262 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/resample/upsample.h"
+#include "audio_core/renderer/upsampler/upsampler_info.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K.
+ *
+ * @param output - Output buffer.
+ * @param input - Input buffer.
+ * @param target_sample_count - Number of samples for output.
+ * @param state - Upsampler state, updated each call.
+ */
+static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
+ const u32 target_sample_count, const u32 source_sample_count,
+ UpsamplerState* state) {
+ constexpr u32 WindowSize = 10;
+ constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{
+ 51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f,
+ -1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f,
+ };
+ constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{
+ 105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f,
+ -1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f,
+ };
+ constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{
+ 122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f,
+ -1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f,
+ };
+ constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{
+ 23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f,
+ -0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f,
+ };
+ constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{
+ 80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f,
+ -1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f,
+ };
+
+ if (!state->initialized) {
+ switch (source_sample_count) {
+ case 40:
+ state->window_size = WindowSize;
+ state->ratio = 6.0f;
+ state->history.fill(0);
+ break;
+
+ case 80:
+ state->window_size = WindowSize;
+ state->ratio = 3.0f;
+ state->history.fill(0);
+ break;
+
+ case 160:
+ state->window_size = WindowSize;
+ state->ratio = 1.5f;
+ state->history.fill(0);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid upsampling source count {}!", source_sample_count);
+ // This continues anyway, but let's assume 160 for sanity
+ state->window_size = WindowSize;
+ state->ratio = 1.5f;
+ state->history.fill(0);
+ break;
+ }
+
+ state->history_input_index = 0;
+ state->history_output_index = 9;
+ state->history_start_index = 0;
+ state->history_end_index = UpsamplerState::HistorySize - 1;
+ state->initialized = true;
+ }
+
+ if (target_sample_count == 0) {
+ return;
+ }
+
+ u32 read_index{0};
+
+ auto increment = [&]() -> void {
+ state->history[state->history_input_index] = input[read_index++];
+ state->history_input_index =
+ static_cast<u16>((state->history_input_index + 1) % UpsamplerState::HistorySize);
+ state->history_output_index =
+ static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
+ };
+
+ auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1,
+ std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 {
+ auto output_index{state->history_output_index};
+ auto start_pos{output_index - state->history_start_index + 1U};
+ auto end_pos{10U};
+
+ if (start_pos < 10) {
+ end_pos = start_pos;
+ }
+
+ u64 prev_contrib{0};
+ u32 coeff_index{0};
+ for (; coeff_index < end_pos; coeff_index++, output_index--) {
+ prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
+ coeffs1[coeff_index].to_raw();
+ }
+
+ auto end_index{state->history_end_index};
+ for (; start_pos < 9; start_pos++, coeff_index++, end_index--) {
+ prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) *
+ coeffs1[coeff_index].to_raw();
+ }
+
+ output_index =
+ static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
+ start_pos = state->history_end_index - output_index + 1U;
+ end_pos = 10U;
+
+ if (start_pos < 10) {
+ end_pos = start_pos;
+ }
+
+ u64 next_contrib{0};
+ coeff_index = 0;
+ for (; coeff_index < end_pos; coeff_index++, output_index++) {
+ next_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
+ coeffs2[coeff_index].to_raw();
+ }
+
+ auto start_index{state->history_start_index};
+ for (; start_pos < 9; start_pos++, start_index++, coeff_index++) {
+ next_contrib += static_cast<u64>(state->history[start_index].to_raw()) *
+ coeffs2[coeff_index].to_raw();
+ }
+
+ return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8);
+ };
+
+ switch (state->ratio.to_int_floor()) {
+ // 40 -> 240
+ case 6:
+ for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
+ switch (state->sample_index) {
+ case 0:
+ increment();
+ output[write_index] = state->history[state->history_output_index].to_int_floor();
+ break;
+
+ case 1:
+ output[write_index] = calculate_sample(SincWindow3, SincWindow4);
+ break;
+
+ case 2:
+ output[write_index] = calculate_sample(SincWindow2, SincWindow1);
+ break;
+
+ case 3:
+ output[write_index] = calculate_sample(SincWindow5, SincWindow5);
+ break;
+
+ case 4:
+ output[write_index] = calculate_sample(SincWindow1, SincWindow2);
+ break;
+
+ case 5:
+ output[write_index] = calculate_sample(SincWindow4, SincWindow3);
+ break;
+ }
+ state->sample_index = static_cast<u8>((state->sample_index + 1) % 6);
+ }
+ break;
+
+ // 80 -> 240
+ case 3:
+ for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
+ switch (state->sample_index) {
+ case 0:
+ increment();
+ output[write_index] = state->history[state->history_output_index].to_int_floor();
+ break;
+
+ case 1:
+ output[write_index] = calculate_sample(SincWindow2, SincWindow1);
+ break;
+
+ case 2:
+ output[write_index] = calculate_sample(SincWindow1, SincWindow2);
+ break;
+ }
+ state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
+ }
+ break;
+
+ // 160 -> 240
+ default:
+ for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
+ switch (state->sample_index) {
+ case 0:
+ increment();
+ output[write_index] = state->history[state->history_output_index].to_int_floor();
+ break;
+
+ case 1:
+ output[write_index] = calculate_sample(SincWindow1, SincWindow2);
+ break;
+
+ case 2:
+ increment();
+ output[write_index] = calculate_sample(SincWindow2, SincWindow1);
+ break;
+ }
+ state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
+ }
+
+ break;
+ }
+}
+
+auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) -> void {
+ string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}",
+ source_sample_count, source_sample_rate);
+ const auto upsampler{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
+ if (upsampler != nullptr) {
+ string += fmt::format("\n\tUpsampler\n\t\tenabled {} sample count {}\n\tinputs: ",
+ upsampler->enabled, upsampler->sample_count);
+ for (u32 i = 0; i < upsampler->input_count; i++) {
+ string += fmt::format("{:02X}, ", upsampler->inputs[i]);
+ }
+ }
+ string += "\n";
+}
+
+void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) {
+ const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
+ const auto input_count{std::min(info->input_count, buffer_count)};
+ const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count};
+
+ for (u32 i = 0; i < input_count; i++) {
+ const auto channel{inputs_[i]};
+
+ if (channel >= 0 && channel < static_cast<s16>(processor.buffer_count)) {
+ auto state{&info->states[i]};
+ std::span<s32> output{
+ reinterpret_cast<s32*>(samples_buffer + info->sample_count * channel * sizeof(s32)),
+ info->sample_count};
+ auto input{processor.mix_buffers.subspan(channel * processor.sample_count,
+ processor.sample_count)};
+
+ SrcProcessFrame(output, input, info->sample_count, source_sample_count, state);
+ }
+ }
+}
+
+bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/upsample.h b/src/audio_core/renderer/command/resample/upsample.h
new file mode 100644
index 000000000..bfc94e8af
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/upsample.h
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for upsampling a mix buffer to 48Khz.
+ * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz.
+ */
+struct UpsampleCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Pointer to the output samples buffer.
+ CpuAddr samples_buffer;
+ /// Pointer to input mix buffer indexes.
+ CpuAddr inputs;
+ /// Number of input mix buffers.
+ u32 buffer_count;
+ /// Unknown, unused.
+ u32 unk_20;
+ /// Source data sample count.
+ u32 source_sample_count;
+ /// Source data sample rate.
+ u32 source_sample_rate;
+ /// Pointer to the upsampler info for this command.
+ CpuAddr upsampler_info;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/circular_buffer.cpp b/src/audio_core/renderer/command/sink/circular_buffer.cpp
new file mode 100644
index 000000000..ded5afc94
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/circular_buffer.cpp
@@ -0,0 +1,48 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <vector>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/sink/circular_buffer.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer {
+
+void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format(
+ "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ",
+ input_count, size, pos);
+ for (u32 i = 0; i < input_count; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n";
+}
+
+void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
+ constexpr s32 min{std::numeric_limits<s16>::min()};
+ constexpr s32 max{std::numeric_limits<s16>::max()};
+
+ std::vector<s16> output(processor.sample_count);
+ for (u32 channel = 0; channel < input_count; channel++) {
+ auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count,
+ processor.sample_count)};
+ for (u32 sample_index = 0; sample_index < processor.sample_count; sample_index++) {
+ output[sample_index] = static_cast<s16>(std::clamp(input[sample_index], min, max));
+ }
+
+ processor.memory->WriteBlockUnsafe(address + pos, output.data(),
+ output.size() * sizeof(s16));
+ pos += static_cast<u32>(processor.sample_count * sizeof(s16));
+ if (pos >= size) {
+ pos = 0;
+ }
+ }
+}
+
+bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/circular_buffer.h b/src/audio_core/renderer/command/sink/circular_buffer.h
new file mode 100644
index 000000000..e7d5be26e
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/circular_buffer.h
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for sinking samples to a circular buffer.
+ */
+struct CircularBufferSinkCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Number of input mix buffers
+ u32 input_count;
+ /// Input mix buffer indexes
+ std::array<s16, MaxChannels> inputs;
+ /// Circular buffer address
+ CpuAddr address;
+ /// Circular buffer size
+ u32 size;
+ /// Current buffer offset
+ u32 pos;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp
new file mode 100644
index 000000000..47e0c6722
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/device.cpp
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+
+#include "audio_core/renderer/adsp/command_list_processor.h"
+#include "audio_core/renderer/command/sink/device.h"
+#include "audio_core/sink/sink.h"
+
+namespace AudioCore::AudioRenderer {
+
+void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
+ std::string& string) {
+ string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ",
+ std::string_view(name), session_id, input_count);
+ for (u32 i = 0; i < input_count; i++) {
+ string += fmt::format("{:02X}, ", inputs[i]);
+ }
+ string += "\n";
+}
+
+void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
+ constexpr s32 min = std::numeric_limits<s16>::min();
+ constexpr s32 max = std::numeric_limits<s16>::max();
+
+ auto stream{processor.GetOutputSinkStream()};
+ stream->SetSystemChannels(input_count);
+
+ Sink::SinkBuffer out_buffer{
+ .frames{TargetSampleCount},
+ .frames_played{0},
+ .tag{0},
+ .consumed{false},
+ };
+
+ std::vector<s16> samples(out_buffer.frames * input_count);
+
+ for (u32 channel = 0; channel < input_count; channel++) {
+ const auto offset{inputs[channel] * out_buffer.frames};
+
+ for (u32 index = 0; index < out_buffer.frames; index++) {
+ samples[index * input_count + channel] =
+ static_cast<s16>(std::clamp(sample_buffer[offset + index], min, max));
+ }
+ }
+
+ out_buffer.tag = reinterpret_cast<u64>(samples.data());
+ stream->AppendBuffer(out_buffer, samples);
+}
+
+bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
+ return true;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/device.h b/src/audio_core/renderer/command/sink/device.h
new file mode 100644
index 000000000..1099bcf8c
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/device.h
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <span>
+#include <string>
+
+#include "audio_core/renderer/command/icommand.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class CommandListProcessor;
+}
+
+/**
+ * AudioRenderer command for sinking samples to an output device.
+ */
+struct DeviceSinkCommand : ICommand {
+ /**
+ * Print this command's information to a string.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @param string - The string to print into.
+ */
+ void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
+
+ /**
+ * Process this command.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ */
+ void Process(const ADSP::CommandListProcessor& processor) override;
+
+ /**
+ * Verify this command's data is valid.
+ *
+ * @param processor - The CommandListProcessor processing this command.
+ * @return True if the command is valid, otherwise false.
+ */
+ bool Verify(const ADSP::CommandListProcessor& processor) override;
+
+ /// Device name
+ char name[0x100];
+ /// System session id (unused)
+ s32 session_id;
+ /// Sample buffer to sink
+ std::span<s32> sample_buffer;
+ /// Number of input channels
+ u32 input_count;
+ /// Mix buffer indexes for each channel
+ std::array<s16, MaxChannels> inputs;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/aux_.cpp b/src/audio_core/renderer/effect/aux_.cpp
new file mode 100644
index 000000000..51e780ef1
--- /dev/null
+++ b/src/audio_core/renderer/effect/aux_.cpp
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/aux_.h"
+
+namespace AudioCore::AudioRenderer {
+
+void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+ if (buffer_unmapped || in_params.is_new) {
+ const bool send_unmapped{!pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_specific->send_buffer_info_address,
+ sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
+ const bool return_unmapped{!pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[1], in_specific->return_buffer_info_address,
+ sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
+
+ buffer_unmapped = send_unmapped || return_unmapped;
+
+ if (!buffer_unmapped) {
+ auto send{workbuffers[0].GetReference(false)};
+ send_buffer_info = send + sizeof(AuxInfoDsp);
+ send_buffer = send + sizeof(AuxBufferInfo);
+
+ auto ret{workbuffers[1].GetReference(false)};
+ return_buffer_info = ret + sizeof(AuxInfoDsp);
+ return_buffer = ret + sizeof(AuxBufferInfo);
+ }
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion2));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (buffer_unmapped || in_params.is_new) {
+ const bool send_unmapped{!pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], params->send_buffer_info_address,
+ sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
+ const bool return_unmapped{!pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[1], params->return_buffer_info_address,
+ sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
+
+ buffer_unmapped = send_unmapped || return_unmapped;
+
+ if (!buffer_unmapped) {
+ auto send{workbuffers[0].GetReference(false)};
+ send_buffer_info = send + sizeof(AuxInfoDsp);
+ send_buffer = send + sizeof(AuxBufferInfo);
+
+ auto ret{workbuffers[1].GetReference(false)};
+ return_buffer_info = ret + sizeof(AuxInfoDsp);
+ return_buffer = ret + sizeof(AuxBufferInfo);
+ }
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void AuxInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+}
+
+void AuxInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void AuxInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
+
+CpuAddr AuxInfo::GetWorkbuffer(s32 index) {
+ return workbuffers[index].GetReference(true);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/aux_.h b/src/audio_core/renderer/effect/aux_.h
new file mode 100644
index 000000000..4d3d9e3d9
--- /dev/null
+++ b/src/audio_core/renderer/effect/aux_.h
@@ -0,0 +1,123 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Auxiliary Buffer used for Aux commands.
+ * Send and return buffers are available (names from the game's perspective).
+ * Send is read by the host, containing a buffer of samples to be used for whatever purpose.
+ * Return is written by the host, writing a mix buffer back to the game.
+ * This allows the game to use pre-processed samples skipping the other render processing,
+ * and to examine or modify what the audio renderer has generated.
+ */
+class AuxInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
+ /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
+ /* 0x30 */ u32 mix_buffer_count;
+ /* 0x34 */ u32 sample_rate;
+ /* 0x38 */ u32 count_max;
+ /* 0x3C */ u32 mix_buffer_count_max;
+ /* 0x40 */ CpuAddr send_buffer_info_address;
+ /* 0x48 */ CpuAddr send_buffer_address;
+ /* 0x50 */ CpuAddr return_buffer_info_address;
+ /* 0x58 */ CpuAddr return_buffer_address;
+ /* 0x60 */ u32 mix_buffer_sample_size;
+ /* 0x64 */ u32 sample_count;
+ /* 0x68 */ u32 mix_buffer_sample_count;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "AuxInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
+ /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
+ /* 0x30 */ u32 mix_buffer_count;
+ /* 0x34 */ u32 sample_rate;
+ /* 0x38 */ u32 count_max;
+ /* 0x3C */ u32 mix_buffer_count_max;
+ /* 0x40 */ CpuAddr send_buffer_info_address;
+ /* 0x48 */ CpuAddr send_buffer_address;
+ /* 0x50 */ CpuAddr return_buffer_info_address;
+ /* 0x58 */ CpuAddr return_buffer_address;
+ /* 0x60 */ u32 mix_buffer_sample_size;
+ /* 0x64 */ u32 sample_count;
+ /* 0x68 */ u32 mix_buffer_sample_count;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "AuxInfo::ParameterVersion2 has the wrong size!");
+
+ struct AuxInfoDsp {
+ /* 0x00 */ u32 read_offset;
+ /* 0x04 */ u32 write_offset;
+ /* 0x08 */ u32 lost_sample_count;
+ /* 0x0C */ u32 total_sample_count;
+ /* 0x10 */ char unk10[0x30];
+ };
+ static_assert(sizeof(AuxInfoDsp) == 0x40, "AuxInfo::AuxInfoDsp has the wrong size!");
+
+ struct AuxBufferInfo {
+ /* 0x00 */ AuxInfoDsp cpu_info;
+ /* 0x40 */ AuxInfoDsp dsp_info;
+ };
+ static_assert(sizeof(AuxBufferInfo) == 0x80, "AuxInfo::AuxBufferInfo has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/biquad_filter.cpp b/src/audio_core/renderer/effect/biquad_filter.cpp
new file mode 100644
index 000000000..a1efb3231
--- /dev/null
+++ b/src/audio_core/renderer/effect/biquad_filter.cpp
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/biquad_filter.h"
+
+namespace AudioCore::AudioRenderer {
+
+void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion2));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void BiquadFilterInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+}
+
+void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state,
+ EffectResultState& dsp_state) {}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/biquad_filter.h b/src/audio_core/renderer/effect/biquad_filter.h
new file mode 100644
index 000000000..f53fd5bab
--- /dev/null
+++ b/src/audio_core/renderer/effect/biquad_filter.h
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+class BiquadFilterInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ std::array<s16, 3> b;
+ /* 0x12 */ std::array<s16, 2> a;
+ /* 0x16 */ s8 channel_count;
+ /* 0x17 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "BiquadFilterInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ std::array<s16, 3> b;
+ /* 0x12 */ std::array<s16, 2> a;
+ /* 0x16 */ s8 channel_count;
+ /* 0x17 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "BiquadFilterInfo::ParameterVersion2 has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/buffer_mixer.cpp b/src/audio_core/renderer/effect/buffer_mixer.cpp
new file mode 100644
index 000000000..9c8877f01
--- /dev/null
+++ b/src/audio_core/renderer/effect/buffer_mixer.cpp
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/buffer_mixer.h"
+
+namespace AudioCore::AudioRenderer {
+
+void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion2));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void BufferMixerInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+}
+
+void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state,
+ EffectResultState& dsp_state) {}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/buffer_mixer.h b/src/audio_core/renderer/effect/buffer_mixer.h
new file mode 100644
index 000000000..23eed4a8b
--- /dev/null
+++ b/src/audio_core/renderer/effect/buffer_mixer.h
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+class BufferMixerInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
+ /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
+ /* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
+ /* 0x90 */ u32 mix_count;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "BufferMixerInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
+ /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
+ /* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
+ /* 0x90 */ u32 mix_count;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "BufferMixerInfo::ParameterVersion2 has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/capture.cpp b/src/audio_core/renderer/effect/capture.cpp
new file mode 100644
index 000000000..3f038efdb
--- /dev/null
+++ b/src/audio_core/renderer/effect/capture.cpp
@@ -0,0 +1,82 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/aux_.h"
+#include "audio_core/renderer/effect/capture.h"
+
+namespace AudioCore::AudioRenderer {
+
+void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{
+ reinterpret_cast<const AuxInfo::ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<AuxInfo::ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+ if (buffer_unmapped || in_params.is_new) {
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_specific->send_buffer_info_address,
+ in_specific->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
+
+ if (!buffer_unmapped) {
+ const auto send_address{workbuffers[0].GetReference(false)};
+ send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
+ send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
+ return_buffer_info = 0;
+ return_buffer = 0;
+ }
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{
+ reinterpret_cast<const AuxInfo::ParameterVersion2*>(in_params.specific.data())};
+ auto params{reinterpret_cast<AuxInfo::ParameterVersion2*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion2));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (buffer_unmapped || in_params.is_new) {
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], params->send_buffer_info_address,
+ params->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
+
+ if (!buffer_unmapped) {
+ const auto send_address{workbuffers[0].GetReference(false)};
+ send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
+ send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
+ return_buffer_info = 0;
+ return_buffer = 0;
+ }
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void CaptureInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+}
+
+void CaptureInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void CaptureInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
+
+CpuAddr CaptureInfo::GetWorkbuffer(s32 index) {
+ return workbuffers[index].GetReference(true);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/capture.h b/src/audio_core/renderer/effect/capture.h
new file mode 100644
index 000000000..6fbed8e6b
--- /dev/null
+++ b/src/audio_core/renderer/effect/capture.h
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+class CaptureInfo : public EffectInfoBase {
+public:
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/compressor.cpp b/src/audio_core/renderer/effect/compressor.cpp
new file mode 100644
index 000000000..220ae02f9
--- /dev/null
+++ b/src/audio_core/renderer/effect/compressor.cpp
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/compressor.h"
+
+namespace AudioCore::AudioRenderer {
+
+void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {}
+
+void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void CompressorInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+}
+
+CpuAddr CompressorInfo::GetWorkbuffer(s32 index) {
+ return GetSingleBuffer(index);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/compressor.h b/src/audio_core/renderer/effect/compressor.h
new file mode 100644
index 000000000..019a5ae58
--- /dev/null
+++ b/src/audio_core/renderer/effect/compressor.h
@@ -0,0 +1,106 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+class CompressorInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ s16 channel_count_max;
+ /* 0x0E */ s16 channel_count;
+ /* 0x10 */ s32 sample_rate;
+ /* 0x14 */ f32 threshold;
+ /* 0x18 */ f32 compressor_ratio;
+ /* 0x1C */ s32 attack_time;
+ /* 0x20 */ s32 release_time;
+ /* 0x24 */ f32 unk_24;
+ /* 0x28 */ f32 unk_28;
+ /* 0x2C */ f32 unk_2C;
+ /* 0x30 */ f32 out_gain;
+ /* 0x34 */ ParameterState state;
+ /* 0x35 */ bool makeup_gain_enabled;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "CompressorInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ s16 channel_count_max;
+ /* 0x0E */ s16 channel_count;
+ /* 0x10 */ s32 sample_rate;
+ /* 0x14 */ f32 threshold;
+ /* 0x18 */ f32 compressor_ratio;
+ /* 0x1C */ s32 attack_time;
+ /* 0x20 */ s32 release_time;
+ /* 0x24 */ f32 unk_24;
+ /* 0x28 */ f32 unk_28;
+ /* 0x2C */ f32 unk_2C;
+ /* 0x30 */ f32 out_gain;
+ /* 0x34 */ ParameterState state;
+ /* 0x35 */ bool makeup_gain_enabled;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "CompressorInfo::ParameterVersion2 has the wrong size!");
+
+ struct State {
+ f32 unk_00;
+ f32 unk_04;
+ f32 unk_08;
+ f32 unk_0C;
+ f32 unk_10;
+ f32 unk_14;
+ f32 unk_18;
+ f32 makeup_gain;
+ f32 unk_20;
+ char unk_24[0x1C];
+ };
+ static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
+ "CompressorInfo::State has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/delay.cpp b/src/audio_core/renderer/effect/delay.cpp
new file mode 100644
index 000000000..d9853efd9
--- /dev/null
+++ b/src/audio_core/renderer/effect/delay.cpp
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/delay.h"
+
+namespace AudioCore::AudioRenderer {
+
+void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void DelayInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+}
+
+void DelayInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void DelayInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
+
+CpuAddr DelayInfo::GetWorkbuffer(s32 index) {
+ return GetSingleBuffer(index);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/delay.h b/src/audio_core/renderer/effect/delay.h
new file mode 100644
index 000000000..accc42a06
--- /dev/null
+++ b/src/audio_core/renderer/effect/delay.h
@@ -0,0 +1,135 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+class DelayInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x10 */ u32 delay_time_max;
+ /* 0x14 */ u32 delay_time;
+ /* 0x18 */ Common::FixedPoint<18, 14> sample_rate;
+ /* 0x1C */ Common::FixedPoint<18, 14> in_gain;
+ /* 0x20 */ Common::FixedPoint<18, 14> feedback_gain;
+ /* 0x24 */ Common::FixedPoint<18, 14> wet_gain;
+ /* 0x28 */ Common::FixedPoint<18, 14> dry_gain;
+ /* 0x2C */ Common::FixedPoint<18, 14> channel_spread;
+ /* 0x30 */ Common::FixedPoint<18, 14> lowpass_amount;
+ /* 0x34 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "DelayInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ s16 channel_count_max;
+ /* 0x0E */ s16 channel_count;
+ /* 0x10 */ s32 delay_time_max;
+ /* 0x14 */ s32 delay_time;
+ /* 0x18 */ s32 sample_rate;
+ /* 0x1C */ s32 in_gain;
+ /* 0x20 */ s32 feedback_gain;
+ /* 0x24 */ s32 wet_gain;
+ /* 0x28 */ s32 dry_gain;
+ /* 0x2C */ s32 channel_spread;
+ /* 0x30 */ s32 lowpass_amount;
+ /* 0x34 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "DelayInfo::ParameterVersion2 has the wrong size!");
+
+ struct DelayLine {
+ Common::FixedPoint<50, 14> Read() const {
+ return buffer[buffer_pos];
+ }
+
+ void Write(const Common::FixedPoint<50, 14> value) {
+ buffer[buffer_pos] = value;
+ buffer_pos = static_cast<u32>((buffer_pos + 1) % buffer.size());
+ }
+
+ s32 sample_count_max{};
+ s32 sample_count{};
+ std::vector<Common::FixedPoint<50, 14>> buffer{};
+ u32 buffer_pos{};
+ Common::FixedPoint<18, 14> decay_rate{};
+ };
+
+ struct State {
+ /* 0x000 */ std::array<s32, 8> unk_000;
+ /* 0x020 */ std::array<DelayLine, MaxChannels> delay_lines;
+ /* 0x0B0 */ Common::FixedPoint<18, 14> feedback_gain;
+ /* 0x0B4 */ Common::FixedPoint<18, 14> delay_feedback_gain;
+ /* 0x0B8 */ Common::FixedPoint<18, 14> delay_feedback_cross_gain;
+ /* 0x0BC */ Common::FixedPoint<18, 14> lowpass_gain;
+ /* 0x0C0 */ Common::FixedPoint<18, 14> lowpass_feedback_gain;
+ /* 0x0C4 */ std::array<Common::FixedPoint<50, 14>, MaxChannels> lowpass_z;
+ };
+ static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
+ "DelayInfo::State has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_context.cpp b/src/audio_core/renderer/effect/effect_context.cpp
new file mode 100644
index 000000000..74c7801c9
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_context.cpp
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/effect_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
+ std::span<EffectResultState> result_states_cpu_,
+ std::span<EffectResultState> result_states_dsp_,
+ const size_t dsp_state_count_) {
+ effect_infos = effect_infos_;
+ effect_count = effect_count_;
+ result_states_cpu = result_states_cpu_;
+ result_states_dsp = result_states_dsp_;
+ dsp_state_count = dsp_state_count_;
+}
+
+EffectInfoBase& EffectContext::GetInfo(const u32 index) {
+ return effect_infos[index];
+}
+
+EffectResultState& EffectContext::GetResultState(const u32 index) {
+ return result_states_cpu[index];
+}
+
+EffectResultState& EffectContext::GetDspSharedResultState(const u32 index) {
+ return result_states_dsp[index];
+}
+
+u32 EffectContext::GetCount() const {
+ return effect_count;
+}
+
+void EffectContext::UpdateStateByDspShared() {
+ for (size_t i = 0; i < dsp_state_count; i++) {
+ effect_infos[i].UpdateResultState(result_states_cpu[i], result_states_dsp[i]);
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h
new file mode 100644
index 000000000..85955bd9c
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_context.h
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "audio_core/renderer/effect/effect_result_state.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+class EffectContext {
+public:
+ /**
+ * Initialize the effect context
+ * @param effect_infos List of effect infos for this context
+ * @param effect_count The number of effects in the list
+ * @param result_states_cpu The workbuffer of result states for the CPU for this context
+ * @param result_states_dsp The workbuffer of result states for the DSP for this context
+ * @param state_count The number of result states
+ */
+ void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
+ std::span<EffectResultState> result_states_cpu_,
+ std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count);
+
+ /**
+ * Get the EffectInfo for a given index
+ * @param index Which effect to return
+ * @return Pointer to the effect
+ */
+ EffectInfoBase& GetInfo(const u32 index);
+
+ /**
+ * Get the CPU result state for a given index
+ * @param index Which result to return
+ * @return Pointer to the effect result state
+ */
+ EffectResultState& GetResultState(const u32 index);
+
+ /**
+ * Get the DSP result state for a given index
+ * @param index Which result to return
+ * @return Pointer to the effect result state
+ */
+ EffectResultState& GetDspSharedResultState(const u32 index);
+
+ /**
+ * Get the number of effects in this context
+ * @return The number of effects
+ */
+ u32 GetCount() const;
+
+ /**
+ * Update the CPU and DSP result states for all effects
+ */
+ void UpdateStateByDspShared();
+
+private:
+ /// Workbuffer for all of the effects
+ std::span<EffectInfoBase> effect_infos{};
+ /// Number of effects in the workbuffer
+ u32 effect_count{};
+ /// Workbuffer of states for all effects, kept host-side and not directly modified, dsp states
+ /// are copied here on the next render frame
+ std::span<EffectResultState> result_states_cpu{};
+ /// Workbuffer of states for all effects, used by the AudioRenderer to track effect state
+ /// between calls
+ std::span<EffectResultState> result_states_dsp{};
+ /// Number of result states in the workbuffers
+ size_t dsp_state_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h
new file mode 100644
index 000000000..43d0589cc
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_info_base.h
@@ -0,0 +1,435 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/effect/effect_result_state.h"
+#include "audio_core/renderer/memory/address_info.h"
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Base of all effects. Holds various data and functions used for all derived effects.
+ * Should not be used directly.
+ */
+class EffectInfoBase {
+public:
+ enum class Type : u8 {
+ Invalid,
+ Mix,
+ Aux,
+ Delay,
+ Reverb,
+ I3dl2Reverb,
+ BiquadFilter,
+ LightLimiter,
+ Capture,
+ Compressor,
+ };
+
+ enum class UsageState {
+ Invalid,
+ New,
+ Enabled,
+ Disabled,
+ };
+
+ enum class OutStatus : u8 {
+ Invalid,
+ New,
+ Initialized,
+ Used,
+ Removed,
+ };
+
+ enum class ParameterState : u8 {
+ Initialized,
+ Updating,
+ Updated,
+ };
+
+ struct InParameterVersion1 {
+ /* 0x00 */ Type type;
+ /* 0x01 */ bool is_new;
+ /* 0x02 */ bool enabled;
+ /* 0x04 */ u32 mix_id;
+ /* 0x08 */ CpuAddr workbuffer;
+ /* 0x10 */ CpuAddr workbuffer_size;
+ /* 0x18 */ u32 process_order;
+ /* 0x1C */ char unk1C[0x4];
+ /* 0x20 */ std::array<u8, 0xA0> specific;
+ };
+ static_assert(sizeof(InParameterVersion1) == 0xC0,
+ "EffectInfoBase::InParameterVersion1 has the wrong size!");
+
+ struct InParameterVersion2 {
+ /* 0x00 */ Type type;
+ /* 0x01 */ bool is_new;
+ /* 0x02 */ bool enabled;
+ /* 0x04 */ u32 mix_id;
+ /* 0x08 */ CpuAddr workbuffer;
+ /* 0x10 */ CpuAddr workbuffer_size;
+ /* 0x18 */ u32 process_order;
+ /* 0x1C */ char unk1C[0x4];
+ /* 0x20 */ std::array<u8, 0xA0> specific;
+ };
+ static_assert(sizeof(InParameterVersion2) == 0xC0,
+ "EffectInfoBase::InParameterVersion2 has the wrong size!");
+
+ struct OutStatusVersion1 {
+ /* 0x00 */ OutStatus state;
+ /* 0x01 */ char unk01[0xF];
+ };
+ static_assert(sizeof(OutStatusVersion1) == 0x10,
+ "EffectInfoBase::OutStatusVersion1 has the wrong size!");
+
+ struct OutStatusVersion2 {
+ /* 0x00 */ OutStatus state;
+ /* 0x01 */ char unk01[0xF];
+ /* 0x10 */ EffectResultState result_state;
+ };
+ static_assert(sizeof(OutStatusVersion2) == 0x90,
+ "EffectInfoBase::OutStatusVersion2 has the wrong size!");
+
+ struct State {
+ std::array<u8, 0x500> buffer;
+ };
+ static_assert(sizeof(State) == 0x500, "EffectInfoBase::State has the wrong size!");
+
+ EffectInfoBase() {
+ Cleanup();
+ }
+
+ virtual ~EffectInfoBase() = default;
+
+ /**
+ * Cleanup this effect, resetting it to a starting state.
+ */
+ void Cleanup() {
+ type = Type::Invalid;
+ enabled = false;
+ mix_id = UnusedMixId;
+ process_order = InvalidProcessOrder;
+ buffer_unmapped = false;
+ parameter = {};
+ for (auto& workbuffer : workbuffers) {
+ workbuffer.Setup(CpuAddr(0), 0);
+ }
+ }
+
+ /**
+ * Forcibly unmap all assigned workbuffers from the AudioRenderer.
+ *
+ * @param pool_mapper - Mapper to unmap the buffers.
+ */
+ void ForceUnmapBuffers(const PoolMapper& pool_mapper) {
+ for (auto& workbuffer : workbuffers) {
+ if (workbuffer.GetReference(false) != 0) {
+ pool_mapper.ForceUnmapPointer(workbuffer);
+ }
+ }
+ }
+
+ /**
+ * Check if this effect is enabled.
+ *
+ * @return True if effect is enabled, otherwise false.
+ */
+ bool IsEnabled() const {
+ return enabled;
+ }
+
+ /**
+ * Check if this effect should not be generated.
+ *
+ * @return True if effect should be skipped, otherwise false.
+ */
+ bool ShouldSkip() const {
+ return buffer_unmapped;
+ }
+
+ /**
+ * Get the type of this effect.
+ *
+ * @return The type of this effect. See EffectInfoBase::Type
+ */
+ Type GetType() const {
+ return type;
+ }
+
+ /**
+ * Set the type of this effect.
+ *
+ * @param type_ - The new type of this effect.
+ */
+ void SetType(const Type type_) {
+ type = type_;
+ }
+
+ /**
+ * Get the mix id of this effect.
+ *
+ * @return Mix id of this effect.
+ */
+ s32 GetMixId() const {
+ return mix_id;
+ }
+
+ /**
+ * Get the processing order of this effect.
+ *
+ * @return Process order of this effect.
+ */
+ s32 GetProcessingOrder() const {
+ return process_order;
+ }
+
+ /**
+ * Get this effect's parameter data.
+ *
+ * @return Pointer to the parametter, must be cast to the correct type.
+ */
+ u8* GetParameter() {
+ return parameter.data();
+ }
+
+ /**
+ * Get this effect's parameter data.
+ *
+ * @return Pointer to the parametter, must be cast to the correct type.
+ */
+ u8* GetStateBuffer() {
+ return state.data();
+ }
+
+ /**
+ * Set this effect's usage state.
+ *
+ * @param usage - new usage state of this effect.
+ */
+ void SetUsage(const UsageState usage) {
+ usage_state = usage;
+ }
+
+ /**
+ * Check if this effects need to have its workbuffer information updated.
+ * Version 1.
+ *
+ * @param params - Input parameters.
+ * @return True if workbuffers need updating, otherwise false.
+ */
+ bool ShouldUpdateWorkBufferInfo(const InParameterVersion1& params) const {
+ return buffer_unmapped || params.is_new;
+ }
+
+ /**
+ * Check if this effects need to have its workbuffer information updated.
+ * Version 2.
+ *
+ * @param params - Input parameters.
+ * @return True if workbuffers need updating, otherwise false.
+ */
+ bool ShouldUpdateWorkBufferInfo(const InParameterVersion2& params) const {
+ return buffer_unmapped || params.is_new;
+ }
+
+ /**
+ * Get the current usage state of this effect.
+ *
+ * @return The current usage state.
+ */
+ UsageState GetUsage() const {
+ return usage_state;
+ }
+
+ /**
+ * Write the current state. Version 1.
+ *
+ * @param out_status - Status to write.
+ * @param renderer_active - Is the AudioRenderer active?
+ */
+ void StoreStatus(OutStatusVersion1& out_status, const bool renderer_active) const {
+ if (renderer_active) {
+ if (usage_state != UsageState::Disabled) {
+ out_status.state = OutStatus::Used;
+ } else {
+ out_status.state = OutStatus::Removed;
+ }
+ } else if (usage_state == UsageState::New) {
+ out_status.state = OutStatus::Used;
+ } else {
+ out_status.state = OutStatus::Removed;
+ }
+ }
+
+ /**
+ * Write the current state. Version 2.
+ *
+ * @param out_status - Status to write.
+ * @param renderer_active - Is the AudioRenderer active?
+ */
+ void StoreStatus(OutStatusVersion2& out_status, const bool renderer_active) const {
+ if (renderer_active) {
+ if (usage_state != UsageState::Disabled) {
+ out_status.state = OutStatus::Used;
+ } else {
+ out_status.state = OutStatus::Removed;
+ }
+ } else if (usage_state == UsageState::New) {
+ out_status.state = OutStatus::Used;
+ } else {
+ out_status.state = OutStatus::Removed;
+ }
+ }
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ virtual void Update(BehaviorInfo::ErrorInfo& error_info,
+ [[maybe_unused]] const InParameterVersion1& params,
+ [[maybe_unused]] const PoolMapper& pool_mapper) {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ virtual void Update(BehaviorInfo::ErrorInfo& error_info,
+ [[maybe_unused]] const InParameterVersion2& params,
+ [[maybe_unused]] const PoolMapper& pool_mapper) {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ virtual void UpdateForCommandGeneration() {}
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ virtual void InitializeResultState([[maybe_unused]] EffectResultState& result_state) {}
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ virtual void UpdateResultState([[maybe_unused]] EffectResultState& cpu_state,
+ [[maybe_unused]] EffectResultState& dsp_state) {}
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ virtual CpuAddr GetWorkbuffer([[maybe_unused]] s32 index) {
+ return 0;
+ }
+
+ /**
+ * Get the first workbuffer assigned to this effect.
+ *
+ * @param index - Workbuffer index. Unused.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetSingleBuffer([[maybe_unused]] const s32 index) {
+ if (enabled) {
+ return workbuffers[0].GetReference(true);
+ }
+
+ if (usage_state != UsageState::Disabled) {
+ const auto ref{workbuffers[0].GetReference(false)};
+ const auto size{workbuffers[0].GetSize()};
+ if (ref != 0 && size > 0) {
+ // Invalidate DSP cache
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Get the send buffer info, used by Aux and Capture.
+ *
+ * @return Address of the buffer info.
+ */
+ CpuAddr GetSendBufferInfo() const {
+ return send_buffer_info;
+ }
+
+ /**
+ * Get the send buffer, used by Aux and Capture.
+ *
+ * @return Address of the buffer.
+ */
+ CpuAddr GetSendBuffer() const {
+ return send_buffer;
+ }
+
+ /**
+ * Get the return buffer info, used by Aux and Capture.
+ *
+ * @return Address of the buffer info.
+ */
+ CpuAddr GetReturnBufferInfo() const {
+ return return_buffer_info;
+ }
+
+ /**
+ * Get the return buffer, used by Aux and Capture.
+ *
+ * @return Address of the buffer.
+ */
+ CpuAddr GetReturnBuffer() const {
+ return return_buffer;
+ }
+
+protected:
+ /// Type of this effect. May be changed
+ Type type{Type::Invalid};
+ /// Is this effect enabled?
+ bool enabled{};
+ /// Are this effect's buffers unmapped?
+ bool buffer_unmapped{};
+ /// Current usage state
+ UsageState usage_state{UsageState::Invalid};
+ /// Mix id of this effect
+ s32 mix_id{UnusedMixId};
+ /// Process order of this effect
+ s32 process_order{InvalidProcessOrder};
+ /// Workbuffers assigned to this effect
+ std::array<AddressInfo, 2> workbuffers{AddressInfo(CpuAddr(0), 0), AddressInfo(CpuAddr(0), 0)};
+ /// Aux/Capture buffer info for reading
+ CpuAddr send_buffer_info;
+ /// Aux/Capture buffer for reading
+ CpuAddr send_buffer;
+ /// Aux/Capture buffer info for writing
+ CpuAddr return_buffer_info;
+ /// Aux/Capture buffer for writing
+ CpuAddr return_buffer;
+ /// Parameters of this effect
+ std::array<u8, sizeof(InParameterVersion2)> parameter{};
+ /// State of this effect used by the AudioRenderer across calls
+ std::array<u8, sizeof(State)> state{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_reset.h b/src/audio_core/renderer/effect/effect_reset.h
new file mode 100644
index 000000000..1ea67e334
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_reset.h
@@ -0,0 +1,71 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/effect/aux_.h"
+#include "audio_core/renderer/effect/biquad_filter.h"
+#include "audio_core/renderer/effect/buffer_mixer.h"
+#include "audio_core/renderer/effect/capture.h"
+#include "audio_core/renderer/effect/compressor.h"
+#include "audio_core/renderer/effect/delay.h"
+#include "audio_core/renderer/effect/i3dl2.h"
+#include "audio_core/renderer/effect/light_limiter.h"
+#include "audio_core/renderer/effect/reverb.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Reset an effect, and create a new one of the given type.
+ *
+ * @param effect - Effect to reset and re-construct.
+ * @param type - Type of the new effect to create.
+ */
+static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type) {
+ *effect = {};
+
+ switch (type) {
+ case EffectInfoBase::Type::Invalid:
+ std::construct_at<EffectInfoBase>(effect);
+ effect->SetType(EffectInfoBase::Type::Invalid);
+ break;
+ case EffectInfoBase::Type::Mix:
+ std::construct_at<BufferMixerInfo>(reinterpret_cast<BufferMixerInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Mix);
+ break;
+ case EffectInfoBase::Type::Aux:
+ std::construct_at<AuxInfo>(reinterpret_cast<AuxInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Aux);
+ break;
+ case EffectInfoBase::Type::Delay:
+ std::construct_at<DelayInfo>(reinterpret_cast<DelayInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Delay);
+ break;
+ case EffectInfoBase::Type::Reverb:
+ std::construct_at<ReverbInfo>(reinterpret_cast<ReverbInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Reverb);
+ break;
+ case EffectInfoBase::Type::I3dl2Reverb:
+ std::construct_at<I3dl2ReverbInfo>(reinterpret_cast<I3dl2ReverbInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::I3dl2Reverb);
+ break;
+ case EffectInfoBase::Type::BiquadFilter:
+ std::construct_at<BiquadFilterInfo>(reinterpret_cast<BiquadFilterInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::BiquadFilter);
+ break;
+ case EffectInfoBase::Type::LightLimiter:
+ std::construct_at<LightLimiterInfo>(reinterpret_cast<LightLimiterInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::LightLimiter);
+ break;
+ case EffectInfoBase::Type::Capture:
+ std::construct_at<CaptureInfo>(reinterpret_cast<CaptureInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Capture);
+ break;
+ case EffectInfoBase::Type::Compressor:
+ std::construct_at<CompressorInfo>(reinterpret_cast<CompressorInfo*>(effect));
+ effect->SetType(EffectInfoBase::Type::Compressor);
+ break;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_result_state.h b/src/audio_core/renderer/effect/effect_result_state.h
new file mode 100644
index 000000000..ae096ad69
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_result_state.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+struct EffectResultState {
+ std::array<u8, 0x80> state;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/i3dl2.cpp b/src/audio_core/renderer/effect/i3dl2.cpp
new file mode 100644
index 000000000..960b29cfc
--- /dev/null
+++ b/src/audio_core/renderer/effect/i3dl2.cpp
@@ -0,0 +1,94 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/i3dl2.h"
+
+namespace AudioCore::AudioRenderer {
+
+void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void I3dl2ReverbInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+}
+
+void I3dl2ReverbInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void I3dl2ReverbInfo::UpdateResultState(EffectResultState& cpu_state,
+ EffectResultState& dsp_state) {}
+
+CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) {
+ return GetSingleBuffer(index);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h
new file mode 100644
index 000000000..7a088a627
--- /dev/null
+++ b/src/audio_core/renderer/effect/i3dl2.h
@@ -0,0 +1,200 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+class I3dl2ReverbInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x10 */ char unk10[0x4];
+ /* 0x14 */ u32 sample_rate;
+ /* 0x18 */ f32 room_HF_gain;
+ /* 0x1C */ f32 reference_HF;
+ /* 0x20 */ f32 late_reverb_decay_time;
+ /* 0x24 */ f32 late_reverb_HF_decay_ratio;
+ /* 0x28 */ f32 room_gain;
+ /* 0x2C */ f32 reflection_gain;
+ /* 0x30 */ f32 reverb_gain;
+ /* 0x34 */ f32 late_reverb_diffusion;
+ /* 0x38 */ f32 reflection_delay;
+ /* 0x3C */ f32 late_reverb_delay_time;
+ /* 0x40 */ f32 late_reverb_density;
+ /* 0x44 */ f32 dry_gain;
+ /* 0x48 */ ParameterState state;
+ /* 0x49 */ char unk49[0x3];
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "I3dl2ReverbInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x10 */ char unk10[0x4];
+ /* 0x14 */ u32 sample_rate;
+ /* 0x18 */ f32 room_HF_gain;
+ /* 0x1C */ f32 reference_HF;
+ /* 0x20 */ f32 late_reverb_decay_time;
+ /* 0x24 */ f32 late_reverb_HF_decay_ratio;
+ /* 0x28 */ f32 room_gain;
+ /* 0x2C */ f32 reflection_gain;
+ /* 0x30 */ f32 reverb_gain;
+ /* 0x34 */ f32 late_reverb_diffusion;
+ /* 0x38 */ f32 reflection_delay;
+ /* 0x3C */ f32 late_reverb_delay_time;
+ /* 0x40 */ f32 late_reverb_density;
+ /* 0x44 */ f32 dry_gain;
+ /* 0x48 */ ParameterState state;
+ /* 0x49 */ char unk49[0x3];
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "I3dl2ReverbInfo::ParameterVersion2 has the wrong size!");
+
+ static constexpr u32 MaxDelayLines = 4;
+ static constexpr u32 MaxDelayTaps = 20;
+
+ struct I3dl2DelayLine {
+ void Initialize(const s32 delay_time) {
+ max_delay = delay_time;
+ buffer.resize(delay_time + 1, 0);
+ buffer_end = &buffer[delay_time];
+ output = &buffer[0];
+ SetDelay(delay_time);
+ wet_gain = 0.0f;
+ }
+
+ void SetDelay(const s32 delay_time) {
+ if (max_delay < delay_time) {
+ return;
+ }
+ delay = delay_time;
+ input = &buffer[(output - buffer.data() + delay) % (max_delay + 1)];
+ }
+
+ Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) {
+ Write(sample);
+
+ auto out_sample{Read()};
+
+ output++;
+ if (output >= buffer_end) {
+ output = buffer.data();
+ }
+
+ return out_sample;
+ }
+
+ Common::FixedPoint<50, 14> Read() {
+ return *output;
+ }
+
+ void Write(const Common::FixedPoint<50, 14> sample) {
+ *(input++) = sample;
+ if (input >= buffer_end) {
+ input = buffer.data();
+ }
+ }
+
+ Common::FixedPoint<50, 14> TapOut(const s32 index) {
+ auto out{input - (index + 1)};
+ if (out < buffer.data()) {
+ out += max_delay + 1;
+ }
+ return *out;
+ }
+
+ std::vector<Common::FixedPoint<50, 14>> buffer{};
+ Common::FixedPoint<50, 14>* buffer_end{};
+ s32 max_delay{};
+ Common::FixedPoint<50, 14>* input{};
+ Common::FixedPoint<50, 14>* output{};
+ s32 delay{};
+ f32 wet_gain{};
+ };
+
+ struct State {
+ f32 lowpass_0;
+ f32 lowpass_1;
+ f32 lowpass_2;
+ I3dl2DelayLine early_delay_line;
+ std::array<s32, MaxDelayTaps> early_tap_steps;
+ f32 early_gain;
+ f32 late_gain;
+ s32 early_to_late_taps;
+ std::array<I3dl2DelayLine, MaxDelayLines> fdn_delay_lines;
+ std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines0;
+ std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines1;
+ f32 last_reverb_echo;
+ I3dl2DelayLine center_delay_line;
+ std::array<std::array<f32, 3>, MaxDelayLines> lowpass_coeff;
+ std::array<f32, MaxDelayLines> shelf_filter;
+ f32 dry_gain;
+ };
+ static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
+ "I3dl2ReverbInfo::State is too large!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/light_limiter.cpp b/src/audio_core/renderer/effect/light_limiter.cpp
new file mode 100644
index 000000000..1635a952d
--- /dev/null
+++ b/src/audio_core/renderer/effect/light_limiter.cpp
@@ -0,0 +1,81 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/light_limiter.h"
+
+namespace AudioCore::AudioRenderer {
+
+void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
+ const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void LightLimiterInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+ params->statistics_reset_required = false;
+}
+
+void LightLimiterInfo::InitializeResultState(EffectResultState& result_state) {
+ auto result_state_{reinterpret_cast<StatisticsInternal*>(result_state.state.data())};
+
+ result_state_->channel_max_sample.fill(0);
+ result_state_->channel_compression_gain_min.fill(1.0f);
+}
+
+void LightLimiterInfo::UpdateResultState(EffectResultState& cpu_state,
+ EffectResultState& dsp_state) {
+ auto cpu_statistics{reinterpret_cast<StatisticsInternal*>(cpu_state.state.data())};
+ auto dsp_statistics{reinterpret_cast<StatisticsInternal*>(dsp_state.state.data())};
+
+ *cpu_statistics = *dsp_statistics;
+}
+
+CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) {
+ return GetSingleBuffer(index);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/light_limiter.h b/src/audio_core/renderer/effect/light_limiter.h
new file mode 100644
index 000000000..338d67bbc
--- /dev/null
+++ b/src/audio_core/renderer/effect/light_limiter.h
@@ -0,0 +1,138 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+class LightLimiterInfo : public EffectInfoBase {
+public:
+ enum class ProcessingMode {
+ Mode0,
+ Mode1,
+ };
+
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x0C */ u32 sample_rate;
+ /* 0x14 */ s32 look_ahead_time_max;
+ /* 0x18 */ s32 attack_time;
+ /* 0x1C */ s32 release_time;
+ /* 0x20 */ s32 look_ahead_time;
+ /* 0x24 */ f32 attack_coeff;
+ /* 0x28 */ f32 release_coeff;
+ /* 0x2C */ f32 threshold;
+ /* 0x30 */ f32 input_gain;
+ /* 0x34 */ f32 output_gain;
+ /* 0x38 */ s32 look_ahead_samples_min;
+ /* 0x3C */ s32 look_ahead_samples_max;
+ /* 0x40 */ ParameterState state;
+ /* 0x41 */ bool statistics_enabled;
+ /* 0x42 */ bool statistics_reset_required;
+ /* 0x43 */ ProcessingMode processing_mode;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "LightLimiterInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x0C */ u32 sample_rate;
+ /* 0x14 */ s32 look_ahead_time_max;
+ /* 0x18 */ s32 attack_time;
+ /* 0x1C */ s32 release_time;
+ /* 0x20 */ s32 look_ahead_time;
+ /* 0x24 */ f32 attack_coeff;
+ /* 0x28 */ f32 release_coeff;
+ /* 0x2C */ f32 threshold;
+ /* 0x30 */ f32 input_gain;
+ /* 0x34 */ f32 output_gain;
+ /* 0x38 */ s32 look_ahead_samples_min;
+ /* 0x3C */ s32 look_ahead_samples_max;
+ /* 0x40 */ ParameterState state;
+ /* 0x41 */ bool statistics_enabled;
+ /* 0x42 */ bool statistics_reset_required;
+ /* 0x43 */ ProcessingMode processing_mode;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "LightLimiterInfo::ParameterVersion2 has the wrong size!");
+
+ struct State {
+ std::array<Common::FixedPoint<49, 15>, MaxChannels> samples_average;
+ std::array<Common::FixedPoint<49, 15>, MaxChannels> compression_gain;
+ std::array<s32, MaxChannels> look_ahead_sample_offsets;
+ std::array<std::vector<Common::FixedPoint<49, 15>>, MaxChannels> look_ahead_sample_buffers;
+ };
+ static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
+ "LightLimiterInfo::State has the wrong size!");
+
+ struct StatisticsInternal {
+ /* 0x00 */ std::array<f32, MaxChannels> channel_max_sample;
+ /* 0x18 */ std::array<f32, MaxChannels> channel_compression_gain_min;
+ };
+ static_assert(sizeof(StatisticsInternal) == 0x30,
+ "LightLimiterInfo::StatisticsInternal has the wrong size!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new limiter statistics result state. Version 2 only.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side limiter statistics with the ADSP-side one. Version 2 only.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/reverb.cpp b/src/audio_core/renderer/effect/reverb.cpp
new file mode 100644
index 000000000..2d32383d0
--- /dev/null
+++ b/src/audio_core/renderer/effect/reverb.cpp
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/effect/reverb.h"
+
+namespace AudioCore::AudioRenderer {
+
+void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion1));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) {
+ auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
+ auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
+
+ if (IsChannelCountValid(in_specific->channel_count_max)) {
+ const auto old_state{params->state};
+ std::memcpy(params, in_specific, sizeof(ParameterVersion2));
+ mix_id = in_params.mix_id;
+ process_order = in_params.process_order;
+ enabled = in_params.enabled;
+
+ if (!IsChannelCountValid(in_specific->channel_count)) {
+ params->channel_count = params->channel_count_max;
+ }
+
+ if (!IsChannelCountValid(in_specific->channel_count) ||
+ old_state != ParameterState::Updated) {
+ params->state = old_state;
+ }
+
+ if (buffer_unmapped || in_params.is_new) {
+ usage_state = UsageState::New;
+ params->state = ParameterState::Initialized;
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(
+ error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
+ return;
+ }
+ }
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void ReverbInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage_state = UsageState::Enabled;
+ } else {
+ usage_state = UsageState::Disabled;
+ }
+
+ auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
+ params->state = ParameterState::Updated;
+}
+
+void ReverbInfo::InitializeResultState(EffectResultState& result_state) {}
+
+void ReverbInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
+
+CpuAddr ReverbInfo::GetWorkbuffer(s32 index) {
+ return GetSingleBuffer(index);
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h
new file mode 100644
index 000000000..b4df9f6ef
--- /dev/null
+++ b/src/audio_core/renderer/effect/reverb.h
@@ -0,0 +1,190 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+
+class ReverbInfo : public EffectInfoBase {
+public:
+ struct ParameterVersion1 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x10 */ u32 sample_rate;
+ /* 0x14 */ u32 early_mode;
+ /* 0x18 */ s32 early_gain;
+ /* 0x1C */ s32 pre_delay;
+ /* 0x20 */ s32 late_mode;
+ /* 0x24 */ s32 late_gain;
+ /* 0x28 */ s32 decay_time;
+ /* 0x2C */ s32 high_freq_Decay_ratio;
+ /* 0x30 */ s32 colouration;
+ /* 0x34 */ s32 base_gain;
+ /* 0x38 */ s32 wet_gain;
+ /* 0x3C */ s32 dry_gain;
+ /* 0x40 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
+ "ReverbInfo::ParameterVersion1 has the wrong size!");
+
+ struct ParameterVersion2 {
+ /* 0x00 */ std::array<s8, MaxChannels> inputs;
+ /* 0x06 */ std::array<s8, MaxChannels> outputs;
+ /* 0x0C */ u16 channel_count_max;
+ /* 0x0E */ u16 channel_count;
+ /* 0x10 */ u32 sample_rate;
+ /* 0x14 */ u32 early_mode;
+ /* 0x18 */ s32 early_gain;
+ /* 0x1C */ s32 pre_delay;
+ /* 0x20 */ s32 late_mode;
+ /* 0x24 */ s32 late_gain;
+ /* 0x28 */ s32 decay_time;
+ /* 0x2C */ s32 high_freq_decay_ratio;
+ /* 0x30 */ s32 colouration;
+ /* 0x34 */ s32 base_gain;
+ /* 0x38 */ s32 wet_gain;
+ /* 0x3C */ s32 dry_gain;
+ /* 0x40 */ ParameterState state;
+ };
+ static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
+ "ReverbInfo::ParameterVersion2 has the wrong size!");
+
+ static constexpr u32 MaxDelayLines = 4;
+ static constexpr u32 MaxDelayTaps = 10;
+ static constexpr u32 NumEarlyModes = 5;
+ static constexpr u32 NumLateModes = 5;
+
+ struct ReverbDelayLine {
+ void Initialize(const s32 delay_time, const f32 decay_rate) {
+ buffer.resize(delay_time + 1, 0);
+ buffer_end = &buffer[delay_time];
+ output = &buffer[0];
+ decay = decay_rate;
+ sample_count_max = delay_time;
+ SetDelay(delay_time);
+ }
+
+ void SetDelay(const s32 delay_time) {
+ if (sample_count_max < delay_time) {
+ return;
+ }
+ sample_count = delay_time;
+ input = &buffer[(output - buffer.data() + sample_count) % (sample_count_max + 1)];
+ }
+
+ Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) {
+ Write(sample);
+
+ auto out_sample{Read()};
+
+ output++;
+ if (output >= buffer_end) {
+ output = buffer.data();
+ }
+
+ return out_sample;
+ }
+
+ Common::FixedPoint<50, 14> Read() {
+ return *output;
+ }
+
+ void Write(const Common::FixedPoint<50, 14> sample) {
+ *(input++) = sample;
+ if (input >= buffer_end) {
+ input = buffer.data();
+ }
+ }
+
+ Common::FixedPoint<50, 14> TapOut(const s32 index) {
+ auto out{input - (index + 1)};
+ if (out < buffer.data()) {
+ out += sample_count;
+ }
+ return *out;
+ }
+
+ s32 sample_count{};
+ s32 sample_count_max{};
+ std::vector<Common::FixedPoint<50, 14>> buffer{};
+ Common::FixedPoint<50, 14>* buffer_end;
+ Common::FixedPoint<50, 14>* input{};
+ Common::FixedPoint<50, 14>* output{};
+ Common::FixedPoint<50, 14> decay{};
+ };
+
+ struct State {
+ ReverbDelayLine pre_delay_line;
+ ReverbDelayLine center_delay_line;
+ std::array<s32, MaxDelayTaps> early_delay_times;
+ std::array<Common::FixedPoint<50, 14>, MaxDelayTaps> early_gains;
+ s32 pre_delay_time;
+ std::array<ReverbDelayLine, MaxDelayLines> decay_delay_lines;
+ std::array<ReverbDelayLine, MaxDelayLines> fdn_delay_lines;
+ std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_gain;
+ std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_prev_gain;
+ std::array<Common::FixedPoint<50, 14>, MaxDelayLines> prev_feedback_output;
+ };
+ static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
+ "ReverbInfo::State is too large!");
+
+ /**
+ * Update the info with new parameters, version 1.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info with new parameters, version 2.
+ *
+ * @param error_info - Used to write call result code.
+ * @param in_params - New parameters to update the info with.
+ * @param pool_mapper - Pool for mapping buffers.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
+ const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the info after command generation. Usually only changes its state.
+ */
+ void UpdateForCommandGeneration() override;
+
+ /**
+ * Initialize a new result state. Version 2 only, unused.
+ *
+ * @param result_state - Result state to initialize.
+ */
+ void InitializeResultState(EffectResultState& result_state) override;
+
+ /**
+ * Update the host-side state with the ADSP-side state. Version 2 only, unused.
+ *
+ * @param cpu_state - Host-side result state to update.
+ * @param dsp_state - AudioRenderer-side result state to update from.
+ */
+ void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
+
+ /**
+ * Get a workbuffer assigned to this effect with the given index.
+ *
+ * @param index - Workbuffer index.
+ * @return Address of the buffer.
+ */
+ CpuAddr GetWorkbuffer(s32 index) override;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h
new file mode 100644
index 000000000..4cfefea8e
--- /dev/null
+++ b/src/audio_core/renderer/memory/address_info.h
@@ -0,0 +1,125 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+/**
+ * Represents a region of mapped or unmapped memory.
+ */
+class AddressInfo {
+public:
+ AddressInfo() = default;
+ AddressInfo(CpuAddr cpu_address_, u64 size_) : cpu_address{cpu_address_}, size{size_} {}
+
+ /**
+ * Setup a new AddressInfo.
+ *
+ * @param cpu_address - The CPU address of this region.
+ * @param size - The size of this region.
+ */
+ void Setup(CpuAddr cpu_address_, u64 size_) {
+ cpu_address = cpu_address_;
+ size = size_;
+ memory_pool = nullptr;
+ dsp_address = 0;
+ }
+
+ /**
+ * Get the CPU address.
+ *
+ * @return The CpuAddr address
+ */
+ CpuAddr GetCpuAddr() const {
+ return cpu_address;
+ }
+
+ /**
+ * Assign this region to a memory pool.
+ *
+ * @param memory_pool_ - Memory pool to assign.
+ * @return The CpuAddr address of this region.
+ */
+ void SetPool(MemoryPoolInfo* memory_pool_) {
+ memory_pool = memory_pool_;
+ }
+
+ /**
+ * Get the size of this region.
+ *
+ * @return The size of this region.
+ */
+ u64 GetSize() const {
+ return size;
+ }
+
+ /**
+ * Get the ADSP address for this region.
+ *
+ * @return The ADSP address for this region.
+ */
+ CpuAddr GetForceMappedDspAddr() const {
+ return dsp_address;
+ }
+
+ /**
+ * Set the ADSP address for this region.
+ *
+ * @param dsp_addr - The new ADSP address for this region.
+ */
+ void SetForceMappedDspAddr(CpuAddr dsp_addr) {
+ dsp_address = dsp_addr;
+ }
+
+ /**
+ * Check whether this region has an active memory pool.
+ *
+ * @return True if this region has a mapped memory pool, otherwise false.
+ */
+ bool HasMappedMemoryPool() const {
+ return memory_pool != nullptr && memory_pool->GetDspAddress() != 0;
+ }
+
+ /**
+ * Check whether this region is mapped to the ADSP.
+ *
+ * @return True if this region is mapped, otherwise false.
+ */
+ bool IsMapped() const {
+ return HasMappedMemoryPool() || dsp_address != 0;
+ }
+
+ /**
+ * Get a usable reference to this region of memory.
+ *
+ * @param mark_in_use - Whether this region should be marked as being in use.
+ * @return A valid memory address if valid, otherwise 0.
+ */
+ CpuAddr GetReference(bool mark_in_use) {
+ if (!HasMappedMemoryPool()) {
+ return dsp_address;
+ }
+
+ if (mark_in_use) {
+ memory_pool->SetUsed(true);
+ }
+
+ return memory_pool->Translate(cpu_address, size);
+ }
+
+private:
+ /// CPU address of this region
+ CpuAddr cpu_address;
+ /// Size of this region
+ u64 size;
+ /// The memory this region is mapped to
+ MemoryPoolInfo* memory_pool;
+ /// ADSP address of this region
+ CpuAddr dsp_address;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/memory_pool_info.cpp b/src/audio_core/renderer/memory/memory_pool_info.cpp
new file mode 100644
index 000000000..9b7824af1
--- /dev/null
+++ b/src/audio_core/renderer/memory/memory_pool_info.cpp
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/memory/memory_pool_info.h"
+
+namespace AudioCore::AudioRenderer {
+
+CpuAddr MemoryPoolInfo::GetCpuAddress() const {
+ return cpu_address;
+}
+
+CpuAddr MemoryPoolInfo::GetDspAddress() const {
+ return dsp_address;
+}
+
+u64 MemoryPoolInfo::GetSize() const {
+ return size;
+}
+
+MemoryPoolInfo::Location MemoryPoolInfo::GetLocation() const {
+ return location;
+}
+
+void MemoryPoolInfo::SetCpuAddress(const CpuAddr address, const u64 size_) {
+ cpu_address = address;
+ size = size_;
+}
+
+void MemoryPoolInfo::SetDspAddress(const CpuAddr address) {
+ dsp_address = address;
+}
+
+bool MemoryPoolInfo::Contains(const CpuAddr address_, const u64 size_) const {
+ return cpu_address <= address_ && (address_ + size_) <= (cpu_address + size);
+}
+
+bool MemoryPoolInfo::IsMapped() const {
+ return dsp_address != 0;
+}
+
+CpuAddr MemoryPoolInfo::Translate(const CpuAddr address, const u64 size_) const {
+ if (!Contains(address, size_)) {
+ return 0;
+ }
+
+ if (!IsMapped()) {
+ return 0;
+ }
+
+ return dsp_address + (address - cpu_address);
+}
+
+void MemoryPoolInfo::SetUsed(const bool used) {
+ in_use = used;
+}
+
+bool MemoryPoolInfo::IsUsed() const {
+ return in_use;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/memory_pool_info.h b/src/audio_core/renderer/memory/memory_pool_info.h
new file mode 100644
index 000000000..537a466ec
--- /dev/null
+++ b/src/audio_core/renderer/memory/memory_pool_info.h
@@ -0,0 +1,170 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper).
+ */
+class MemoryPoolInfo {
+public:
+ /**
+ * The location of this pool.
+ * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper).
+ * DSP pools are mapped in the current process sysmodule.
+ */
+ enum class Location {
+ CPU = 1,
+ DSP = 2,
+ };
+
+ /**
+ * Current state of the pool
+ */
+ enum class State {
+ Invalid,
+ Aquired,
+ RequestDetach,
+ Detached,
+ RequestAttach,
+ Attached,
+ Released,
+ };
+
+ /**
+ * Result code for updating the pool (See InfoUpdater::Update)
+ */
+ enum class ResultState {
+ Success,
+ BadParam,
+ MapFailed,
+ InUse,
+ };
+
+ /**
+ * Input parameters coming from the game which are used to update current pools
+ * (See InfoUpdater::Update)
+ */
+ struct InParameter {
+ /* 0x00 */ u64 address;
+ /* 0x08 */ u64 size;
+ /* 0x10 */ State state;
+ /* 0x14 */ bool in_use;
+ /* 0x18 */ char unk18[0x8];
+ };
+ static_assert(sizeof(InParameter) == 0x20, "MemoryPoolInfo::InParameter has the wrong size!");
+
+ /**
+ * Output status sent back to the game on update (See InfoUpdater::Update)
+ */
+ struct OutStatus {
+ /* 0x00 */ State state;
+ /* 0x04 */ char unk04[0xC];
+ };
+ static_assert(sizeof(OutStatus) == 0x10, "MemoryPoolInfo::OutStatus has the wrong size!");
+
+ MemoryPoolInfo() = default;
+ MemoryPoolInfo(Location location_) : location{location_} {}
+
+ /**
+ * Get the CPU address for this pool.
+ *
+ * @return The CPU address of this pool.
+ */
+ CpuAddr GetCpuAddress() const;
+
+ /**
+ * Get the DSP address for this pool.
+ *
+ * @return The DSP address of this pool.
+ */
+ CpuAddr GetDspAddress() const;
+
+ /**
+ * Get the size of this pool.
+ *
+ * @return The size of this pool.
+ */
+ u64 GetSize() const;
+
+ /**
+ * Get the location of this pool.
+ *
+ * @return The location for the pool (see MemoryPoolInfo::Location).
+ */
+ Location GetLocation() const;
+
+ /**
+ * Set the CPU address for this pool.
+ *
+ * @param address - The new CPU address for this pool.
+ * @param size - The new size for this pool.
+ */
+ void SetCpuAddress(CpuAddr address, u64 size);
+
+ /**
+ * Set the DSP address for this pool.
+ *
+ * @param address - The new DSP address for this pool.
+ */
+ void SetDspAddress(CpuAddr address);
+
+ /**
+ * Check whether the pool contains a given range.
+ *
+ * @param address - The buffer address to look for.
+ * @param size - The size of the given buffer.
+ * @return True if the range is within this pool, otherwise false.
+ */
+ bool Contains(CpuAddr address, u64 size) const;
+
+ /**
+ * Check whether this pool is mapped, which is when the dsp address is set.
+ *
+ * @return True if the pool is mapped, otherwise false.
+ */
+ bool IsMapped() const;
+
+ /**
+ * Translates a given CPU range into a relative offset for the DSP.
+ *
+ * @param address - The buffer address to look for.
+ * @param size - The size of the given buffer.
+ * @return Pointer to the DSP-mapped memory.
+ */
+ CpuAddr Translate(CpuAddr address, u64 size) const;
+
+ /**
+ * Set or unset whether this memory pool is in use.
+ *
+ * @param used - Use state for this pool.
+ */
+ void SetUsed(bool used);
+
+ /**
+ * Get whether this pool is in use.
+ *
+ * @return True if in use, otherwise false.
+ */
+ bool IsUsed() const;
+
+private:
+ /// Base address for the CPU-side memory
+ CpuAddr cpu_address{};
+ /// Base address for the DSP-side memory
+ CpuAddr dsp_address{};
+ /// Size of this pool
+ u64 size{};
+ /// Location of this pool, either CPU or DSP
+ Location location{Location::DSP};
+ /// If this pool is in use
+ bool in_use{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/pool_mapper.cpp b/src/audio_core/renderer/memory/pool_mapper.cpp
new file mode 100644
index 000000000..2baf2ce08
--- /dev/null
+++ b/src/audio_core/renderer/memory/pool_mapper.cpp
@@ -0,0 +1,243 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/memory/address_info.h"
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/svc.h"
+
+namespace AudioCore::AudioRenderer {
+
+PoolMapper::PoolMapper(u32 process_handle_, bool force_map_)
+ : process_handle{process_handle_}, force_map{force_map_} {}
+
+PoolMapper::PoolMapper(u32 process_handle_, std::span<MemoryPoolInfo> pool_infos_, u32 pool_count_,
+ bool force_map_)
+ : process_handle{process_handle_}, pool_infos{pool_infos_.data()},
+ pool_count{pool_count_}, force_map{force_map_} {}
+
+void PoolMapper::ClearUseState(std::span<MemoryPoolInfo> pools, const u32 count) {
+ for (u32 i = 0; i < count; i++) {
+ pools[i].SetUsed(false);
+ }
+}
+
+MemoryPoolInfo* PoolMapper::FindMemoryPool(MemoryPoolInfo* pools, const u64 count,
+ const CpuAddr address, const u64 size) const {
+ auto pool{pools};
+ for (u64 i = 0; i < count; i++, pool++) {
+ if (pool->Contains(address, size)) {
+ return pool;
+ }
+ }
+ return nullptr;
+}
+
+MemoryPoolInfo* PoolMapper::FindMemoryPool(const CpuAddr address, const u64 size) const {
+ auto pool{pool_infos};
+ for (u64 i = 0; i < pool_count; i++, pool++) {
+ if (pool->Contains(address, size)) {
+ return pool;
+ }
+ }
+ return nullptr;
+}
+
+bool PoolMapper::FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools,
+ const u32 count) const {
+ if (address_info.GetCpuAddr() == 0) {
+ address_info.SetPool(nullptr);
+ return false;
+ }
+
+ auto found_pool{
+ FindMemoryPool(pools, count, address_info.GetCpuAddr(), address_info.GetSize())};
+ if (found_pool != nullptr) {
+ address_info.SetPool(found_pool);
+ return true;
+ }
+
+ if (force_map) {
+ address_info.SetForceMappedDspAddr(address_info.GetCpuAddr());
+ } else {
+ address_info.SetPool(nullptr);
+ }
+
+ return false;
+}
+
+bool PoolMapper::FillDspAddr(AddressInfo& address_info) const {
+ if (address_info.GetCpuAddr() == 0) {
+ address_info.SetPool(nullptr);
+ return false;
+ }
+
+ auto found_pool{FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())};
+ if (found_pool != nullptr) {
+ address_info.SetPool(found_pool);
+ return true;
+ }
+
+ if (force_map) {
+ address_info.SetForceMappedDspAddr(address_info.GetCpuAddr());
+ } else {
+ address_info.SetPool(nullptr);
+ }
+
+ return false;
+}
+
+bool PoolMapper::TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info,
+ const CpuAddr address, const u64 size) const {
+ address_info.Setup(address, size);
+
+ if (!FillDspAddr(address_info)) {
+ error_info.error_code = Service::Audio::ERR_POOL_MAPPING_FAILED;
+ error_info.address = address;
+ return force_map;
+ }
+
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ return true;
+}
+
+bool PoolMapper::IsForceMapEnabled() const {
+ return force_map;
+}
+
+u32 PoolMapper::GetProcessHandle(const MemoryPoolInfo* pool) const {
+ switch (pool->GetLocation()) {
+ case MemoryPoolInfo::Location::CPU:
+ return process_handle;
+ case MemoryPoolInfo::Location::DSP:
+ return Kernel::Svc::CurrentProcess;
+ }
+ LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location!");
+ return Kernel::Svc::CurrentProcess;
+}
+
+bool PoolMapper::Map([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr,
+ [[maybe_unused]] const u64 size) const {
+ // nn::audio::dsp::MapUserPointer(handle, cpu_addr, size);
+ return true;
+}
+
+bool PoolMapper::Map(MemoryPoolInfo& pool) const {
+ switch (pool.GetLocation()) {
+ case MemoryPoolInfo::Location::CPU:
+ // Map with process_handle
+ pool.SetDspAddress(pool.GetCpuAddress());
+ return true;
+ case MemoryPoolInfo::Location::DSP:
+ // Map with Kernel::Svc::CurrentProcess
+ pool.SetDspAddress(pool.GetCpuAddress());
+ return true;
+ default:
+ LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!",
+ static_cast<u32>(pool.GetLocation()));
+ return false;
+ }
+}
+
+bool PoolMapper::Unmap([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr,
+ [[maybe_unused]] const u64 size) const {
+ // nn::audio::dsp::UnmapUserPointer(handle, cpu_addr, size);
+ return true;
+}
+
+bool PoolMapper::Unmap(MemoryPoolInfo& pool) const {
+ [[maybe_unused]] u32 handle{0};
+
+ switch (pool.GetLocation()) {
+ case MemoryPoolInfo::Location::CPU:
+ handle = process_handle;
+ break;
+ case MemoryPoolInfo::Location::DSP:
+ handle = Kernel::Svc::CurrentProcess;
+ break;
+ }
+ // nn::audio::dsp::UnmapUserPointer(handle, pool->cpu_address, pool->size);
+ pool.SetCpuAddress(0, 0);
+ pool.SetDspAddress(0);
+ return true;
+}
+
+void PoolMapper::ForceUnmapPointer(const AddressInfo& address_info) const {
+ if (force_map) {
+ [[maybe_unused]] auto found_pool{
+ FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())};
+ // nn::audio::dsp::UnmapUserPointer(this->processHandle, address_info.GetCpuAddr(), 0);
+ }
+}
+
+MemoryPoolInfo::ResultState PoolMapper::Update(MemoryPoolInfo& pool,
+ const MemoryPoolInfo::InParameter& in_params,
+ MemoryPoolInfo::OutStatus& out_params) const {
+ if (in_params.state != MemoryPoolInfo::State::RequestAttach &&
+ in_params.state != MemoryPoolInfo::State::RequestDetach) {
+ return MemoryPoolInfo::ResultState::Success;
+ }
+
+ if (in_params.address == 0 || in_params.size == 0 || !Common::Is4KBAligned(in_params.address) ||
+ !Common::Is4KBAligned(in_params.size)) {
+ return MemoryPoolInfo::ResultState::BadParam;
+ }
+
+ switch (in_params.state) {
+ case MemoryPoolInfo::State::RequestAttach:
+ pool.SetCpuAddress(in_params.address, in_params.size);
+
+ Map(pool);
+
+ if (pool.IsMapped()) {
+ out_params.state = MemoryPoolInfo::State::Attached;
+ return MemoryPoolInfo::ResultState::Success;
+ }
+ pool.SetCpuAddress(0, 0);
+ return MemoryPoolInfo::ResultState::MapFailed;
+
+ case MemoryPoolInfo::State::RequestDetach:
+ if (pool.GetCpuAddress() != in_params.address || pool.GetSize() != in_params.size) {
+ return MemoryPoolInfo::ResultState::BadParam;
+ }
+
+ if (pool.IsUsed()) {
+ return MemoryPoolInfo::ResultState::InUse;
+ }
+
+ Unmap(pool);
+
+ pool.SetCpuAddress(0, 0);
+ pool.SetDspAddress(0);
+ out_params.state = MemoryPoolInfo::State::Detached;
+ return MemoryPoolInfo::ResultState::Success;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid MemoryPoolInfo::State!");
+ break;
+ }
+
+ return MemoryPoolInfo::ResultState::Success;
+}
+
+bool PoolMapper::InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory,
+ const u64 size_) const {
+ switch (pool.GetLocation()) {
+ case MemoryPoolInfo::Location::CPU:
+ return false;
+ case MemoryPoolInfo::Location::DSP:
+ pool.SetCpuAddress(reinterpret_cast<u64>(memory), size_);
+ if (Map(Kernel::Svc::CurrentProcess, reinterpret_cast<u64>(memory), size_)) {
+ pool.SetDspAddress(pool.GetCpuAddress());
+ return true;
+ }
+ return false;
+ default:
+ LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!",
+ static_cast<u32>(pool.GetLocation()));
+ return false;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/pool_mapper.h b/src/audio_core/renderer/memory/pool_mapper.h
new file mode 100644
index 000000000..9a691da7a
--- /dev/null
+++ b/src/audio_core/renderer/memory/pool_mapper.h
@@ -0,0 +1,179 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "common/common_types.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::AudioRenderer {
+class AddressInfo;
+
+/**
+ * Utility functions for managing MemoryPoolInfos
+ */
+class PoolMapper {
+public:
+ explicit PoolMapper(u32 process_handle, bool force_map);
+ explicit PoolMapper(u32 process_handle, std::span<MemoryPoolInfo> pool_infos, u32 pool_count,
+ bool force_map);
+
+ /**
+ * Clear the usage state for all given pools.
+ *
+ * @param pools - The memory pools to clear.
+ * @param count - The number of pools.
+ */
+ static void ClearUseState(std::span<MemoryPoolInfo> pools, u32 count);
+
+ /**
+ * Find the memory pool containing the given address and size from a given list of pools.
+ *
+ * @param pools - The memory pools to search within.
+ * @param count - The number of pools.
+ * @param address - The address of the region to find.
+ * @param size - The size of the region to find.
+ * @return Pointer to the memory pool if found, otherwise nullptr.
+ */
+ MemoryPoolInfo* FindMemoryPool(MemoryPoolInfo* pools, u64 count, CpuAddr address,
+ u64 size) const;
+
+ /**
+ * Find the memory pool containing the given address and size from the PoolMapper's memory pool.
+ *
+ * @param address - The address of the region to find.
+ * @param size - The size of the region to find.
+ * @return Pointer to the memory pool if found, otherwise nullptr.
+ */
+ MemoryPoolInfo* FindMemoryPool(CpuAddr address, u64 size) const;
+
+ /**
+ * Set the PoolMapper's memory pool to one in the given list of pools, which contains
+ * address_info.
+ *
+ * @param address_info - The expected region to find within pools.
+ * @param pools - The list of pools to search within.
+ * @param count - The number of pools given.
+ * @return True if successfully mapped, otherwise false.
+ */
+ bool FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools, u32 count) const;
+
+ /**
+ * Set the PoolMapper's memory pool to the one containing address_info.
+ *
+ * @param address_info - The address to find the memory pool for.
+ * @return True if successfully mapped, otherwise false.
+ */
+ bool FillDspAddr(AddressInfo& address_info) const;
+
+ /**
+ * Try to attach a {address, size} region to the given address_info, and map it. Fills in the
+ * given error_info and address_info.
+ *
+ * @param error_info - Output error info.
+ * @param address_info - Output address info, initialized with the given {address, size} and
+ * attempted to map.
+ * @param address - Address of the region to map.
+ * @param size - Size of the region to map.
+ * @return True if successfully attached, otherwise false.
+ */
+ bool TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info,
+ CpuAddr address, u64 size) const;
+
+ /**
+ * Return whether force mapping is enabled.
+ *
+ * @return True if force mapping is enabled, otherwise false.
+ */
+ bool IsForceMapEnabled() const;
+
+ /**
+ * Get the process handle, depending on location.
+ *
+ * @param pool - The pool to check the location of.
+ * @return CurrentProcessHandle if location == DSP,
+ * the PoolMapper's process_handle if location == CPU
+ */
+ u32 GetProcessHandle(const MemoryPoolInfo* pool) const;
+
+ /**
+ * Map the given region with the given handle. This is a no-op.
+ *
+ * @param handle - The process handle to map to.
+ * @param cpu_addr - Address to map.
+ * @param size - Size to map.
+ * @return True if successfully mapped, otherwise false.
+ */
+ bool Map(u32 handle, CpuAddr cpu_addr, u64 size) const;
+
+ /**
+ * Map the given memory pool.
+ *
+ * @param pool - The pool to map.
+ * @return True if successfully mapped, otherwise false.
+ */
+ bool Map(MemoryPoolInfo& pool) const;
+
+ /**
+ * Unmap the given region with the given handle.
+ *
+ * @param handle - The process handle to unmap to.
+ * @param cpu_addr - Address to unmap.
+ * @param size - Size to unmap.
+ * @return True if successfully unmapped, otherwise false.
+ */
+ bool Unmap(u32 handle, CpuAddr cpu_addr, u64 size) const;
+
+ /**
+ * Unmap the given memory pool.
+ *
+ * @param pool - The pool to unmap.
+ * @return True if successfully unmapped, otherwise false.
+ */
+ bool Unmap(MemoryPoolInfo& pool) const;
+
+ /**
+ * Forcibly unmap the given region.
+ *
+ * @param address_info - The region to unmap.
+ */
+ void ForceUnmapPointer(const AddressInfo& address_info) const;
+
+ /**
+ * Update the given memory pool.
+ *
+ * @param pool - Pool to update.
+ * @param in_params - Input parameters for the update.
+ * @param out_params - Output parameters for the update.
+ * @return The result of the update. See MemoryPoolInfo::ResultState
+ */
+ MemoryPoolInfo::ResultState Update(MemoryPoolInfo& pool,
+ const MemoryPoolInfo::InParameter& in_params,
+ MemoryPoolInfo::OutStatus& out_params) const;
+
+ /**
+ * Initialize the PoolMapper's memory pool.
+ *
+ * @param pool - Input pool to initialize.
+ * @param memory - Pointer to the memory region for the pool.
+ * @param size - Size of the memory region for the pool.
+ * @return True if initialized successfully, otherwise false.
+ */
+ bool InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory, u64 size) const;
+
+private:
+ /// Process handle for this mapper, used when location == CPU
+ u32 process_handle;
+ /// List of memory pools assigned to this mapper
+ MemoryPoolInfo* pool_infos{};
+ /// The number of pools
+ u64 pool_count{};
+ /// Is forced mapping enabled
+ bool force_map;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_context.cpp b/src/audio_core/renderer/mix/mix_context.cpp
new file mode 100644
index 000000000..2427c83ed
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_context.cpp
@@ -0,0 +1,141 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <ranges>
+
+#include "audio_core/renderer/mix/mix_context.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_,
+ const u32 count_, std::span<s32> effect_process_order_buffer_,
+ const u32 effect_count_, std::span<u8> node_states_workbuffer,
+ const u64 node_buffer_size, std::span<u8> edge_matrix_workbuffer,
+ const u64 edge_matrix_size) {
+ count = count_;
+ sorted_mix_infos = sorted_mix_infos_;
+ mix_infos = mix_infos_;
+ effect_process_order_buffer = effect_process_order_buffer_;
+ effect_count = effect_count_;
+
+ if (node_states_workbuffer.size() > 0 && edge_matrix_workbuffer.size() > 0) {
+ node_states.Initialize(node_states_workbuffer, node_buffer_size, count);
+ edge_matrix.Initialize(edge_matrix_workbuffer, edge_matrix_size, count);
+ }
+
+ for (s32 i = 0; i < count; i++) {
+ sorted_mix_infos[i] = &mix_infos[i];
+ }
+}
+
+MixInfo* MixContext::GetSortedInfo(const s32 index) {
+ return sorted_mix_infos[index];
+}
+
+void MixContext::SetSortedInfo(const s32 index, MixInfo& mix_info) {
+ sorted_mix_infos[index] = &mix_info;
+}
+
+MixInfo* MixContext::GetInfo(const s32 index) {
+ return &mix_infos[index];
+}
+
+MixInfo* MixContext::GetFinalMixInfo() {
+ return &mix_infos[0];
+}
+
+s32 MixContext::GetCount() const {
+ return count;
+}
+
+void MixContext::UpdateDistancesFromFinalMix() {
+ for (s32 i = 0; i < count; i++) {
+ mix_infos[i].distance_from_final_mix = InvalidDistanceFromFinalMix;
+ }
+
+ for (s32 i = 0; i < count; i++) {
+ auto& mix_info{mix_infos[i]};
+ sorted_mix_infos[i] = &mix_info;
+
+ if (!mix_info.in_use) {
+ continue;
+ }
+
+ auto mix_id{mix_info.mix_id};
+ auto distance_to_final_mix{FinalMixId};
+
+ while (distance_to_final_mix < count) {
+ if (mix_id == FinalMixId) {
+ break;
+ }
+
+ if (mix_id == UnusedMixId) {
+ distance_to_final_mix = InvalidDistanceFromFinalMix;
+ break;
+ }
+
+ auto distance_from_final_mix{mix_infos[mix_id].distance_from_final_mix};
+ if (distance_from_final_mix != InvalidDistanceFromFinalMix) {
+ distance_to_final_mix = distance_from_final_mix + 1;
+ break;
+ }
+
+ distance_to_final_mix++;
+ mix_id = mix_infos[mix_id].dst_mix_id;
+ }
+
+ if (distance_to_final_mix >= count) {
+ distance_to_final_mix = InvalidDistanceFromFinalMix;
+ }
+ mix_info.distance_from_final_mix = distance_to_final_mix;
+ }
+}
+
+void MixContext::SortInfo() {
+ UpdateDistancesFromFinalMix();
+
+ std::ranges::sort(sorted_mix_infos, [](const MixInfo* lhs, const MixInfo* rhs) {
+ return lhs->distance_from_final_mix > rhs->distance_from_final_mix;
+ });
+
+ CalcMixBufferOffset();
+}
+
+void MixContext::CalcMixBufferOffset() {
+ s16 offset{0};
+ for (s32 i = 0; i < count; i++) {
+ auto mix_info{sorted_mix_infos[i]};
+ if (mix_info->in_use) {
+ const auto buffer_count{mix_info->buffer_count};
+ mix_info->buffer_offset = offset;
+ offset += buffer_count;
+ }
+ }
+}
+
+bool MixContext::TSortInfo(const SplitterContext& splitter_context) {
+ if (!splitter_context.UsingSplitter()) {
+ CalcMixBufferOffset();
+ return true;
+ }
+
+ if (!node_states.Tsort(edge_matrix)) {
+ return false;
+ }
+
+ std::vector<s32> sorted_results{node_states.GetSortedResuls()};
+ const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))};
+ for (s32 i = 0; i < result_size; i++) {
+ sorted_mix_infos[i] = &mix_infos[sorted_results[i]];
+ }
+
+ CalcMixBufferOffset();
+ return true;
+}
+
+EdgeMatrix& MixContext::GetEdgeMatrix() {
+ return edge_matrix;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_context.h b/src/audio_core/renderer/mix/mix_context.h
new file mode 100644
index 000000000..da3aa2829
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_context.h
@@ -0,0 +1,124 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/mix/mix_info.h"
+#include "audio_core/renderer/nodes/edge_matrix.h"
+#include "audio_core/renderer/nodes/node_states.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class SplitterContext;
+
+/*
+ * Manages mixing states, sorting and building a node graph to describe a mix order.
+ */
+class MixContext {
+public:
+ /**
+ * Initialize the mix context.
+ *
+ * @param sorted_mix_infos - Buffer for the sorted mix infos.
+ * @param mix_infos - Buffer for the mix infos.
+ * @param effect_process_order_buffer - Buffer for the effect process orders.
+ * @param effect_count - Number of effects in the buffer.
+ * @param node_states_workbuffer - Buffer for node states.
+ * @param node_buffer_size - Size of the node states buffer.
+ * @param edge_matrix_workbuffer - Buffer for edge matrix.
+ * @param edge_matrix_size - Size of the edge matrix buffer.
+ */
+ void Initialize(std::span<MixInfo*> sorted_mix_infos, std::span<MixInfo> mix_infos, u32 count_,
+ std::span<s32> effect_process_order_buffer, u32 effect_count,
+ std::span<u8> node_states_workbuffer, u64 node_buffer_size,
+ std::span<u8> edge_matrix_workbuffer, u64 edge_matrix_size);
+
+ /**
+ * Get a sorted mix at the given index.
+ *
+ * @param index - Index of sorted mix.
+ * @return The sorted mix.
+ */
+ MixInfo* GetSortedInfo(s32 index);
+
+ /**
+ * Set the sorted info at the given index.
+ *
+ * @param index - Index of sorted mix.
+ * @param mix_info - The new mix for this index.
+ */
+ void SetSortedInfo(s32 index, MixInfo& mix_info);
+
+ /**
+ * Get a mix at the given index.
+ *
+ * @param index - Index of mix.
+ * @return The mix.
+ */
+ MixInfo* GetInfo(s32 index);
+
+ /**
+ * Get the final mix.
+ *
+ * @return The final mix.
+ */
+ MixInfo* GetFinalMixInfo();
+
+ /**
+ * Get the current number of mixes.
+ *
+ * @return The number of active mixes.
+ */
+ s32 GetCount() const;
+
+ /**
+ * Update all of the mixes' distance from the final mix.
+ * Needs to be called after altering the mix graph.
+ */
+ void UpdateDistancesFromFinalMix();
+
+ /**
+ * Non-splitter sort, sorts the sorted mixes based on their distance from the final mix.
+ */
+ void SortInfo();
+
+ /**
+ * Re-calculate the mix buffer offsets for each mix after altering the mix.
+ */
+ void CalcMixBufferOffset();
+
+ /**
+ * Splitter sort, traverse the splitter node graph and sort the sorted mixes from results.
+ *
+ * @param splitter_context - Splitter context for the sort.
+ * @return True if the sort was successful, othewise false.
+ */
+ bool TSortInfo(const SplitterContext& splitter_context);
+
+ /**
+ * Get the edge matrix used for the mix graph.
+ *
+ * @return The edge matrix used.
+ */
+ EdgeMatrix& GetEdgeMatrix();
+
+private:
+ /// Array of sorted mixes
+ std::span<MixInfo*> sorted_mix_infos{};
+ /// Array of mixes
+ std::span<MixInfo> mix_infos{};
+ /// Number of active mixes
+ s32 count{};
+ /// Array of effect process orderings
+ std::span<s32> effect_process_order_buffer{};
+ /// Number of effects in the process ordering buffer
+ u64 effect_count{};
+ /// Node states used in splitter sort
+ NodeStates node_states{};
+ /// Edge matrix for connected nodes used in splitter sort
+ EdgeMatrix edge_matrix{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_info.cpp b/src/audio_core/renderer/mix/mix_info.cpp
new file mode 100644
index 000000000..cc18e57ee
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_info.cpp
@@ -0,0 +1,120 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/effect/effect_context.h"
+#include "audio_core/renderer/mix/mix_info.h"
+#include "audio_core/renderer/nodes/edge_matrix.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior)
+ : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_},
+ long_size_pre_delay_supported{behavior.IsLongSizePreDelaySupported()} {
+ ClearEffectProcessingOrder();
+}
+
+void MixInfo::Cleanup() {
+ mix_id = UnusedMixId;
+ dst_mix_id = UnusedMixId;
+ dst_splitter_id = UnusedSplitterId;
+}
+
+void MixInfo::ClearEffectProcessingOrder() {
+ for (s32 i = 0; i < effect_count; i++) {
+ effect_order_buffer[i] = -1;
+ }
+}
+
+bool MixInfo::Update(EdgeMatrix& edge_matrix, const InParameter& in_params,
+ EffectContext& effect_context, SplitterContext& splitter_context,
+ const BehaviorInfo& behavior) {
+ volume = in_params.volume;
+ sample_rate = in_params.sample_rate;
+ buffer_count = static_cast<s16>(in_params.buffer_count);
+ in_use = in_params.in_use;
+ mix_id = in_params.mix_id;
+ node_id = in_params.node_id;
+ mix_volumes = in_params.mix_volumes;
+
+ bool sort_required{false};
+ if (behavior.IsSplitterSupported()) {
+ sort_required = UpdateConnection(edge_matrix, in_params, splitter_context);
+ } else {
+ if (dst_mix_id != in_params.dest_mix_id) {
+ dst_mix_id = in_params.dest_mix_id;
+ sort_required = true;
+ }
+ dst_splitter_id = UnusedSplitterId;
+ }
+
+ ClearEffectProcessingOrder();
+
+ // Check all effects, and set their order if they belong to this mix.
+ const auto count{effect_context.GetCount()};
+ for (u32 i = 0; i < count; i++) {
+ const auto& info{effect_context.GetInfo(i)};
+ if (mix_id == info.GetMixId()) {
+ const auto processing_order{info.GetProcessingOrder()};
+ if (processing_order > effect_count) {
+ break;
+ }
+ effect_order_buffer[processing_order] = i;
+ }
+ }
+
+ return sort_required;
+}
+
+bool MixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params,
+ SplitterContext& splitter_context) {
+ auto has_new_connection{false};
+ if (dst_splitter_id != UnusedSplitterId) {
+ auto& splitter_info{splitter_context.GetInfo(dst_splitter_id)};
+ has_new_connection = splitter_info.HasNewConnection();
+ }
+
+ // Check if this mix matches the input parameters.
+ // If everything is the same, don't bother updating.
+ if (dst_mix_id == in_params.dest_mix_id && dst_splitter_id == in_params.dest_splitter_id &&
+ !has_new_connection) {
+ return false;
+ }
+
+ // Reset the mix in the graph, as we're about to update it.
+ edge_matrix.RemoveEdges(mix_id);
+
+ if (in_params.dest_mix_id == UnusedMixId) {
+ if (in_params.dest_splitter_id != UnusedSplitterId) {
+ // If the splitter is used, connect this mix to each active destination.
+ auto& splitter_info{splitter_context.GetInfo(in_params.dest_splitter_id)};
+ auto const destination_count{splitter_info.GetDestinationCount()};
+
+ for (u32 i = 0; i < destination_count; i++) {
+ auto destination{
+ splitter_context.GetDesintationData(in_params.dest_splitter_id, i)};
+
+ if (destination) {
+ const auto destination_id{destination->GetMixId()};
+ if (destination_id != UnusedMixId) {
+ edge_matrix.Connect(mix_id, destination_id);
+ }
+ }
+ }
+ }
+ } else {
+ // If the splitter is not used, only connect this mix to its destination.
+ edge_matrix.Connect(mix_id, in_params.dest_mix_id);
+ }
+
+ dst_mix_id = in_params.dest_mix_id;
+ dst_splitter_id = in_params.dest_splitter_id;
+ return true;
+}
+
+bool MixInfo::HasAnyConnection() const {
+ return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_info.h b/src/audio_core/renderer/mix/mix_info.h
new file mode 100644
index 000000000..b5fa4c0c7
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_info.h
@@ -0,0 +1,124 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class EdgeMatrix;
+class SplitterContext;
+class EffectContext;
+class BehaviorInfo;
+
+/**
+ * A single mix, which may feed through other mixes in a chain until reaching the final output mix.
+ */
+class MixInfo {
+public:
+ struct InParameter {
+ /* 0x000 */ f32 volume;
+ /* 0x004 */ u32 sample_rate;
+ /* 0x008 */ u32 buffer_count;
+ /* 0x00C */ bool in_use;
+ /* 0x00D */ bool is_dirty;
+ /* 0x010 */ s32 mix_id;
+ /* 0x014 */ u32 effect_count;
+ /* 0x018 */ s32 node_id;
+ /* 0x01C */ char unk01C[0x8];
+ /* 0x024 */ std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes;
+ /* 0x924 */ s32 dest_mix_id;
+ /* 0x928 */ s32 dest_splitter_id;
+ /* 0x92C */ char unk92C[0x4];
+ };
+ static_assert(sizeof(InParameter) == 0x930, "MixInfo::InParameter has the wrong size!");
+
+ struct InDirtyParameter {
+ /* 0x00 */ u32 magic;
+ /* 0x04 */ s32 count;
+ /* 0x08 */ char unk08[0x18];
+ };
+ static_assert(sizeof(InDirtyParameter) == 0x20,
+ "MixInfo::InDirtyParameter has the wrong size!");
+
+ MixInfo(std::span<s32> effect_order_buffer, s32 effect_count, BehaviorInfo& behavior);
+
+ /**
+ * Clean up the mix, resetting it to a default state.
+ */
+ void Cleanup();
+
+ /**
+ * Clear the effect process order for all effects in this mix.
+ */
+ void ClearEffectProcessingOrder();
+
+ /**
+ * Update the mix according to the given parameters.
+ *
+ * @param edge_matrix - Updated with new splitter node connections, if supported.
+ * @param in_params - Input parameters.
+ * @param effect_context - Used to update the effect orderings.
+ * @param splitter_context - Used to update the mix graph if supported.
+ * @param behavior - Used for checking which features are supported.
+ * @return True if the mix was updated and a sort is required, otherwise false.
+ */
+ bool Update(EdgeMatrix& edge_matrix, const InParameter& in_params,
+ EffectContext& effect_context, SplitterContext& splitter_context,
+ const BehaviorInfo& behavior);
+
+ /**
+ * Update the mix's connection in the node graph according to the given parameters.
+ *
+ * @param edge_matrix - Updated with new splitter node connections, if supported.
+ * @param in_params - Input parameters.
+ * @param splitter_context - Used to update the mix graph if supported.
+ * @return True if the mix was updated and a sort is required, otherwise false.
+ */
+ bool UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params,
+ SplitterContext& splitter_context);
+
+ /**
+ * Check if this mix is connected to any other.
+ *
+ * @return True if the mix has a connection, otherwise false.
+ */
+ bool HasAnyConnection() const;
+
+ /// Volume of this mix
+ f32 volume{};
+ /// Sample rate of this mix
+ u32 sample_rate{};
+ /// Number of buffers in this mix
+ s16 buffer_count{};
+ /// Is this mix in use?
+ bool in_use{};
+ /// Is this mix enabled?
+ bool enabled{};
+ /// Id of this mix
+ s32 mix_id{UnusedMixId};
+ /// Node id of this mix
+ s32 node_id{};
+ /// Buffer offset for this mix
+ s16 buffer_offset{};
+ /// Distance to the final mix
+ s32 distance_from_final_mix{InvalidDistanceFromFinalMix};
+ /// Array of effect orderings of all effects in this mix
+ std::span<s32> effect_order_buffer;
+ /// Number of effects in this mix
+ const s32 effect_count;
+ /// Id for next mix in the chain
+ s32 dst_mix_id{UnusedMixId};
+ /// Mixing volumes for this mix used when this mix is chained with another
+ std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes{};
+ /// Id for next mix in the graph when splitter is used
+ s32 dst_splitter_id{UnusedSplitterId};
+ /// Is a longer pre-delay time supported for the reverb effect?
+ const bool long_size_pre_delay_supported;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/bit_array.h b/src/audio_core/renderer/nodes/bit_array.h
new file mode 100644
index 000000000..b0d53cd51
--- /dev/null
+++ b/src/audio_core/renderer/nodes/bit_array.h
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Represents an array of bits used for nodes and edges for the mixing graph.
+ */
+struct BitArray {
+ void reset() {
+ buffer.assign(buffer.size(), false);
+ }
+
+ /// Bits
+ std::vector<bool> buffer{};
+ /// Size of the buffer
+ u32 size{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/edge_matrix.cpp b/src/audio_core/renderer/nodes/edge_matrix.cpp
new file mode 100644
index 000000000..5573f33b9
--- /dev/null
+++ b/src/audio_core/renderer/nodes/edge_matrix.cpp
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/nodes/edge_matrix.h"
+
+namespace AudioCore::AudioRenderer {
+
+void EdgeMatrix::Initialize([[maybe_unused]] std::span<u8> buffer,
+ [[maybe_unused]] const u64 node_buffer_size, const u32 count_) {
+ count = count_;
+ edges.buffer.resize(count_ * count_);
+ edges.size = count_ * count_;
+ edges.reset();
+}
+
+bool EdgeMatrix::Connected(const u32 id, const u32 destination_id) const {
+ return edges.buffer[count * id + destination_id];
+}
+
+void EdgeMatrix::Connect(const u32 id, const u32 destination_id) {
+ edges.buffer[count * id + destination_id] = true;
+}
+
+void EdgeMatrix::Disconnect(const u32 id, const u32 destination_id) {
+ edges.buffer[count * id + destination_id] = false;
+}
+
+void EdgeMatrix::RemoveEdges(const u32 id) {
+ for (u32 dest = 0; dest < count; dest++) {
+ Disconnect(id, dest);
+ }
+}
+
+u32 EdgeMatrix::GetNodeCount() const {
+ return count;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/edge_matrix.h b/src/audio_core/renderer/nodes/edge_matrix.h
new file mode 100644
index 000000000..27a20e43e
--- /dev/null
+++ b/src/audio_core/renderer/nodes/edge_matrix.h
@@ -0,0 +1,82 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/nodes/bit_array.h"
+#include "common/alignment.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * An edge matrix, holding the connections for each node to every other node in the graph.
+ */
+class EdgeMatrix {
+public:
+ /**
+ * Calculate the size required for its workbuffer.
+ *
+ * @param count - The number of nodes in the graph.
+ * @return The required workbuffer size.
+ */
+ static u64 GetWorkBufferSize(u32 count) {
+ return Common::AlignUp(count * count, 0x40) / sizeof(u64);
+ }
+
+ /**
+ * Initialize this edge matrix.
+ *
+ * @param buffer - The workbuffer to use. Unused.
+ * @param node_buffer_size - The size of the workbuffer. Unused.
+ * @param count - The number of nodes in the graph.
+ */
+ void Initialize(std::span<u8> buffer, u64 node_buffer_size, u32 count);
+
+ /**
+ * Check if a node is connected to another.
+ *
+ * @param id - The node id to check.
+ * @param destination_id - Node id to check connection with.
+ */
+ bool Connected(u32 id, u32 destination_id) const;
+
+ /**
+ * Connect a node to another.
+ *
+ * @param id - The node id to connect.
+ * @param destination_id - Destination to connect it to.
+ */
+ void Connect(u32 id, u32 destination_id);
+
+ /**
+ * Disconnect a node from another.
+ *
+ * @param id - The node id to disconnect.
+ * @param destination_id - Destination to disconnect it from.
+ */
+ void Disconnect(u32 id, u32 destination_id);
+
+ /**
+ * Remove all connections for a given node.
+ *
+ * @param id - The node id to disconnect.
+ */
+ void RemoveEdges(u32 id);
+
+ /**
+ * Get the number of nodes in the graph.
+ *
+ * @return Number of nodes.
+ */
+ u32 GetNodeCount() const;
+
+private:
+ /// Edges for the current graph
+ BitArray edges;
+ /// Number of nodes (not edges) in the graph
+ u32 count;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/node_states.cpp b/src/audio_core/renderer/nodes/node_states.cpp
new file mode 100644
index 000000000..1821a51e6
--- /dev/null
+++ b/src/audio_core/renderer/nodes/node_states.cpp
@@ -0,0 +1,141 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/nodes/node_states.h"
+#include "common/logging/log.h"
+
+namespace AudioCore::AudioRenderer {
+
+void NodeStates::Initialize(std::span<u8> buffer_, [[maybe_unused]] const u64 node_buffer_size,
+ const u32 count) {
+ u64 num_blocks{Common::AlignUp(count, 0x40) / sizeof(u64)};
+ u64 offset{0};
+
+ node_count = count;
+
+ nodes_found.buffer.resize(count);
+ nodes_found.size = count;
+ nodes_found.reset();
+
+ offset += num_blocks;
+
+ nodes_complete.buffer.resize(count);
+ nodes_complete.size = count;
+ nodes_complete.reset();
+
+ offset += num_blocks;
+
+ results = {reinterpret_cast<u32*>(&buffer_[offset]), count};
+
+ offset += count * sizeof(u32);
+
+ stack.stack = {reinterpret_cast<u32*>(&buffer_[offset]), count * count};
+ stack.size = count * count;
+ stack.unk_10 = count * count;
+
+ offset += count * count * sizeof(u32);
+}
+
+bool NodeStates::Tsort(const EdgeMatrix& edge_matrix) {
+ return DepthFirstSearch(edge_matrix, stack);
+}
+
+bool NodeStates::DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack_) {
+ ResetState();
+
+ for (u32 node_id = 0; node_id < node_count; node_id++) {
+ if (GetState(node_id) == SearchState::Unknown) {
+ stack_.push(node_id);
+ }
+
+ while (stack_.Count() > 0) {
+ auto current_node{stack_.top()};
+ switch (GetState(current_node)) {
+ case SearchState::Unknown:
+ SetState(current_node, SearchState::Found);
+ break;
+ case SearchState::Found:
+ SetState(current_node, SearchState::Complete);
+ PushTsortResult(current_node);
+ stack_.pop();
+ continue;
+ case SearchState::Complete:
+ stack_.pop();
+ continue;
+ }
+
+ const auto edge_count{edge_matrix.GetNodeCount()};
+ for (u32 edge_id = 0; edge_id < edge_count; edge_id++) {
+ if (!edge_matrix.Connected(current_node, edge_id)) {
+ continue;
+ }
+
+ switch (GetState(edge_id)) {
+ case SearchState::Unknown:
+ stack_.push(edge_id);
+ break;
+ case SearchState::Found:
+ LOG_ERROR(Service_Audio,
+ "Cycle detected in the node graph, graph is not a DAG! "
+ "Bailing to avoid an infinite loop");
+ ResetState();
+ return false;
+ case SearchState::Complete:
+ break;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+NodeStates::SearchState NodeStates::GetState(const u32 id) const {
+ if (nodes_found.buffer[id]) {
+ return SearchState::Found;
+ } else if (nodes_complete.buffer[id]) {
+ return SearchState::Complete;
+ }
+ return SearchState::Unknown;
+}
+
+void NodeStates::PushTsortResult(const u32 id) {
+ results[result_pos++] = id;
+}
+
+void NodeStates::SetState(const u32 id, const SearchState state) {
+ switch (state) {
+ case SearchState::Complete:
+ nodes_found.buffer[id] = false;
+ nodes_complete.buffer[id] = true;
+ break;
+ case SearchState::Found:
+ nodes_found.buffer[id] = true;
+ nodes_complete.buffer[id] = false;
+ break;
+ case SearchState::Unknown:
+ nodes_found.buffer[id] = false;
+ nodes_complete.buffer[id] = false;
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Unknown node SearchState {}", static_cast<u32>(state));
+ break;
+ }
+}
+
+void NodeStates::ResetState() {
+ nodes_found.reset();
+ nodes_complete.reset();
+ std::fill(results.begin(), results.end(), -1);
+ result_pos = 0;
+}
+
+u32 NodeStates::GetNodeCount() const {
+ return node_count;
+}
+
+std::vector<s32> NodeStates::GetSortedResuls() const {
+ return {results.rbegin(), results.rbegin() + result_pos};
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h
new file mode 100644
index 000000000..a1e0958a2
--- /dev/null
+++ b/src/audio_core/renderer/nodes/node_states.h
@@ -0,0 +1,195 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+#include <vector>
+
+#include "audio_core/renderer/nodes/edge_matrix.h"
+#include "common/alignment.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Graph utility functions for sorting and getting results from the DAG.
+ */
+class NodeStates {
+ /**
+ * State of a node in the depth first search.
+ */
+ enum class SearchState {
+ Unknown,
+ Found,
+ Complete,
+ };
+
+ /**
+ * Stack used for a depth first search.
+ */
+ struct Stack {
+ /**
+ * Calculate the workbuffer size required for this stack.
+ *
+ * @param count - Maximum number of nodes for the stack.
+ * @return Required buffer size.
+ */
+ static u32 CalcBufferSize(u32 count) {
+ return count * sizeof(u32);
+ }
+
+ /**
+ * Reset the stack back to default.
+ *
+ * @param buffer_ - The new buffer to use.
+ * @param size_ - The size of the new buffer.
+ */
+ void Reset(u32* buffer_, u32 size_) {
+ stack = {buffer_, size_};
+ size = size_;
+ pos = 0;
+ unk_10 = size_;
+ }
+
+ /**
+ * Get the current stack position.
+ *
+ * @return The current stack position.
+ */
+ u32 Count() {
+ return pos;
+ }
+
+ /**
+ * Push a new node to the stack.
+ *
+ * @param data - The node to push.
+ */
+ void push(u32 data) {
+ stack[pos++] = data;
+ }
+
+ /**
+ * Pop a node from the stack.
+ *
+ * @return The node on the top of the stack.
+ */
+ u32 pop() {
+ return stack[--pos];
+ }
+
+ /**
+ * Get the top of the stack without popping.
+ *
+ * @return The node on the top of the stack.
+ */
+ u32 top() {
+ return stack[pos - 1];
+ }
+
+ /// Buffer for the stack
+ std::span<u32> stack{};
+ /// Size of the stack buffer
+ u32 size{};
+ /// Current stack position
+ u32 pos{};
+ /// Unknown
+ u32 unk_10{};
+ };
+
+public:
+ /**
+ * Calculate the workbuffer size required for the node states.
+ *
+ * @param count - The number of nodes.
+ * @return The required workbuffer size.
+ */
+ static u64 GetWorkBufferSize(u32 count) {
+ return (Common::AlignUp(count, 0x40) / sizeof(u64)) * 2 + count * sizeof(BitArray) +
+ count * Stack::CalcBufferSize(count);
+ }
+
+ /**
+ * Initialize the node states.
+ *
+ * @param buffer - The workbuffer to use. Unused.
+ * @param node_buffer_size - The size of the workbuffer. Unused.
+ * @param count - The number of nodes in the graph.
+ */
+ void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count);
+
+ /**
+ * Sort the graph. Only calls DepthFirstSearch.
+ *
+ * @param edge_matrix - The edge matrix used to hold the connections between nodes.
+ * @return True if the sort was successful, otherwise false.
+ */
+ bool Tsort(const EdgeMatrix& edge_matrix);
+
+ /**
+ * Sort the graph via depth first search.
+ *
+ * @param edge_matrix - The edge matrix used to hold the connections between nodes.
+ * @param stack - The stack used for pushing and popping nodes.
+ * @return True if the sort was successful, otherwise false.
+ */
+ bool DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack);
+
+ /**
+ * Get the search state of a given node.
+ *
+ * @param id - The node id to check.
+ * @return The node's search state. See SearchState
+ */
+ SearchState GetState(u32 id) const;
+
+ /**
+ * Push a node id to the results buffer when found in the DFS.
+ *
+ * @param id - The node id to push.
+ */
+ void PushTsortResult(u32 id);
+
+ /**
+ * Set the state of a node.
+ *
+ * @param id - The node id to alter.
+ * @param state - The new search state.
+ */
+ void SetState(u32 id, SearchState state);
+
+ /**
+ * Reset the nodes found, complete and the results.
+ */
+ void ResetState();
+
+ /**
+ * Get the number of nodes in the graph.
+ *
+ * @return The number of nodes.
+ */
+ u32 GetNodeCount() const;
+
+ /**
+ * Get the sorted results from the DFS.
+ *
+ * @return Vector of nodes in reverse order.
+ */
+ std::vector<s32> GetSortedResuls() const;
+
+private:
+ /// Number of nodes in the graph
+ u32 node_count{};
+ /// Position in results buffer
+ u32 result_pos{};
+ /// List of nodes found
+ BitArray nodes_found{};
+ /// List of nodes completed
+ BitArray nodes_complete{};
+ /// List of results from the depth first search
+ std::span<u32> results{};
+ /// Stack used during the depth first search
+ Stack stack{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/detail_aspect.cpp b/src/audio_core/renderer/performance/detail_aspect.cpp
new file mode 100644
index 000000000..f6405937f
--- /dev/null
+++ b/src/audio_core/renderer/performance/detail_aspect.cpp
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_generator.h"
+#include "audio_core/renderer/performance/detail_aspect.h"
+
+namespace AudioCore::AudioRenderer {
+
+DetailAspect::DetailAspect(CommandGenerator& command_generator_,
+ const PerformanceEntryType entry_type, const s32 node_id_,
+ const PerformanceDetailType detail_type)
+ : command_generator{command_generator_}, node_id{node_id_} {
+ auto perf_manager{command_generator.GetPerformanceManager()};
+ if (perf_manager != nullptr && perf_manager->IsInitialized() &&
+ perf_manager->IsDetailTarget(node_id) &&
+ perf_manager->GetNextEntry(performance_entry_address, detail_type, entry_type, node_id)) {
+ command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start,
+ performance_entry_address);
+
+ initialized = true;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/detail_aspect.h b/src/audio_core/renderer/performance/detail_aspect.h
new file mode 100644
index 000000000..ee4ac2f76
--- /dev/null
+++ b/src/audio_core/renderer/performance/detail_aspect.h
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/performance/performance_entry_addresses.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class CommandGenerator;
+
+/**
+ * Holds detailed information about performance metrics, filled in by the AudioRenderer during
+ * Performance commands.
+ */
+class DetailAspect {
+public:
+ DetailAspect() = default;
+ DetailAspect(CommandGenerator& command_generator, PerformanceEntryType entry_type, s32 node_id,
+ PerformanceDetailType detail_type);
+
+ /// Command generator the command will be generated into
+ CommandGenerator& command_generator;
+ /// Addresses to be filled by the AudioRenderer
+ PerformanceEntryAddresses performance_entry_address{};
+ /// Is this detail aspect initialized?
+ bool initialized{};
+ /// Node id of this aspect
+ s32 node_id;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/entry_aspect.cpp b/src/audio_core/renderer/performance/entry_aspect.cpp
new file mode 100644
index 000000000..dd4165803
--- /dev/null
+++ b/src/audio_core/renderer/performance/entry_aspect.cpp
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_generator.h"
+#include "audio_core/renderer/performance/entry_aspect.h"
+
+namespace AudioCore::AudioRenderer {
+
+EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type,
+ const s32 node_id_)
+ : command_generator{command_generator_}, node_id{node_id_} {
+ auto perf_manager{command_generator.GetPerformanceManager()};
+ if (perf_manager != nullptr && perf_manager->IsInitialized() &&
+ perf_manager->GetNextEntry(performance_entry_address, type, node_id)) {
+ command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start,
+ performance_entry_address);
+
+ initialized = true;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/entry_aspect.h b/src/audio_core/renderer/performance/entry_aspect.h
new file mode 100644
index 000000000..01c1eb3f1
--- /dev/null
+++ b/src/audio_core/renderer/performance/entry_aspect.h
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/performance/performance_entry_addresses.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class CommandGenerator;
+
+/**
+ * Holds entry information about performance metrics, filled in by the AudioRenderer during
+ * Performance commands.
+ */
+class EntryAspect {
+public:
+ EntryAspect() = default;
+ EntryAspect(CommandGenerator& command_generator, PerformanceEntryType type, s32 node_id);
+
+ /// Command generator the command will be generated into
+ CommandGenerator& command_generator;
+ /// Addresses to be filled by the AudioRenderer
+ PerformanceEntryAddresses performance_entry_address{};
+ /// Is this detail aspect initialized?
+ bool initialized{};
+ /// Node id of this aspect
+ s32 node_id;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_detail.h b/src/audio_core/renderer/performance/performance_detail.h
new file mode 100644
index 000000000..3a4897e60
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_detail.h
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/performance/performance_entry.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+enum class PerformanceDetailType : u8 {
+ Invalid,
+ Unk1,
+ Unk2,
+ Unk3,
+ Unk4,
+ Unk5,
+ Unk6,
+ Unk7,
+ Unk8,
+ Unk9,
+ Unk10,
+ Unk11,
+ Unk12,
+ Unk13,
+};
+
+struct PerformanceDetailVersion1 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceDetailType detail_type;
+ /* 0x0D */ PerformanceEntryType entry_type;
+};
+static_assert(sizeof(PerformanceDetailVersion1) == 0x10,
+ "PerformanceDetailVersion1 has the worng size!");
+
+struct PerformanceDetailVersion2 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceDetailType detail_type;
+ /* 0x0D */ PerformanceEntryType entry_type;
+ /* 0x10 */ u32 unk_10;
+ /* 0x14 */ char unk14[0x4];
+};
+static_assert(sizeof(PerformanceDetailVersion2) == 0x18,
+ "PerformanceDetailVersion2 has the worng size!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_entry.h b/src/audio_core/renderer/performance/performance_entry.h
new file mode 100644
index 000000000..d1b21406b
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_entry.h
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+enum class PerformanceEntryType : u8 {
+ Invalid,
+ Voice,
+ SubMix,
+ FinalMix,
+ Sink,
+};
+
+struct PerformanceEntryVersion1 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceEntryType entry_type;
+};
+static_assert(sizeof(PerformanceEntryVersion1) == 0x10,
+ "PerformanceEntryVersion1 has the worng size!");
+
+struct PerformanceEntryVersion2 {
+ /* 0x00 */ u32 node_id;
+ /* 0x04 */ u32 start_time;
+ /* 0x08 */ u32 processed_time;
+ /* 0x0C */ PerformanceEntryType entry_type;
+ /* 0x0D */ char unk0D[0xB];
+};
+static_assert(sizeof(PerformanceEntryVersion2) == 0x18,
+ "PerformanceEntryVersion2 has the worng size!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_entry_addresses.h b/src/audio_core/renderer/performance/performance_entry_addresses.h
new file mode 100644
index 000000000..e381d765c
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_entry_addresses.h
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/common/common.h"
+
+namespace AudioCore::AudioRenderer {
+
+struct PerformanceEntryAddresses {
+ CpuAddr translated_address;
+ CpuAddr entry_start_time_offset;
+ CpuAddr header_entry_count_offset;
+ CpuAddr entry_processed_time_offset;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_frame_header.h b/src/audio_core/renderer/performance/performance_frame_header.h
new file mode 100644
index 000000000..707cc0afb
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_frame_header.h
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+
+struct PerformanceFrameHeaderVersion1 {
+ /* 0x00 */ u32 magic; // "PERF"
+ /* 0x04 */ u32 entry_count;
+ /* 0x08 */ u32 detail_count;
+ /* 0x0C */ u32 next_offset;
+ /* 0x10 */ u32 total_processing_time;
+ /* 0x14 */ u32 frame_index;
+};
+static_assert(sizeof(PerformanceFrameHeaderVersion1) == 0x18,
+ "PerformanceFrameHeaderVersion1 has the worng size!");
+
+struct PerformanceFrameHeaderVersion2 {
+ /* 0x00 */ u32 magic; // "PERF"
+ /* 0x04 */ u32 entry_count;
+ /* 0x08 */ u32 detail_count;
+ /* 0x0C */ u32 next_offset;
+ /* 0x10 */ u32 total_processing_time;
+ /* 0x14 */ u32 voices_dropped;
+ /* 0x18 */ u64 start_time;
+ /* 0x20 */ u32 frame_index;
+ /* 0x24 */ bool render_time_exceeded;
+ /* 0x25 */ char unk25[0xB];
+};
+static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30,
+ "PerformanceFrameHeaderVersion2 has the worng size!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_manager.cpp b/src/audio_core/renderer/performance/performance_manager.cpp
new file mode 100644
index 000000000..fd5873e1e
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_manager.cpp
@@ -0,0 +1,645 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "common/common_funcs.h"
+
+namespace AudioCore::AudioRenderer {
+
+void PerformanceManager::CreateImpl(const size_t version) {
+ switch (version) {
+ case 1:
+ impl = std::make_unique<
+ PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>>();
+ break;
+ case 2:
+ impl = std::make_unique<
+ PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2, PerformanceDetailVersion2>>();
+ break;
+ default:
+ LOG_WARNING(Service_Audio, "Invalid PerformanceMetricsDataFormat {}, creating version 1",
+ static_cast<u32>(version));
+ impl = std::make_unique<
+ PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>>();
+ }
+}
+
+void PerformanceManager::Initialize(std::span<u8> workbuffer, const u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) {
+ CreateImpl(behavior.GetPerformanceMetricsDataFormat());
+ impl->Initialize(workbuffer, workbuffer_size, params, behavior, memory_pool);
+}
+
+bool PerformanceManager::IsInitialized() const {
+ if (impl) {
+ return impl->IsInitialized();
+ }
+ return false;
+}
+
+u32 PerformanceManager::CopyHistories(u8* out_buffer, u64 out_size) {
+ if (impl) {
+ return impl->CopyHistories(out_buffer, out_size);
+ }
+ return 0;
+}
+
+bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ const PerformanceSysDetailType sys_detail_type,
+ const s32 node_id) {
+ if (impl) {
+ return impl->GetNextEntry(addresses, unk, sys_detail_type, node_id);
+ }
+ return false;
+}
+
+bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceEntryType entry_type, const s32 node_id) {
+ if (impl) {
+ return impl->GetNextEntry(addresses, entry_type, node_id);
+ }
+ return false;
+}
+
+bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceDetailType detail_type,
+ const PerformanceEntryType entry_type, const s32 node_id) {
+ if (impl) {
+ return impl->GetNextEntry(addresses, detail_type, entry_type, node_id);
+ }
+ return false;
+}
+
+void PerformanceManager::TapFrame(const bool dsp_behind, const u32 voices_dropped,
+ const u64 rendering_start_tick) {
+ if (impl) {
+ impl->TapFrame(dsp_behind, voices_dropped, rendering_start_tick);
+ }
+}
+
+bool PerformanceManager::IsDetailTarget(const u32 target_node_id) const {
+ if (impl) {
+ return impl->IsDetailTarget(target_node_id);
+ }
+ return false;
+}
+
+void PerformanceManager::SetDetailTarget(const u32 target_node_id) {
+ if (impl) {
+ impl->SetDetailTarget(target_node_id);
+ }
+}
+
+template <>
+void PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) {
+ workbuffer = workbuffer_;
+ entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
+ max_detail_count = MaxDetailEntries;
+ frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
+ const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
+ max_frames = frame_count - 1;
+ translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);
+
+ // The first frame is the "current" frame we're writing to.
+ auto buffer_offset{workbuffer.data()};
+ frame_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
+ entry_buffer = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset), entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
+ detail_buffer = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset), max_detail_count};
+
+ // After the current, is a ringbuffer of history frames, the current frame will be copied here
+ // before a new frame is written.
+ frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);
+
+ // If there's room for any history frames.
+ if (frame_count >= 2) {
+ buffer_offset = frame_history.data();
+ frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
+ frame_history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset),
+ entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
+ frame_history_details = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset),
+ max_detail_count};
+ } else {
+ frame_history_header = {};
+ frame_history_entries = {};
+ frame_history_details = {};
+ }
+
+ target_node_id = 0;
+ version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+ output_frame_index = 0;
+ last_output_frame_index = 0;
+ is_initialized = true;
+}
+
+template <>
+bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>::IsInitialized()
+ const {
+ return is_initialized;
+}
+
+template <>
+u32 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::CopyHistories(u8* out_buffer, u64 out_size) {
+ if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
+ return 0;
+ }
+
+ // Are there any new frames waiting to be output?
+ if (last_output_frame_index == output_frame_index) {
+ return 0;
+ }
+
+ PerformanceFrameHeaderVersion1* out_header{nullptr};
+ u32 out_history_size{0};
+
+ while (last_output_frame_index != output_frame_index) {
+ PerformanceFrameHeaderVersion1* history_header{nullptr};
+ std::span<PerformanceEntryVersion1> history_entries{};
+ std::span<PerformanceDetailVersion1> history_details{};
+
+ if (max_frames > 0) {
+ auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
+ history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(frame_offset);
+ frame_offset += sizeof(PerformanceFrameHeaderVersion1);
+ history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(frame_offset),
+ history_header->entry_count};
+ frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion1);
+ history_details = {reinterpret_cast<PerformanceDetailVersion1*>(frame_offset),
+ history_header->detail_count};
+ } else {
+ // Original code does not break here, but will crash when trying to dereference the
+ // header in the next if, so let's just skip this frame and continue...
+ // Hopefully this will not happen.
+ LOG_WARNING(Service_Audio,
+ "max_frames should not be 0! Skipping frame to avoid a crash");
+ last_output_frame_index++;
+ continue;
+ }
+
+ if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion1) +
+ history_header->detail_count * sizeof(PerformanceDetailVersion1) +
+ 2 * sizeof(PerformanceFrameHeaderVersion1)) {
+ break;
+ }
+
+ u32 out_offset{sizeof(PerformanceFrameHeaderVersion1)};
+ auto out_entries{std::span<PerformanceEntryVersion1>(
+ reinterpret_cast<PerformanceEntryVersion1*>(out_buffer + out_offset),
+ history_header->entry_count)};
+ u32 out_entry_count{0};
+ u32 total_processing_time{0};
+ for (auto& history_entry : history_entries) {
+ if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
+ out_entries[out_entry_count++] = history_entry;
+ total_processing_time += history_entry.processed_time;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion1));
+ auto out_details{std::span<PerformanceDetailVersion1>(
+ reinterpret_cast<PerformanceDetailVersion1*>(out_buffer + out_offset),
+ history_header->detail_count)};
+ u32 out_detail_count{0};
+ for (auto& history_detail : history_details) {
+ if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
+ out_details[out_detail_count++] = history_detail;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion1));
+ out_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(out_buffer);
+ out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
+ out_header->entry_count = out_entry_count;
+ out_header->detail_count = out_detail_count;
+ out_header->next_offset = out_offset;
+ out_header->total_processing_time = total_processing_time;
+ out_header->frame_index = history_header->frame_index;
+
+ out_history_size += out_offset;
+
+ out_buffer += out_offset;
+ out_size -= out_offset;
+ last_output_frame_index = (last_output_frame_index + 1) % max_frames;
+ }
+
+ // We're out of frames to output, so if there's enough left in the output buffer for another
+ // header, and we output at least 1 frame, set the next header to null.
+ if (out_size > sizeof(PerformanceFrameHeaderVersion1) && out_header != nullptr) {
+ std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion1));
+ }
+
+ return out_history_size;
+}
+
+template <>
+bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1, PerformanceDetailVersion1>::
+ GetNextEntry([[maybe_unused]] PerformanceEntryAddresses& addresses, [[maybe_unused]] u32** unk,
+ [[maybe_unused]] PerformanceSysDetailType sys_detail_type,
+ [[maybe_unused]] s32 node_id) {
+ return false;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized) {
+ return false;
+ }
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion1, entry_count);
+
+ auto entry{&entry_buffer[entry_count++]};
+ addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion1, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion1, processed_time);
+
+ std::memset(entry, 0, sizeof(PerformanceEntryVersion1));
+ entry->node_id = node_id;
+ entry->entry_type = entry_type;
+ return true;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceDetailType detail_type,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized || detail_count > MaxDetailEntries) {
+ return false;
+ }
+
+ auto detail{&detail_buffer[detail_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion1, detail_count);
+ addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion1, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion1, processed_time);
+
+ std::memset(detail, 0, sizeof(PerformanceDetailVersion1));
+ detail->node_id = node_id;
+ detail->entry_type = entry_type;
+ detail->detail_type = detail_type;
+ return true;
+}
+
+template <>
+void PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::TapFrame([[maybe_unused]] bool dsp_behind,
+ [[maybe_unused]] u32 voices_dropped,
+ [[maybe_unused]] u64 rendering_start_tick) {
+ if (!is_initialized) {
+ return;
+ }
+
+ if (max_frames > 0) {
+ if (!frame_history.empty() && !workbuffer.empty()) {
+ auto history_frame = reinterpret_cast<PerformanceFrameHeaderVersion1*>(
+ &frame_history[output_frame_index * frame_size]);
+ std::memcpy(history_frame, workbuffer.data(), frame_size);
+ history_frame->frame_index = history_frame_index++;
+ }
+ output_frame_index = (output_frame_index + 1) % max_frames;
+ }
+
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::IsDetailTarget(const u32 target_node_id_) const {
+ return target_node_id == target_node_id_;
+}
+
+template <>
+void PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
+ PerformanceEntryVersion1,
+ PerformanceDetailVersion1>::SetDetailTarget(const u32 target_node_id_) {
+ target_node_id = target_node_id_;
+}
+
+template <>
+void PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) {
+ workbuffer = workbuffer_;
+ entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
+ max_detail_count = MaxDetailEntries;
+ frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
+ const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
+ max_frames = frame_count - 1;
+ translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);
+
+ // The first frame is the "current" frame we're writing to.
+ auto buffer_offset{workbuffer.data()};
+ frame_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
+ entry_buffer = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset), entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
+ detail_buffer = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset), max_detail_count};
+
+ // After the current, is a ringbuffer of history frames, the current frame will be copied here
+ // before a new frame is written.
+ frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);
+
+ // If there's room for any history frames.
+ if (frame_count >= 2) {
+ buffer_offset = frame_history.data();
+ frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
+ buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
+ frame_history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset),
+ entries_per_frame};
+ buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
+ frame_history_details = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset),
+ max_detail_count};
+ } else {
+ frame_history_header = {};
+ frame_history_entries = {};
+ frame_history_details = {};
+ }
+
+ target_node_id = 0;
+ version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+ output_frame_index = 0;
+ last_output_frame_index = 0;
+ is_initialized = true;
+}
+
+template <>
+bool PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2, PerformanceDetailVersion2>::IsInitialized()
+ const {
+ return is_initialized;
+}
+
+template <>
+u32 PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::CopyHistories(u8* out_buffer, u64 out_size) {
+ if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
+ return 0;
+ }
+
+ // Are there any new frames waiting to be output?
+ if (last_output_frame_index == output_frame_index) {
+ return 0;
+ }
+
+ PerformanceFrameHeaderVersion2* out_header{nullptr};
+ u32 out_history_size{0};
+
+ while (last_output_frame_index != output_frame_index) {
+ PerformanceFrameHeaderVersion2* history_header{nullptr};
+ std::span<PerformanceEntryVersion2> history_entries{};
+ std::span<PerformanceDetailVersion2> history_details{};
+
+ if (max_frames > 0) {
+ auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
+ history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(frame_offset);
+ frame_offset += sizeof(PerformanceFrameHeaderVersion2);
+ history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(frame_offset),
+ history_header->entry_count};
+ frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion2);
+ history_details = {reinterpret_cast<PerformanceDetailVersion2*>(frame_offset),
+ history_header->detail_count};
+ } else {
+ // Original code does not break here, but will crash when trying to dereference the
+ // header in the next if, so let's just skip this frame and continue...
+ // Hopefully this will not happen.
+ LOG_WARNING(Service_Audio,
+ "max_frames should not be 0! Skipping frame to avoid a crash");
+ last_output_frame_index++;
+ continue;
+ }
+
+ if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion2) +
+ history_header->detail_count * sizeof(PerformanceDetailVersion2) +
+ 2 * sizeof(PerformanceFrameHeaderVersion2)) {
+ break;
+ }
+
+ u32 out_offset{sizeof(PerformanceFrameHeaderVersion2)};
+ auto out_entries{std::span<PerformanceEntryVersion2>(
+ reinterpret_cast<PerformanceEntryVersion2*>(out_buffer + out_offset),
+ history_header->entry_count)};
+ u32 out_entry_count{0};
+ u32 total_processing_time{0};
+ for (auto& history_entry : history_entries) {
+ if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
+ out_entries[out_entry_count++] = history_entry;
+ total_processing_time += history_entry.processed_time;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion2));
+ auto out_details{std::span<PerformanceDetailVersion2>(
+ reinterpret_cast<PerformanceDetailVersion2*>(out_buffer + out_offset),
+ history_header->detail_count)};
+ u32 out_detail_count{0};
+ for (auto& history_detail : history_details) {
+ if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
+ out_details[out_detail_count++] = history_detail;
+ }
+ }
+
+ out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion2));
+ out_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(out_buffer);
+ out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
+ out_header->entry_count = out_entry_count;
+ out_header->detail_count = out_detail_count;
+ out_header->next_offset = out_offset;
+ out_header->total_processing_time = total_processing_time;
+ out_header->voices_dropped = history_header->voices_dropped;
+ out_header->start_time = history_header->start_time;
+ out_header->frame_index = history_header->frame_index;
+ out_header->render_time_exceeded = history_header->render_time_exceeded;
+
+ out_history_size += out_offset;
+
+ out_buffer += out_offset;
+ out_size -= out_offset;
+ last_output_frame_index = (last_output_frame_index + 1) % max_frames;
+ }
+
+ // We're out of frames to output, so if there's enough left in the output buffer for another
+ // header, and we output at least 1 frame, set the next header to null.
+ if (out_size > sizeof(PerformanceFrameHeaderVersion2) && out_header != nullptr) {
+ std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion2));
+ }
+
+ return out_history_size;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ const PerformanceSysDetailType sys_detail_type,
+ const s32 node_id) {
+ if (!is_initialized || detail_count > MaxDetailEntries) {
+ return false;
+ }
+
+ auto detail{&detail_buffer[detail_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion2, detail_count);
+ addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, processed_time);
+
+ std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
+ detail->node_id = node_id;
+ detail->detail_type = static_cast<PerformanceDetailType>(sys_detail_type);
+
+ if (unk) {
+ *unk = &detail->unk_10;
+ }
+ return true;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized) {
+ return false;
+ }
+
+ auto entry{&entry_buffer[entry_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion2, entry_count);
+ addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion2, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceEntryVersion2, processed_time);
+
+ std::memset(entry, 0, sizeof(PerformanceEntryVersion2));
+ entry->node_id = node_id;
+ entry->entry_type = entry_type;
+ return true;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
+ const PerformanceDetailType detail_type,
+ const PerformanceEntryType entry_type,
+ const s32 node_id) {
+ if (!is_initialized || detail_count > MaxDetailEntries) {
+ return false;
+ }
+
+ auto detail{&detail_buffer[detail_count++]};
+
+ addresses.translated_address = translated_buffer;
+ addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceFrameHeaderVersion2, detail_count);
+ addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, start_time);
+ addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
+ offsetof(PerformanceDetailVersion2, processed_time);
+
+ std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
+ detail->node_id = node_id;
+ detail->entry_type = entry_type;
+ detail->detail_type = detail_type;
+ return true;
+}
+
+template <>
+void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::TapFrame(const bool dsp_behind,
+ const u32 voices_dropped,
+ const u64 rendering_start_tick) {
+ if (!is_initialized) {
+ return;
+ }
+
+ if (max_frames > 0) {
+ if (!frame_history.empty() && !workbuffer.empty()) {
+ auto history_frame{reinterpret_cast<PerformanceFrameHeaderVersion2*>(
+ &frame_history[output_frame_index * frame_size])};
+ std::memcpy(history_frame, workbuffer.data(), frame_size);
+ history_frame->render_time_exceeded = dsp_behind;
+ history_frame->voices_dropped = voices_dropped;
+ history_frame->start_time = rendering_start_tick;
+ history_frame->frame_index = history_frame_index++;
+ }
+ output_frame_index = (output_frame_index + 1) % max_frames;
+ }
+
+ entry_count = 0;
+ detail_count = 0;
+ frame_header->entry_count = 0;
+ frame_header->detail_count = 0;
+}
+
+template <>
+bool PerformanceManagerImpl<
+ PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::IsDetailTarget(const u32 target_node_id_) const {
+ return target_node_id == target_node_id_;
+}
+
+template <>
+void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
+ PerformanceEntryVersion2,
+ PerformanceDetailVersion2>::SetDetailTarget(const u32 target_node_id_) {
+ target_node_id = target_node_id_;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h
new file mode 100644
index 000000000..b82176bef
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_manager.h
@@ -0,0 +1,273 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <chrono>
+#include <memory>
+#include <span>
+
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/renderer/performance/performance_detail.h"
+#include "audio_core/renderer/performance/performance_entry.h"
+#include "audio_core/renderer/performance/performance_entry_addresses.h"
+#include "audio_core/renderer/performance/performance_frame_header.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class BehaviorInfo;
+class MemoryPoolInfo;
+
+enum class PerformanceVersion {
+ Version1,
+ Version2,
+};
+
+enum class PerformanceSysDetailType {
+ PcmInt16 = 15,
+ PcmFloat = 16,
+ Adpcm = 17,
+ LightLimiter = 37,
+};
+
+enum class PerformanceState {
+ Invalid,
+ Start,
+ Stop,
+};
+
+/**
+ * Manages performance information.
+ *
+ * The performance buffer is split into frames, each comprised of:
+ * Frame header - Information about the number of entries/details and some others
+ * Entries - Created when starting to generate types of commands, such as voice
+ * commands, mix commands, sink commands etc. Details - Created for specific commands
+ * within each group. Up to MaxDetailEntries per frame.
+ *
+ * A current frame is written to by the AudioRenderer, and before it processes the next command
+ * list, the current frame is copied to a ringbuffer of history frames. These frames are then
+ * output back to the game if it supplies a performance buffer to RequestUpdate.
+ *
+ * Two versions currently exist, version 2 adds a few extra fields to the header, and a new
+ * SysDetail type which is seemingly unused.
+ */
+class PerformanceManager {
+public:
+ static constexpr size_t MaxDetailEntries = 100;
+
+ struct InParameter {
+ /* 0x00 */ s32 target_node_id;
+ /* 0x04 */ char unk04[0xC];
+ };
+ static_assert(sizeof(InParameter) == 0x10,
+ "PerformanceManager::InParameter has the wrong size!");
+
+ struct OutStatus {
+ /* 0x00 */ s32 history_size;
+ /* 0x04 */ char unk04[0xC];
+ };
+ static_assert(sizeof(OutStatus) == 0x10, "PerformanceManager::OutStatus has the wrong size!");
+
+ /**
+ * Calculate the required size for the performance workbuffer.
+ *
+ * @param behavior - Check which version is supported.
+ * @param params - Input parameters.
+ * @return Required workbuffer size.
+ */
+ static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame(
+ const BehaviorInfo& behavior, const AudioRendererParameterInternal& params) {
+ u64 entry_count{params.voices + params.effects + params.sub_mixes + params.sinks + 1};
+ switch (behavior.GetPerformanceMetricsDataFormat()) {
+ case 1:
+ return sizeof(PerformanceFrameHeaderVersion1) +
+ PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) +
+ entry_count * sizeof(PerformanceEntryVersion1);
+ case 2:
+ return sizeof(PerformanceFrameHeaderVersion2) +
+ PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion2) +
+ entry_count * sizeof(PerformanceEntryVersion2);
+ }
+
+ LOG_WARNING(Service_Audio, "Invalid PerformanceMetrics version, assuming version 1");
+ return sizeof(PerformanceFrameHeaderVersion1) +
+ PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) +
+ entry_count * sizeof(PerformanceEntryVersion1);
+ }
+
+ virtual ~PerformanceManager() = default;
+
+ /**
+ * Initialize the performance manager.
+ *
+ * @param workbuffer - Workbuffer to use for performance frames.
+ * @param workbuffer_size - Size of the workbuffer.
+ * @param params - Input parameters.
+ * @param behavior - Behaviour to check version and data format.
+ * @param memory_pool - Used to translate the workbuffer address for the DSP.
+ */
+ virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
+ const AudioRendererParameterInternal& params,
+ const BehaviorInfo& behavior, const MemoryPoolInfo& memory_pool);
+
+ /**
+ * Check if the manager is initialized.
+ *
+ * @return True if initialized, otherwise false.
+ */
+ virtual bool IsInitialized() const;
+
+ /**
+ * Copy the waiting performance frames to the output buffer.
+ *
+ * @param out_buffer - Output buffer to store performance frames.
+ * @param out_size - Size of the output buffer.
+ * @return Size in bytes that were written to the buffer.
+ */
+ virtual u32 CopyHistories(u8* out_buffer, u64 out_size);
+
+ /**
+ * Setup a new sys detail in the current frame, filling in addresses with offsets to the
+ * current workbuffer, to be written by the AudioRenderer. Note: This version is
+ * unused/incomplete.
+ *
+ * @param addresses - Filled with pointers to the new entry, which should be passed to
+ * the AudioRenderer with Performance commands to be written.
+ * @param unk - Unknown.
+ * @param sys_detail_type - Sys detail type.
+ * @param node_id - Node id for this entry.
+ * @return True if a new entry was created and the offsets are valid, otherwise false.
+ */
+ virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ PerformanceSysDetailType sys_detail_type, s32 node_id);
+
+ /**
+ * Setup a new entry in the current frame, filling in addresses with offsets to the current
+ * workbuffer, to be written by the AudioRenderer.
+ *
+ * @param addresses - Filled with pointers to the new entry, which should be passed to
+ * the AudioRenderer with Performance commands to be written.
+ * @param entry_type - The type of this entry. See PerformanceEntryType
+ * @param node_id - Node id for this entry.
+ * @return True if a new entry was created and the offsets are valid, otherwise false.
+ */
+ virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type,
+ s32 node_id);
+
+ /**
+ * Setup a new detail in the current frame, filling in addresses with offsets to the current
+ * workbuffer, to be written by the AudioRenderer.
+ *
+ * @param addresses - Filled with pointers to the new detail, which should be passed
+ * to the AudioRenderer with Performance commands to be written.
+ * @param entry_type - The type of this detail. See PerformanceEntryType
+ * @param node_id - Node id for this detail.
+ * @return True if a new detail was created and the offsets are valid, otherwise false.
+ */
+ virtual bool GetNextEntry(PerformanceEntryAddresses& addresses,
+ PerformanceDetailType detail_type, PerformanceEntryType entry_type,
+ s32 node_id);
+
+ /**
+ * Save the current frame to the ring buffer.
+ *
+ * @param dsp_behind - Did the AudioRenderer fall behind and not
+ * finish processing the command list?
+ * @param voices_dropped - The number of voices that were dropped.
+ * @param rendering_start_tick - The tick rendering started.
+ */
+ virtual void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick);
+
+ /**
+ * Check if the node id is a detail type.
+ *
+ * @return True if the node is a detail type, otherwise false.
+ */
+ virtual bool IsDetailTarget(u32 target_node_id) const;
+
+ /**
+ * Set the given node to be a detail type.
+ *
+ * @param target_node_id - Node to set.
+ */
+ virtual void SetDetailTarget(u32 target_node_id);
+
+private:
+ /**
+ * Create the performance manager.
+ *
+ * @param version - Performance version to create.
+ */
+ void CreateImpl(size_t version);
+
+ std::unique_ptr<PerformanceManager>
+ /// Impl for the performance manager, may be version 1 or 2.
+ impl;
+};
+
+template <PerformanceVersion Version, typename FrameHeaderVersion, typename EntryVersion,
+ typename DetailVersion>
+class PerformanceManagerImpl : public PerformanceManager {
+public:
+ void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
+ const AudioRendererParameterInternal& params, const BehaviorInfo& behavior,
+ const MemoryPoolInfo& memory_pool) override;
+ bool IsInitialized() const override;
+ u32 CopyHistories(u8* out_buffer, u64 out_size) override;
+ bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
+ PerformanceSysDetailType sys_detail_type, s32 node_id) override;
+ bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type,
+ s32 node_id) override;
+ bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceDetailType detail_type,
+ PerformanceEntryType entry_type, s32 node_id) override;
+ void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick) override;
+ bool IsDetailTarget(u32 target_node_id) const override;
+ void SetDetailTarget(u32 target_node_id) override;
+
+private:
+ /// Workbuffer used to store the current performance frame
+ std::span<u8> workbuffer{};
+ /// DSP address of the workbuffer, used by the AudioRenderer
+ CpuAddr translated_buffer{};
+ /// Current frame index
+ u32 history_frame_index{};
+ /// Current frame header
+ FrameHeaderVersion* frame_header{};
+ /// Current frame entry buffer
+ std::span<EntryVersion> entry_buffer{};
+ /// Current frame detail buffer
+ std::span<DetailVersion> detail_buffer{};
+ /// Current frame entry count
+ u32 entry_count{};
+ /// Current frame detail count
+ u32 detail_count{};
+ /// Ringbuffer of previous frames
+ std::span<u8> frame_history{};
+ /// Current history frame header
+ FrameHeaderVersion* frame_history_header{};
+ /// Current history entry buffer
+ std::span<EntryVersion> frame_history_entries{};
+ /// Current history detail buffer
+ std::span<DetailVersion> frame_history_details{};
+ /// Current history ringbuffer write index
+ u32 output_frame_index{};
+ /// Last history frame index that was written back to the game
+ u32 last_output_frame_index{};
+ /// Maximum number of history frames in the ringbuffer
+ u32 max_frames{};
+ /// Number of entries per frame
+ u32 entries_per_frame{};
+ /// Maximum number of details per frame
+ u32 max_detail_count{};
+ /// Frame size in bytes
+ u64 frame_size{};
+ /// Is the performance manager initialized?
+ bool is_initialized{};
+ /// Target node id
+ u32 target_node_id{};
+ /// Performance version in use
+ PerformanceVersion version{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp
new file mode 100644
index 000000000..d91f10402
--- /dev/null
+++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp
@@ -0,0 +1,76 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
+#include "audio_core/renderer/upsampler/upsampler_manager.h"
+
+namespace AudioCore::AudioRenderer {
+
+CircularBufferSinkInfo::CircularBufferSinkInfo() {
+ state.fill(0);
+ parameter.fill(0);
+ type = Type::CircularBufferSink;
+
+ auto state_{reinterpret_cast<CircularBufferState*>(state.data())};
+ state_->address_info.Setup(0, 0);
+}
+
+void CircularBufferSinkInfo::CleanUp() {
+ auto state_{reinterpret_cast<DeviceState*>(state.data())};
+
+ if (state_->upsampler_info) {
+ state_->upsampler_info->manager->Free(state_->upsampler_info);
+ state_->upsampler_info = nullptr;
+ }
+
+ parameter.fill(0);
+ type = Type::Invalid;
+}
+
+void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ const InParameter& in_params, const PoolMapper& pool_mapper) {
+ const auto buffer_params{
+ reinterpret_cast<const CircularBufferInParameter*>(&in_params.circular_buffer)};
+ auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
+ auto current_state{reinterpret_cast<CircularBufferState*>(state.data())};
+
+ if (in_use == buffer_params->in_use && !buffer_unmapped) {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ out_status.writeOffset = current_state->last_pos2;
+ return;
+ }
+
+ node_id = in_params.node_id;
+ in_use = in_params.in_use;
+
+ if (in_use) {
+ buffer_unmapped =
+ !pool_mapper.TryAttachBuffer(error_info, current_state->address_info,
+ buffer_params->cpu_address, buffer_params->size);
+ *current_params = *buffer_params;
+ } else {
+ *current_params = *buffer_params;
+ }
+ out_status.writeOffset = current_state->last_pos2;
+}
+
+void CircularBufferSinkInfo::UpdateForCommandGeneration() {
+ if (in_use) {
+ auto params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
+ auto state_{reinterpret_cast<CircularBufferState*>(state.data())};
+
+ const auto pos{state_->current_pos};
+ state_->last_pos2 = state_->last_pos;
+ state_->last_pos = pos;
+
+ state_->current_pos += static_cast<s32>(params->input_count * params->sample_count *
+ GetSampleFormatByteSize(SampleFormat::PcmInt16));
+ if (params->size > 0) {
+ state_->current_pos %= params->size;
+ }
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.h b/src/audio_core/renderer/sink/circular_buffer_sink_info.h
new file mode 100644
index 000000000..3356213ea
--- /dev/null
+++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.h
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/sink/sink_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Info for a circular buffer sink.
+ */
+class CircularBufferSinkInfo : public SinkInfoBase {
+public:
+ CircularBufferSinkInfo();
+
+ /**
+ * Clean up for info, resetting it to a default state.
+ */
+ void CleanUp() override;
+
+ /**
+ * Update the info according to parameters, and write the current state to out_status.
+ *
+ * @param error_info - Output error code.
+ * @param out_status - Output status.
+ * @param in_params - Input parameters.
+ * @param pool_mapper - Used to map the circular buffer.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ const InParameter& in_params, const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the circular buffer on command generation, incrementing its current offsets.
+ */
+ void UpdateForCommandGeneration() override;
+};
+static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase),
+ "CircularBufferSinkInfo is too large!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/device_sink_info.cpp b/src/audio_core/renderer/sink/device_sink_info.cpp
new file mode 100644
index 000000000..b7b3d6f1d
--- /dev/null
+++ b/src/audio_core/renderer/sink/device_sink_info.cpp
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/sink/device_sink_info.h"
+#include "audio_core/renderer/upsampler/upsampler_manager.h"
+
+namespace AudioCore::AudioRenderer {
+
+DeviceSinkInfo::DeviceSinkInfo() {
+ state.fill(0);
+ parameter.fill(0);
+ type = Type::DeviceSink;
+}
+
+void DeviceSinkInfo::CleanUp() {
+ auto state_{reinterpret_cast<DeviceState*>(state.data())};
+
+ if (state_->upsampler_info) {
+ state_->upsampler_info->manager->Free(state_->upsampler_info);
+ state_->upsampler_info = nullptr;
+ }
+
+ parameter.fill(0);
+ type = Type::Invalid;
+}
+
+void DeviceSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ const InParameter& in_params,
+ [[maybe_unused]] const PoolMapper& pool_mapper) {
+
+ const auto device_params{reinterpret_cast<const DeviceInParameter*>(&in_params.device)};
+ auto current_params{reinterpret_cast<DeviceInParameter*>(parameter.data())};
+
+ if (in_use == in_params.in_use) {
+ current_params->downmix_enabled = device_params->downmix_enabled;
+ current_params->downmix_coeff = device_params->downmix_coeff;
+ } else {
+ type = in_params.type;
+ in_use = in_params.in_use;
+ node_id = in_params.node_id;
+ *current_params = *device_params;
+ }
+
+ auto current_state{reinterpret_cast<DeviceState*>(state.data())};
+
+ for (size_t i = 0; i < current_state->downmix_coeff.size(); i++) {
+ current_state->downmix_coeff[i] = current_params->downmix_coeff[i];
+ }
+
+ std::memset(&out_status, 0, sizeof(OutStatus));
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void DeviceSinkInfo::UpdateForCommandGeneration() {}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/device_sink_info.h b/src/audio_core/renderer/sink/device_sink_info.h
new file mode 100644
index 000000000..a1c441454
--- /dev/null
+++ b/src/audio_core/renderer/sink/device_sink_info.h
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/sink/sink_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Info for a device sink.
+ */
+class DeviceSinkInfo : public SinkInfoBase {
+public:
+ DeviceSinkInfo();
+
+ /**
+ * Clean up for info, resetting it to a default state.
+ */
+ void CleanUp() override;
+
+ /**
+ * Update the info according to parameters, and write the current state to out_status.
+ *
+ * @param error_info - Output error code.
+ * @param out_status - Output status.
+ * @param in_params - Input parameters.
+ * @param pool_mapper - Unused.
+ */
+ void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ const InParameter& in_params, const PoolMapper& pool_mapper) override;
+
+ /**
+ * Update the device sink on command generation, unused.
+ */
+ void UpdateForCommandGeneration() override;
+};
+static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!");
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_context.cpp b/src/audio_core/renderer/sink/sink_context.cpp
new file mode 100644
index 000000000..634bc1cf9
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_context.cpp
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/sink/sink_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) {
+ sink_infos = sink_infos_;
+ sink_count = sink_count_;
+}
+
+SinkInfoBase* SinkContext::GetInfo(const u32 index) {
+ return &sink_infos[index];
+}
+
+u32 SinkContext::GetCount() const {
+ return sink_count;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_context.h b/src/audio_core/renderer/sink/sink_context.h
new file mode 100644
index 000000000..185572e29
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_context.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/sink/sink_info_base.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Manages output sinks.
+ */
+class SinkContext {
+public:
+ /**
+ * Initialize the sink context.
+ *
+ * @param sink_infos - Workbuffer for the sinks.
+ * @param sink_count - Number of sinks in the buffer.
+ */
+ void Initialize(std::span<SinkInfoBase> sink_infos, u32 sink_count);
+
+ /**
+ * Get a given index's info.
+ *
+ * @param index - Sink index to get.
+ * @return The sink info base for the given index.
+ */
+ SinkInfoBase* GetInfo(u32 index);
+
+ /**
+ * Get the current number of sinks.
+ *
+ * @return The number of sinks.
+ */
+ u32 GetCount() const;
+
+private:
+ /// Buffer of sink infos
+ std::span<SinkInfoBase> sink_infos{};
+ /// Number of sinks in the buffer
+ u32 sink_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_info_base.cpp b/src/audio_core/renderer/sink/sink_info_base.cpp
new file mode 100644
index 000000000..4279beaa0
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_info_base.cpp
@@ -0,0 +1,51 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "audio_core/renderer/sink/sink_info_base.h"
+
+namespace AudioCore::AudioRenderer {
+
+void SinkInfoBase::CleanUp() {
+ type = Type::Invalid;
+}
+
+void SinkInfoBase::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ [[maybe_unused]] const InParameter& in_params,
+ [[maybe_unused]] const PoolMapper& pool_mapper) {
+ std::memset(&out_status, 0, sizeof(OutStatus));
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+}
+
+void SinkInfoBase::UpdateForCommandGeneration() {}
+
+SinkInfoBase::DeviceState* SinkInfoBase::GetDeviceState() {
+ return reinterpret_cast<DeviceState*>(state.data());
+}
+
+SinkInfoBase::Type SinkInfoBase::GetType() const {
+ return type;
+}
+
+bool SinkInfoBase::IsUsed() const {
+ return in_use;
+}
+
+bool SinkInfoBase::ShouldSkip() const {
+ return buffer_unmapped;
+}
+
+u32 SinkInfoBase::GetNodeId() const {
+ return node_id;
+}
+
+u8* SinkInfoBase::GetState() {
+ return state.data();
+}
+
+u8* SinkInfoBase::GetParameter() {
+ return parameter.data();
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_info_base.h b/src/audio_core/renderer/sink/sink_info_base.h
new file mode 100644
index 000000000..a1b855f20
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_info_base.h
@@ -0,0 +1,177 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/memory/address_info.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+struct UpsamplerInfo;
+class PoolMapper;
+
+/**
+ * Base for the circular buffer and device sinks, holding their states for the AudioRenderer and
+ * their parametetrs for generating sink commands.
+ */
+class SinkInfoBase {
+public:
+ enum class Type : u8 {
+ Invalid,
+ DeviceSink,
+ CircularBufferSink,
+ };
+
+ struct DeviceInParameter {
+ /* 0x000 */ char name[0x100];
+ /* 0x100 */ u32 input_count;
+ /* 0x104 */ std::array<s8, MaxChannels> inputs;
+ /* 0x10A */ char unk10A[0x1];
+ /* 0x10B */ bool downmix_enabled;
+ /* 0x10C */ std::array<f32, 4> downmix_coeff;
+ };
+ static_assert(sizeof(DeviceInParameter) == 0x11C, "DeviceInParameter has the wrong size!");
+
+ struct DeviceState {
+ /* 0x00 */ UpsamplerInfo* upsampler_info;
+ /* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff;
+ /* 0x18 */ char unk18[0x18];
+ };
+ static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!");
+
+ struct CircularBufferInParameter {
+ /* 0x00 */ u64 cpu_address;
+ /* 0x08 */ u32 size;
+ /* 0x0C */ u32 input_count;
+ /* 0x10 */ u32 sample_count;
+ /* 0x14 */ u32 previous_pos;
+ /* 0x18 */ SampleFormat format;
+ /* 0x1C */ std::array<s8, MaxChannels> inputs;
+ /* 0x22 */ bool in_use;
+ /* 0x23 */ char unk23[0x5];
+ };
+ static_assert(sizeof(CircularBufferInParameter) == 0x28,
+ "CircularBufferInParameter has the wrong size!");
+
+ struct CircularBufferState {
+ /* 0x00 */ u32 last_pos2;
+ /* 0x04 */ s32 current_pos;
+ /* 0x08 */ u32 last_pos;
+ /* 0x0C */ char unk0C[0x4];
+ /* 0x10 */ AddressInfo address_info;
+ };
+ static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
+
+ struct InParameter {
+ /* 0x000 */ Type type;
+ /* 0x001 */ bool in_use;
+ /* 0x004 */ u32 node_id;
+ /* 0x008 */ char unk08[0x18];
+ union {
+ /* 0x020 */ DeviceInParameter device;
+ /* 0x020 */ CircularBufferInParameter circular_buffer;
+ };
+ };
+ static_assert(sizeof(InParameter) == 0x140, "SinkInfoBase::InParameter has the wrong size!");
+
+ struct OutStatus {
+ /* 0x00 */ u32 writeOffset;
+ /* 0x04 */ char unk04[0x1C];
+ }; // size == 0x20
+ static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
+
+ virtual ~SinkInfoBase() = default;
+
+ /**
+ * Clean up for info, resetting it to a default state.
+ */
+ virtual void CleanUp();
+
+ /**
+ * Update the info according to parameters, and write the current state to out_status.
+ *
+ * @param error_info - Output error code.
+ * @param out_status - Output status.
+ * @param in_params - Input parameters.
+ * @param pool_mapper - Used to map the circular buffer.
+ */
+ virtual void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
+ [[maybe_unused]] const InParameter& in_params,
+ [[maybe_unused]] const PoolMapper& pool_mapper);
+
+ /**
+ * Update the circular buffer on command generation, incrementing its current offsets.
+ */
+ virtual void UpdateForCommandGeneration();
+
+ /**
+ * Get the state as a device sink.
+ *
+ * @return Device state.
+ */
+ DeviceState* GetDeviceState();
+
+ /**
+ * Get the type of this sink.
+ *
+ * @return Either Device, Circular, or Invalid.
+ */
+ Type GetType() const;
+
+ /**
+ * Check if this sink is in use.
+ *
+ * @return True if used, otherwise false.
+ */
+ bool IsUsed() const;
+
+ /**
+ * Check if this sink should be skipped for updates.
+ *
+ * @return True if it should be skipped, otherwise false.
+ */
+ bool ShouldSkip() const;
+
+ /**
+ * Get the node if of this sink.
+ *
+ * @return Node id for this sink.
+ */
+ u32 GetNodeId() const;
+
+ /**
+ * Get the state of this sink.
+ *
+ * @return Pointer to the state, must be cast to the correct type.
+ */
+ u8* GetState();
+
+ /**
+ * Get the parameters of this sink.
+ *
+ * @return Pointer to the parameters, must be cast to the correct type.
+ */
+ u8* GetParameter();
+
+protected:
+ /// Type of this sink
+ Type type{Type::Invalid};
+ /// Is this sink in use?
+ bool in_use{};
+ /// Is this sink's buffer unmapped? Circular only
+ bool buffer_unmapped{};
+ /// Node id for this sink
+ u32 node_id{};
+ /// State buffer for this sink
+ std::array<u8, std::max(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
+ /// Parameter buffer for this sink
+ std::array<u8, std::max(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))>
+ parameter{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp
new file mode 100644
index 000000000..7a23ba43f
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_context.cpp
@@ -0,0 +1,217 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/common/workbuffer_allocator.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+#include "common/alignment.h"
+
+namespace AudioCore::AudioRenderer {
+
+SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id,
+ const s32 destination_id) {
+ return splitter_infos[splitter_id].GetData(destination_id);
+}
+
+SplitterInfo& SplitterContext::GetInfo(const s32 splitter_id) {
+ return splitter_infos[splitter_id];
+}
+
+u32 SplitterContext::GetDataCount() const {
+ return destinations_count;
+}
+
+u32 SplitterContext::GetInfoCount() const {
+ return info_count;
+}
+
+SplitterDestinationData& SplitterContext::GetData(const u32 index) {
+ return splitter_destinations[index];
+}
+
+void SplitterContext::Setup(std::span<SplitterInfo> splitter_infos_, const u32 splitter_info_count_,
+ SplitterDestinationData* splitter_destinations_,
+ const u32 destination_count_, const bool splitter_bug_fixed_) {
+ splitter_infos = splitter_infos_;
+ info_count = splitter_info_count_;
+ splitter_destinations = splitter_destinations_;
+ destinations_count = destination_count_;
+ splitter_bug_fixed = splitter_bug_fixed_;
+}
+
+bool SplitterContext::UsingSplitter() const {
+ return splitter_infos.size() > 0 && info_count > 0 && splitter_destinations != nullptr &&
+ destinations_count > 0;
+}
+
+void SplitterContext::ClearAllNewConnectionFlag() {
+ for (s32 i = 0; i < info_count; i++) {
+ splitter_infos[i].SetNewConnectionFlag();
+ }
+}
+
+bool SplitterContext::Initialize(const BehaviorInfo& behavior,
+ const AudioRendererParameterInternal& params,
+ WorkbufferAllocator& allocator) {
+ if (behavior.IsSplitterSupported() && params.splitter_infos > 0 &&
+ params.splitter_destinations > 0) {
+ splitter_infos = allocator.Allocate<SplitterInfo>(params.splitter_infos, 0x10);
+
+ for (u32 i = 0; i < params.splitter_infos; i++) {
+ std::construct_at<SplitterInfo>(&splitter_infos[i], static_cast<s32>(i));
+ }
+
+ if (splitter_infos.size() == 0) {
+ splitter_infos = {};
+ return false;
+ }
+
+ splitter_destinations =
+ allocator.Allocate<SplitterDestinationData>(params.splitter_destinations, 0x10).data();
+
+ for (s32 i = 0; i < params.splitter_destinations; i++) {
+ std::construct_at<SplitterDestinationData>(&splitter_destinations[i], i);
+ }
+
+ if (params.splitter_destinations <= 0) {
+ splitter_infos = {};
+ splitter_destinations = nullptr;
+ return false;
+ }
+
+ Setup(splitter_infos, params.splitter_infos, splitter_destinations,
+ params.splitter_destinations, behavior.IsSplitterBugFixed());
+ }
+ return true;
+}
+
+bool SplitterContext::Update(const u8* input, u32& consumed_size) {
+ auto in_params{reinterpret_cast<const InParameterHeader*>(input)};
+
+ if (destinations_count == 0 || info_count == 0) {
+ consumed_size = 0;
+ return true;
+ }
+
+ if (in_params->magic != GetSplitterInParamHeaderMagic()) {
+ consumed_size = 0;
+ return false;
+ }
+
+ for (auto& splitter_info : splitter_infos) {
+ splitter_info.ClearNewConnectionFlag();
+ }
+
+ u32 offset{sizeof(InParameterHeader)};
+ offset = UpdateInfo(input, offset, in_params->info_count);
+ offset = UpdateData(input, offset, in_params->destination_count);
+
+ consumed_size = Common::AlignUp(offset, 0x10);
+ return true;
+}
+
+u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_count) {
+ for (u32 i = 0; i < splitter_count; i++) {
+ auto info_header{reinterpret_cast<const SplitterInfo::InParameter*>(input + offset)};
+
+ if (info_header->magic != GetSplitterInfoMagic()) {
+ continue;
+ }
+
+ if (info_header->id < 0 || info_header->id > info_count) {
+ break;
+ }
+
+ auto& info{splitter_infos[info_header->id]};
+ RecomposeDestination(info, info_header);
+
+ offset += info.Update(info_header);
+ }
+
+ return offset;
+}
+
+u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) {
+ for (u32 i = 0; i < count; i++) {
+ auto data_header{
+ reinterpret_cast<const SplitterDestinationData::InParameter*>(input + offset)};
+
+ if (data_header->magic != GetSplitterSendDataMagic()) {
+ continue;
+ }
+
+ if (data_header->id < 0 || data_header->id > destinations_count) {
+ continue;
+ }
+
+ splitter_destinations[data_header->id].Update(*data_header);
+ offset += sizeof(SplitterDestinationData::InParameter);
+ }
+
+ return offset;
+}
+
+void SplitterContext::UpdateInternalState() {
+ for (s32 i = 0; i < info_count; i++) {
+ splitter_infos[i].UpdateInternalState();
+ }
+}
+
+void SplitterContext::RecomposeDestination(SplitterInfo& out_info,
+ const SplitterInfo::InParameter* info_header) {
+ auto destination{out_info.GetData(0)};
+ while (destination != nullptr) {
+ auto dest{destination->GetNext()};
+ destination->SetNext(nullptr);
+ destination = dest;
+ }
+ out_info.SetDestinations(nullptr);
+
+ auto dest_count{info_header->destination_count};
+ if (!splitter_bug_fixed) {
+ dest_count = std::min(dest_count, GetDestCountPerInfoForCompat());
+ }
+
+ if (dest_count == 0) {
+ return;
+ }
+
+ std::span<const u32> destination_ids{reinterpret_cast<const u32*>(&info_header[1]), dest_count};
+
+ auto head{&splitter_destinations[destination_ids[0]]};
+ auto current_destination{head};
+ for (u32 i = 1; i < dest_count; i++) {
+ auto next_destination{&splitter_destinations[destination_ids[i]]};
+ current_destination->SetNext(next_destination);
+ current_destination = next_destination;
+ }
+
+ out_info.SetDestinations(head);
+ out_info.SetDestinationCount(dest_count);
+}
+
+u32 SplitterContext::GetDestCountPerInfoForCompat() const {
+ if (info_count <= 0) {
+ return 0;
+ }
+ return static_cast<u32>(destinations_count / info_count);
+}
+
+u64 SplitterContext::CalcWorkBufferSize(const BehaviorInfo& behavior,
+ const AudioRendererParameterInternal& params) {
+ u64 size{0};
+ if (!behavior.IsSplitterSupported()) {
+ return size;
+ }
+
+ size += params.splitter_destinations * sizeof(SplitterDestinationData) +
+ params.splitter_infos * sizeof(SplitterInfo);
+
+ if (behavior.IsSplitterBugFixed()) {
+ size += Common::AlignUp(params.splitter_destinations * sizeof(u32), 0x10);
+ }
+ return size;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h
new file mode 100644
index 000000000..cfd092b4f
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_context.h
@@ -0,0 +1,189 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/splitter/splitter_destinations_data.h"
+#include "audio_core/renderer/splitter/splitter_info.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+struct AudioRendererParameterInternal;
+class WorkbufferAllocator;
+
+namespace AudioRenderer {
+class BehaviorInfo;
+
+/**
+ * The splitter allows much more control over how sound is mixed together.
+ * Previously, one mix can only connect to one other, and you may need
+ * more mixes (and duplicate processing) to achieve the same result.
+ * With the splitter, many-to-one and one-to-many mixing is possible.
+ * This was added in revision 2.
+ * Had a bug with incorrect numbers of destinations, fixed in revision 5.
+ */
+class SplitterContext {
+ struct InParameterHeader {
+ /* 0x00 */ u32 magic; // 'SNDH'
+ /* 0x04 */ s32 info_count;
+ /* 0x08 */ s32 destination_count;
+ /* 0x0C */ char unk0C[0x14];
+ };
+ static_assert(sizeof(InParameterHeader) == 0x20,
+ "SplitterContext::InParameterHeader has the wrong size!");
+
+public:
+ /**
+ * Get a destination mix from the given splitter and destination index.
+ *
+ * @param splitter_id - Splitter index to get from.
+ * @param destination_id - Destination index within the splitter.
+ * @return Pointer to the found destination. May be nullptr.
+ */
+ SplitterDestinationData* GetDesintationData(s32 splitter_id, s32 destination_id);
+
+ /**
+ * Get a splitter from the given index.
+ *
+ * @param index - Index of the desired splitter.
+ * @return Splitter requested.
+ */
+ SplitterInfo& GetInfo(s32 index);
+
+ /**
+ * Get the total number of splitter destinations.
+ *
+ * @return Number of destiantions.
+ */
+ u32 GetDataCount() const;
+
+ /**
+ * Get the total number of splitters.
+ *
+ * @return Number of splitters.
+ */
+ u32 GetInfoCount() const;
+
+ /**
+ * Get a specific global destination.
+ *
+ * @param index - Index of the desired destination.
+ * @return The requested destination.
+ */
+ SplitterDestinationData& GetData(u32 index);
+
+ /**
+ * Check if the splitter is in use.
+ *
+ * @return True if any splitter or destination is in use, otherwise false.
+ */
+ bool UsingSplitter() const;
+
+ /**
+ * Mark all splitters as having new connections.
+ */
+ void ClearAllNewConnectionFlag();
+
+ /**
+ * Initialize the context.
+ *
+ * @param behavior - Used to check for splitter support.
+ * @param params - Input parameters.
+ * @param allocator - Allocator used to allocate workbuffer memory.
+ */
+ bool Initialize(const BehaviorInfo& behavior, const AudioRendererParameterInternal& params,
+ WorkbufferAllocator& allocator);
+
+ /**
+ * Update the context.
+ *
+ * @param input - Input buffer with the new info,
+ * expected to point to a InParameterHeader.
+ * @param consumed_size - Output with the number of bytes consumed from input.
+ */
+ bool Update(const u8* input, u32& consumed_size);
+
+ /**
+ * Update the splitters.
+ *
+ * @param input - Input buffer with the new info.
+ * @param offset - Current offset within the input buffer,
+ * input + offset should point to a SplitterInfo::InParameter.
+ * @param splitter_count - Number of splitters in the input buffer.
+ * @return Number of bytes consumed in input.
+ */
+ u32 UpdateInfo(const u8* input, u32 offset, u32 splitter_count);
+
+ /**
+ * Update the splitters.
+ *
+ * @param input - Input buffer with the new info.
+ * @param offset - Current offset within the input buffer,
+ * input + offset should point to a
+ * SplitterDestinationData::InParameter.
+ * @param destination_count - Number of destinations in the input buffer.
+ * @return Number of bytes consumed in input.
+ */
+ u32 UpdateData(const u8* input, u32 offset, u32 destination_count);
+
+ /**
+ * Update the state of all destinations in all splitters.
+ */
+ void UpdateInternalState();
+
+ /**
+ * Replace the given splitter's destinations with new ones.
+ *
+ * @param out_info - Splitter to recompose.
+ * @param info_header - Input parameters containing new destination ids.
+ */
+ void RecomposeDestination(SplitterInfo& out_info, const SplitterInfo::InParameter* info_header);
+
+ /**
+ * Old calculation for destinations, this is the thing the splitter bug fixes.
+ * Left for compatibility, and now min'd with the actual count to not bug.
+ *
+ * @return Number of splitter destinations.
+ */
+ u32 GetDestCountPerInfoForCompat() const;
+
+ /**
+ * Calculate the size of the required workbuffer for splitters and destinations.
+ *
+ * @param behavior - Used to check splitter features.
+ * @param params - Input parameters with splitter/destination counts.
+ * @return Required buffer size.
+ */
+ static u64 CalcWorkBufferSize(const BehaviorInfo& behavior,
+ const AudioRendererParameterInternal& params);
+
+private:
+ /**
+ * Setup the context.
+ *
+ * @param splitter_infos - Workbuffer for splitters.
+ * @param splitter_info_count - Number of splitters in the workbuffer.
+ * @param splitter_destinations - Workbuffer for splitter destinations.
+ * @param destination_count - Number of destinations in the workbuffer.
+ * @param splitter_bug_fixed - Is the splitter bug fixed?
+ */
+ void Setup(std::span<SplitterInfo> splitter_infos, u32 splitter_info_count,
+ SplitterDestinationData* splitter_destinations, u32 destination_count,
+ bool splitter_bug_fixed);
+
+ /// Workbuffer for splitters
+ std::span<SplitterInfo> splitter_infos{};
+ /// Number of splitters in buffer
+ s32 info_count{};
+ /// Workbuffer for destinations
+ SplitterDestinationData* splitter_destinations{};
+ /// Number of destinations in buffer
+ s32 destinations_count{};
+ /// Is the splitter bug fixed?
+ bool splitter_bug_fixed{};
+};
+
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp
new file mode 100644
index 000000000..b27d44896
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp
@@ -0,0 +1,87 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/splitter/splitter_destinations_data.h"
+
+namespace AudioCore::AudioRenderer {
+
+SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {}
+
+void SplitterDestinationData::ClearMixVolume() {
+ mix_volumes.fill(0.0f);
+ prev_mix_volumes.fill(0.0f);
+}
+
+s32 SplitterDestinationData::GetId() const {
+ return id;
+}
+
+bool SplitterDestinationData::IsConfigured() const {
+ return in_use && destination_id != UnusedMixId;
+}
+
+s32 SplitterDestinationData::GetMixId() const {
+ return destination_id;
+}
+
+f32 SplitterDestinationData::GetMixVolume(const u32 index) const {
+ if (index >= mix_volumes.size()) {
+ LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolume Invalid index {}", index);
+ return 0.0f;
+ }
+ return mix_volumes[index];
+}
+
+std::span<f32> SplitterDestinationData::GetMixVolume() {
+ return mix_volumes;
+}
+
+f32 SplitterDestinationData::GetMixVolumePrev(const u32 index) const {
+ if (index >= prev_mix_volumes.size()) {
+ LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolumePrev Invalid index {}",
+ index);
+ return 0.0f;
+ }
+ return prev_mix_volumes[index];
+}
+
+std::span<f32> SplitterDestinationData::GetMixVolumePrev() {
+ return prev_mix_volumes;
+}
+
+void SplitterDestinationData::Update(const InParameter& params) {
+ if (params.id != id || params.magic != GetSplitterSendDataMagic()) {
+ return;
+ }
+
+ destination_id = params.mix_id;
+ mix_volumes = params.mix_volumes;
+
+ if (!in_use && params.in_use) {
+ prev_mix_volumes = mix_volumes;
+ need_update = false;
+ }
+
+ in_use = params.in_use;
+}
+
+void SplitterDestinationData::MarkAsNeedToUpdateInternalState() {
+ need_update = true;
+}
+
+void SplitterDestinationData::UpdateInternalState() {
+ if (in_use && need_update) {
+ prev_mix_volumes = mix_volumes;
+ }
+ need_update = false;
+}
+
+SplitterDestinationData* SplitterDestinationData::GetNext() const {
+ return next;
+}
+
+void SplitterDestinationData::SetNext(SplitterDestinationData* next_) {
+ next = next_;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h
new file mode 100644
index 000000000..bd3d55748
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h
@@ -0,0 +1,135 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <span>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Represents a mixing node, can be connected to a previous and next destination forming a chain
+ * that a certain mix buffer will pass through to output.
+ */
+class SplitterDestinationData {
+public:
+ struct InParameter {
+ /* 0x00 */ u32 magic; // 'SNDD'
+ /* 0x04 */ s32 id;
+ /* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes;
+ /* 0x68 */ u32 mix_id;
+ /* 0x6C */ bool in_use;
+ };
+ static_assert(sizeof(InParameter) == 0x70,
+ "SplitterDestinationData::InParameter has the wrong size!");
+
+ SplitterDestinationData(s32 id);
+
+ /**
+ * Reset the mix volumes for this destination.
+ */
+ void ClearMixVolume();
+
+ /**
+ * Get the id of this destination.
+ *
+ * @return Id for this destination.
+ */
+ s32 GetId() const;
+
+ /**
+ * Check if this destination is correctly configured.
+ *
+ * @return True if configured, otherwise false.
+ */
+ bool IsConfigured() const;
+
+ /**
+ * Get the mix id for this destination.
+ *
+ * @return Mix id for this destination.
+ */
+ s32 GetMixId() const;
+
+ /**
+ * Get the current mix volume of a given index in this destination.
+ *
+ * @param index - Mix buffer index to get the volume for.
+ * @return Current volume of the specified mix.
+ */
+ f32 GetMixVolume(u32 index) const;
+
+ /**
+ * Get the current mix volumes for all mix buffers in this destination.
+ *
+ * @return Span of current mix buffer volumes.
+ */
+ std::span<f32> GetMixVolume();
+
+ /**
+ * Get the previous mix volume of a given index in this destination.
+ *
+ * @param index - Mix buffer index to get the volume for.
+ * @return Previous volume of the specified mix.
+ */
+ f32 GetMixVolumePrev(u32 index) const;
+
+ /**
+ * Get the previous mix volumes for all mix buffers in this destination.
+ *
+ * @return Span of previous mix buffer volumes.
+ */
+ std::span<f32> GetMixVolumePrev();
+
+ /**
+ * Update this destination.
+ *
+ * @param params - Inpout parameters to update the destination.
+ */
+ void Update(const InParameter& params);
+
+ /**
+ * Mark this destination as needing its volumes updated.
+ */
+ void MarkAsNeedToUpdateInternalState();
+
+ /**
+ * Copy current volumes to previous if an update is required.
+ */
+ void UpdateInternalState();
+
+ /**
+ * Get the next destination in the mix chain.
+ *
+ * @return The next splitter destination, may be nullptr if this is the last in the chain.
+ */
+ SplitterDestinationData* GetNext() const;
+
+ /**
+ * Set the next destination in the mix chain.
+ *
+ * @param next - Destination this one is to be connected to.
+ */
+ void SetNext(SplitterDestinationData* next);
+
+private:
+ /// Id of this destination
+ const s32 id;
+ /// Mix id this destination represents
+ s32 destination_id{UnusedMixId};
+ /// Current mix volumes
+ std::array<f32, MaxMixBuffers> mix_volumes{0.0f};
+ /// Previous mix volumes
+ std::array<f32, MaxMixBuffers> prev_mix_volumes{0.0f};
+ /// Next destination in the mix chain
+ SplitterDestinationData* next{};
+ /// Is this destiantion in use?
+ bool in_use{};
+ /// Does this destiantion need its volumes updated?
+ bool need_update{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_info.cpp b/src/audio_core/renderer/splitter/splitter_info.cpp
new file mode 100644
index 000000000..1aee6720b
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_info.cpp
@@ -0,0 +1,79 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/splitter/splitter_info.h"
+
+namespace AudioCore::AudioRenderer {
+
+SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {}
+
+void SplitterInfo::InitializeInfos(SplitterInfo* splitters, const u32 count) {
+ if (splitters == nullptr) {
+ return;
+ }
+
+ for (u32 i = 0; i < count; i++) {
+ auto& splitter{splitters[i]};
+ splitter.destinations = nullptr;
+ splitter.destination_count = 0;
+ splitter.has_new_connection = true;
+ }
+}
+
+u32 SplitterInfo::Update(const InParameter* params) {
+ if (params->id != id) {
+ return 0;
+ }
+ sample_rate = params->sample_rate;
+ has_new_connection = true;
+ return static_cast<u32>((sizeof(InParameter) + 3 * sizeof(s32)) +
+ params->destination_count * sizeof(s32));
+}
+
+SplitterDestinationData* SplitterInfo::GetData(const u32 destination_id) {
+ auto out_destination{destinations};
+ u32 i{0};
+ while (i < destination_id) {
+ if (out_destination == nullptr) {
+ break;
+ }
+ out_destination = out_destination->GetNext();
+ i++;
+ }
+
+ return out_destination;
+}
+
+u32 SplitterInfo::GetDestinationCount() const {
+ return destination_count;
+}
+
+void SplitterInfo::SetDestinationCount(const u32 count) {
+ destination_count = count;
+}
+
+bool SplitterInfo::HasNewConnection() const {
+ return has_new_connection;
+}
+
+void SplitterInfo::ClearNewConnectionFlag() {
+ has_new_connection = false;
+}
+
+void SplitterInfo::SetNewConnectionFlag() {
+ has_new_connection = true;
+}
+
+void SplitterInfo::UpdateInternalState() {
+ auto destination{destinations};
+ while (destination != nullptr) {
+ destination->UpdateInternalState();
+ destination = destination->GetNext();
+ }
+}
+
+void SplitterInfo::SetDestinations(SplitterDestinationData* destinations_) {
+ destinations = destinations_;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_info.h b/src/audio_core/renderer/splitter/splitter_info.h
new file mode 100644
index 000000000..d1d75064c
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_info.h
@@ -0,0 +1,107 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/renderer/splitter/splitter_destinations_data.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Represents a splitter, wraps multiple output destinations to split an input mix into.
+ */
+class SplitterInfo {
+public:
+ struct InParameter {
+ /* 0x00 */ u32 magic; // 'SNDI'
+ /* 0x04 */ s32 id;
+ /* 0x08 */ u32 sample_rate;
+ /* 0x0C */ u32 destination_count;
+ };
+ static_assert(sizeof(InParameter) == 0x10, "SplitterInfo::InParameter has the wrong size!");
+
+ explicit SplitterInfo(s32 id);
+
+ /**
+ * Initialize the given splitters.
+ *
+ * @param splitters - Splitters to initialize.
+ * @param count - Number of splitters given.
+ */
+ static void InitializeInfos(SplitterInfo* splitters, u32 count);
+
+ /**
+ * Update this splitter.
+ *
+ * @param params - Input parameters to update with.
+ * @return The size in bytes of this splitter.
+ */
+ u32 Update(const InParameter* params);
+
+ /**
+ * Get a destination in this splitter.
+ *
+ * @param id - Destination id to get.
+ * @return Pointer to the destination, may be nullptr.
+ */
+ SplitterDestinationData* GetData(u32 id);
+
+ /**
+ * Get the number of destinations in this splitter.
+ *
+ * @return The number of destiantions.
+ */
+ u32 GetDestinationCount() const;
+
+ /**
+ * Set the number of destinations in this splitter.
+ *
+ * @param count - The new number of destiantions.
+ */
+ void SetDestinationCount(u32 count);
+
+ /**
+ * Check if the splitter has a new connection.
+ *
+ * @return True if there is a new connection, otherwise false.
+ */
+ bool HasNewConnection() const;
+
+ /**
+ * Reset the new connection flag.
+ */
+ void ClearNewConnectionFlag();
+
+ /**
+ * Mark as having a new connection.
+ */
+ void SetNewConnectionFlag();
+
+ /**
+ * Update the state of all destinations.
+ */
+ void UpdateInternalState();
+
+ /**
+ * Set this splitter's destinations.
+ *
+ * @param destinations - The new destination list for this splitter.
+ */
+ void SetDestinations(SplitterDestinationData* destinations);
+
+private:
+ /// Id of this splitter
+ s32 id;
+ /// Sample rate of this splitter
+ u32 sample_rate{};
+ /// Number of destinations in this splitter
+ u32 destination_count{};
+ /// Does this splitter have a new connection?
+ bool has_new_connection{true};
+ /// Pointer to the destinations of this splitter
+ SplitterDestinationData* destinations{};
+ /// Number of channels this splitter manages
+ u32 channel_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp
new file mode 100644
index 000000000..7a217969e
--- /dev/null
+++ b/src/audio_core/renderer/system.cpp
@@ -0,0 +1,802 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <chrono>
+#include <span>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/common/common.h"
+#include "audio_core/common/feature_support.h"
+#include "audio_core/common/workbuffer_allocator.h"
+#include "audio_core/renderer/adsp/adsp.h"
+#include "audio_core/renderer/behavior/info_updater.h"
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_generator.h"
+#include "audio_core/renderer/command/command_list_header.h"
+#include "audio_core/renderer/effect/effect_info_base.h"
+#include "audio_core/renderer/effect/effect_result_state.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "audio_core/renderer/mix/mix_info.h"
+#include "audio_core/renderer/nodes/edge_matrix.h"
+#include "audio_core/renderer/nodes/node_states.h"
+#include "audio_core/renderer/sink/sink_info_base.h"
+#include "audio_core/renderer/system.h"
+#include "audio_core/renderer/upsampler/upsampler_info.h"
+#include "audio_core/renderer/voice/voice_channel_resource.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "audio_core/renderer/voice/voice_state.h"
+#include "common/alignment.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/memory.h"
+
+namespace AudioCore::AudioRenderer {
+
+u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
+ BehaviorInfo behavior;
+ behavior.SetUserLibRevision(params.revision);
+
+ u64 size{0};
+
+ size += Common::AlignUp(params.mixes * sizeof(s32), 0x40);
+ size += params.sub_mixes * MaxEffects * sizeof(s32);
+ size += (params.sub_mixes + 1) * sizeof(MixInfo);
+ size += params.voices * (sizeof(VoiceInfo) + sizeof(VoiceChannelResource) + sizeof(VoiceState));
+ size += Common::AlignUp((params.sub_mixes + 1) * sizeof(MixInfo*), 0x10);
+ size += Common::AlignUp(params.voices * sizeof(VoiceInfo*), 0x10);
+ size += Common::AlignUp(((params.sinks + params.sub_mixes) * TargetSampleCount * sizeof(s32) +
+ params.sample_count * sizeof(s32)) *
+ (params.mixes + MaxChannels),
+ 0x40);
+
+ if (behavior.IsSplitterSupported()) {
+ const auto node_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)};
+ const auto edge_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)};
+ size += Common::AlignUp(node_size + edge_size, 0x10);
+ }
+
+ size += SplitterContext::CalcWorkBufferSize(behavior, params);
+ size += (params.effects + params.voices * MaxWaveBuffers) * sizeof(MemoryPoolInfo);
+
+ if (behavior.IsEffectInfoVersion2Supported()) {
+ size += params.effects * sizeof(EffectResultState);
+ }
+ size += 0x50;
+
+ size = Common::AlignUp(size, 0x40);
+
+ size += (params.sinks + params.sub_mixes) * sizeof(UpsamplerInfo);
+ size += params.effects * sizeof(EffectInfoBase);
+ size += Common::AlignUp(params.voices * sizeof(VoiceState), 0x40);
+ size += params.sinks * sizeof(SinkInfoBase);
+
+ if (behavior.IsEffectInfoVersion2Supported()) {
+ size += params.effects * sizeof(EffectResultState);
+ }
+
+ if (params.perf_frames > 0) {
+ auto perf_size{PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame(
+ behavior, params)};
+ size += Common::AlignUp(perf_size * (params.perf_frames + 1) + 0xC0, 0x100);
+ }
+
+ if (behavior.IsVariadicCommandBufferSizeSupported()) {
+ size += CommandGenerator::CalculateCommandBufferSize(behavior, params) + (0x40 - 1) * 2;
+ } else {
+ size += 0x18000 + (0x40 - 1) * 2;
+ }
+
+ size = Common::AlignUp(size, 0x1000);
+ return size;
+}
+
+System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
+ : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {}
+
+Result System::Initialize(const AudioRendererParameterInternal& params,
+ Kernel::KTransferMemory* transfer_memory, const u64 transfer_memory_size,
+ const u32 process_handle_, const u64 applet_resource_user_id_,
+ const s32 session_id_) {
+ if (!CheckValidRevision(params.revision)) {
+ return Service::Audio::ERR_INVALID_REVISION;
+ }
+
+ if (GetWorkBufferSize(params) > transfer_memory_size) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ if (process_handle_ == 0) {
+ return Service::Audio::ERR_INVALID_PROCESS_HANDLE;
+ }
+
+ behavior.SetUserLibRevision(params.revision);
+
+ process_handle = process_handle_;
+ applet_resource_user_id = applet_resource_user_id_;
+ session_id = session_id_;
+
+ sample_rate = params.sample_rate;
+ sample_count = params.sample_count;
+ mix_buffer_count = static_cast<s16>(params.mixes);
+ voice_channels = MaxChannels;
+ upsampler_count = params.sinks + params.sub_mixes;
+ memory_pool_count = params.effects + params.voices * MaxWaveBuffers;
+ render_device = params.rendering_device;
+ execution_mode = params.execution_mode;
+
+ core.Memory().ZeroBlock(*core.Kernel().CurrentProcess(), transfer_memory->GetSourceAddress(),
+ transfer_memory_size);
+
+ // Note: We're not actually using the transfer memory because it's a pain to code for.
+ // Allocate the memory normally instead and hope the game doesn't try to read anything back
+ workbuffer = std::make_unique<u8[]>(transfer_memory_size);
+ workbuffer_size = transfer_memory_size;
+
+ PoolMapper pool_mapper(process_handle, false);
+ pool_mapper.InitializeSystemPool(memory_pool_info, workbuffer.get(), workbuffer_size);
+
+ WorkbufferAllocator allocator({workbuffer.get(), workbuffer_size}, workbuffer_size);
+
+ samples_workbuffer =
+ allocator.Allocate<s32>((voice_channels + mix_buffer_count) * sample_count, 0x10);
+ if (samples_workbuffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ auto upsampler_workbuffer{allocator.Allocate<s32>(
+ (voice_channels + mix_buffer_count) * TargetSampleCount * upsampler_count, 0x10)};
+ if (upsampler_workbuffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ depop_buffer =
+ allocator.Allocate<s32>(Common::AlignUp(static_cast<u32>(mix_buffer_count), 0x40), 0x40);
+ if (depop_buffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ // invalidate samples_workbuffer DSP cache
+
+ auto voice_infos{allocator.Allocate<VoiceInfo>(params.voices, 0x10)};
+ for (auto& voice_info : voice_infos) {
+ std::construct_at<VoiceInfo>(&voice_info);
+ }
+
+ if (voice_infos.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ auto sorted_voice_infos{allocator.Allocate<VoiceInfo*>(params.voices, 0x10)};
+ if (sorted_voice_infos.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ std::memset(sorted_voice_infos.data(), 0, sorted_voice_infos.size_bytes());
+
+ auto voice_channel_resources{allocator.Allocate<VoiceChannelResource>(params.voices, 0x10)};
+ u32 i{0};
+ for (auto& voice_channel_resource : voice_channel_resources) {
+ std::construct_at<VoiceChannelResource>(&voice_channel_resource, i++);
+ }
+
+ if (voice_channel_resources.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ auto voice_cpu_states{allocator.Allocate<VoiceState>(params.voices, 0x10)};
+ if (voice_cpu_states.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ for (auto& voice_state : voice_cpu_states) {
+ voice_state = {};
+ }
+
+ auto mix_infos{allocator.Allocate<MixInfo>(params.sub_mixes + 1, 0x10)};
+
+ if (mix_infos.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ u32 effect_process_order_count{0};
+ std::span<s32> effect_process_order_buffer{};
+
+ if (params.effects > 0) {
+ effect_process_order_count = params.effects * (params.sub_mixes + 1);
+ effect_process_order_buffer = allocator.Allocate<s32>(effect_process_order_count, 0x10);
+ if (effect_process_order_buffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+ }
+
+ i = 0;
+ for (auto& mix_info : mix_infos) {
+ std::construct_at<MixInfo>(
+ &mix_info, effect_process_order_buffer.subspan(i * params.effects, params.effects),
+ params.effects, this->behavior);
+ i++;
+ }
+
+ auto sorted_mix_infos{allocator.Allocate<MixInfo*>(params.sub_mixes + 1, 0x10)};
+ if (sorted_mix_infos.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ std::memset(sorted_mix_infos.data(), 0, sorted_mix_infos.size_bytes());
+
+ if (behavior.IsSplitterSupported()) {
+ u64 node_state_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)};
+ u64 edge_matrix_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)};
+
+ auto node_states_workbuffer{allocator.Allocate<u8>(node_state_size, 1)};
+ auto edge_matrix_workbuffer{allocator.Allocate<u8>(edge_matrix_size, 1)};
+
+ if (node_states_workbuffer.empty() || edge_matrix_workbuffer.size() == 0) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1,
+ effect_process_order_buffer, effect_process_order_count,
+ node_states_workbuffer, node_state_size, edge_matrix_workbuffer,
+ edge_matrix_size);
+ } else {
+ mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1,
+ effect_process_order_buffer, effect_process_order_count, {}, 0, {},
+ 0);
+ }
+
+ upsampler_manager = allocator.Allocate<UpsamplerManager>(1, 0x10).data();
+ if (upsampler_manager == nullptr) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ memory_pool_workbuffer = allocator.Allocate<MemoryPoolInfo>(memory_pool_count, 0x10);
+ for (auto& memory_pool : memory_pool_workbuffer) {
+ std::construct_at<MemoryPoolInfo>(&memory_pool, MemoryPoolInfo::Location::DSP);
+ }
+
+ if (memory_pool_workbuffer.empty() && memory_pool_count > 0) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ if (!splitter_context.Initialize(behavior, params, allocator)) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ std::span<EffectResultState> effect_result_states_cpu{};
+ if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) {
+ effect_result_states_cpu = allocator.Allocate<EffectResultState>(params.effects, 0x10);
+ if (effect_result_states_cpu.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+ std::memset(effect_result_states_cpu.data(), 0, effect_result_states_cpu.size_bytes());
+ }
+
+ allocator.Align(0x40);
+
+ unk_2B0 = allocator.GetSize() - allocator.GetCurrentOffset();
+ unk_2A8 = {&workbuffer[allocator.GetCurrentOffset()], unk_2B0};
+
+ upsampler_infos = allocator.Allocate<UpsamplerInfo>(upsampler_count, 0x40);
+ for (auto& upsampler_info : upsampler_infos) {
+ std::construct_at<UpsamplerInfo>(&upsampler_info);
+ }
+
+ std::construct_at<UpsamplerManager>(upsampler_manager, upsampler_count, upsampler_infos,
+ upsampler_workbuffer);
+
+ if (upsampler_infos.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ auto effect_infos{allocator.Allocate<EffectInfoBase>(params.effects, 0x40)};
+ for (auto& effect_info : effect_infos) {
+ std::construct_at<EffectInfoBase>(&effect_info);
+ }
+
+ if (effect_infos.empty() && params.effects > 0) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ std::span<EffectResultState> effect_result_states_dsp{};
+ if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) {
+ effect_result_states_dsp = allocator.Allocate<EffectResultState>(params.effects, 0x40);
+ if (effect_result_states_dsp.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+ std::memset(effect_result_states_dsp.data(), 0, effect_result_states_dsp.size_bytes());
+ }
+
+ effect_context.Initialize(effect_infos, params.effects, effect_result_states_cpu,
+ effect_result_states_dsp, effect_result_states_dsp.size());
+
+ auto sinks{allocator.Allocate<SinkInfoBase>(params.sinks, 0x10)};
+ for (auto& sink : sinks) {
+ std::construct_at<SinkInfoBase>(&sink);
+ }
+
+ if (sinks.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ sink_context.Initialize(sinks, params.sinks);
+
+ auto voice_dsp_states{allocator.Allocate<VoiceState>(params.voices, 0x40)};
+ if (voice_dsp_states.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ for (auto& voice_state : voice_dsp_states) {
+ voice_state = {};
+ }
+
+ voice_context.Initialize(sorted_voice_infos, voice_infos, voice_channel_resources,
+ voice_cpu_states, voice_dsp_states, params.voices);
+
+ if (params.perf_frames > 0) {
+ const auto perf_workbuffer_size{
+ PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior,
+ params) *
+ (params.perf_frames + 1) +
+ 0xC};
+ performance_workbuffer = allocator.Allocate<u8>(perf_workbuffer_size, 0x40);
+ if (performance_workbuffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+ std::memset(performance_workbuffer.data(), 0, performance_workbuffer.size_bytes());
+ performance_manager.Initialize(performance_workbuffer, performance_workbuffer.size_bytes(),
+ params, behavior, memory_pool_info);
+ }
+
+ render_time_limit_percent = 100;
+ drop_voice = params.voice_drop_enabled && params.execution_mode == ExecutionMode::Auto;
+
+ allocator.Align(0x40);
+ command_workbuffer_size = allocator.GetRemainingSize();
+ command_workbuffer = allocator.Allocate<u8>(command_workbuffer_size, 0x40);
+ if (command_workbuffer.empty()) {
+ return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
+ }
+
+ command_buffer_size = 0;
+ reset_command_buffers = true;
+
+ // nn::audio::dsp::FlushDataCache(transferMemory, transferMemorySize);
+
+ if (behavior.IsCommandProcessingTimeEstimatorVersion5Supported()) {
+ command_processing_time_estimator =
+ std::make_unique<CommandProcessingTimeEstimatorVersion5>(sample_count,
+ mix_buffer_count);
+ } else if (behavior.IsCommandProcessingTimeEstimatorVersion4Supported()) {
+ command_processing_time_estimator =
+ std::make_unique<CommandProcessingTimeEstimatorVersion4>(sample_count,
+ mix_buffer_count);
+ } else if (behavior.IsCommandProcessingTimeEstimatorVersion3Supported()) {
+ command_processing_time_estimator =
+ std::make_unique<CommandProcessingTimeEstimatorVersion3>(sample_count,
+ mix_buffer_count);
+ } else if (behavior.IsCommandProcessingTimeEstimatorVersion2Supported()) {
+ command_processing_time_estimator =
+ std::make_unique<CommandProcessingTimeEstimatorVersion2>(sample_count,
+ mix_buffer_count);
+ } else {
+ command_processing_time_estimator =
+ std::make_unique<CommandProcessingTimeEstimatorVersion1>(sample_count,
+ mix_buffer_count);
+ }
+
+ initialized = true;
+ return ResultSuccess;
+}
+
+void System::Finalize() {
+ if (!initialized) {
+ return;
+ }
+
+ if (active) {
+ Stop();
+ }
+
+ applet_resource_user_id = 0;
+
+ PoolMapper pool_mapper(process_handle, false);
+ pool_mapper.Unmap(memory_pool_info);
+
+ if (process_handle) {
+ pool_mapper.ClearUseState(memory_pool_workbuffer, memory_pool_count);
+ for (auto& memory_pool : memory_pool_workbuffer) {
+ if (memory_pool.IsMapped()) {
+ pool_mapper.Unmap(memory_pool);
+ }
+ }
+
+ // dsp::ProcessCleanup
+ // close handle
+ }
+ initialized = false;
+}
+
+void System::Start() {
+ std::scoped_lock l{lock};
+ frames_elapsed = 0;
+ state = State::Started;
+ active = true;
+}
+
+void System::Stop() {
+ {
+ std::scoped_lock l{lock};
+ state = State::Stopped;
+ active = false;
+ }
+
+ if (execution_mode == ExecutionMode::Auto) {
+ // Should wait for the system to terminate here, but core timing (should have) already
+ // stopped, so this isn't needed. Find a way to make this definite.
+
+ // terminate_event.Wait();
+ }
+}
+
+Result System::Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) {
+ std::scoped_lock l{lock};
+
+ const auto start_time{core.CoreTiming().GetClockTicks()};
+
+ InfoUpdater info_updater(input, output, process_handle, behavior);
+
+ auto result{info_updater.UpdateBehaviorInfo(behavior)};
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update BehaviorInfo!");
+ return result;
+ }
+
+ result = info_updater.UpdateMemoryPools(memory_pool_workbuffer, memory_pool_count);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update MemoryPools!");
+ return result;
+ }
+
+ result = info_updater.UpdateVoiceChannelResources(voice_context);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update VoiceChannelResources!");
+ return result;
+ }
+
+ result = info_updater.UpdateVoices(voice_context, memory_pool_workbuffer, memory_pool_count);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update Voices!");
+ return result;
+ }
+
+ result = info_updater.UpdateEffects(effect_context, active, memory_pool_workbuffer,
+ memory_pool_count);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update Effects!");
+ return result;
+ }
+
+ if (behavior.IsSplitterSupported()) {
+ result = info_updater.UpdateSplitterInfo(splitter_context);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update SplitterInfo!");
+ return result;
+ }
+ }
+
+ result =
+ info_updater.UpdateMixes(mix_context, mix_buffer_count, effect_context, splitter_context);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update Mixes!");
+ return result;
+ }
+
+ result = info_updater.UpdateSinks(sink_context, memory_pool_workbuffer, memory_pool_count);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update Sinks!");
+ return result;
+ }
+
+ PerformanceManager* perf_manager{nullptr};
+ if (performance_manager.IsInitialized()) {
+ perf_manager = &performance_manager;
+ }
+
+ result =
+ info_updater.UpdatePerformanceBuffer(performance, performance.size_bytes(), perf_manager);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update PerformanceBuffer!");
+ return result;
+ }
+
+ result = info_updater.UpdateErrorInfo(behavior);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update ErrorInfo!");
+ return result;
+ }
+
+ if (behavior.IsElapsedFrameCountSupported()) {
+ result = info_updater.UpdateRendererInfo(frames_elapsed);
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Failed to update RendererInfo!");
+ return result;
+ }
+ }
+
+ result = info_updater.CheckConsumedSize();
+ if (result.IsError()) {
+ LOG_ERROR(Service_Audio, "Invalid consume size!");
+ return result;
+ }
+
+ adsp_rendered_event->GetWritableEvent().Clear();
+ num_times_updated++;
+
+ const auto end_time{core.CoreTiming().GetClockTicks()};
+ ticks_spent_updating += end_time - start_time;
+
+ return ResultSuccess;
+}
+
+u32 System::GetRenderingTimeLimit() const {
+ return render_time_limit_percent;
+}
+
+void System::SetRenderingTimeLimit(const u32 limit) {
+ render_time_limit_percent = limit;
+}
+
+u32 System::GetSessionId() const {
+ return session_id;
+}
+
+u32 System::GetSampleRate() const {
+ return sample_rate;
+}
+
+u32 System::GetSampleCount() const {
+ return sample_count;
+}
+
+u32 System::GetMixBufferCount() const {
+ return mix_buffer_count;
+}
+
+ExecutionMode System::GetExecutionMode() const {
+ return execution_mode;
+}
+
+u32 System::GetRenderingDevice() const {
+ return render_device;
+}
+
+bool System::IsActive() const {
+ return active;
+}
+
+void System::SendCommandToDsp() {
+ std::scoped_lock l{lock};
+
+ if (initialized) {
+ if (active) {
+ terminate_event.Reset();
+ const auto remaining_command_count{adsp.GetRemainCommandCount(session_id)};
+ u64 command_size{0};
+
+ if (remaining_command_count) {
+ adsp_behind = true;
+ command_size = command_buffer_size;
+ } else {
+ command_size = GenerateCommand(command_workbuffer, command_workbuffer_size);
+ }
+
+ auto translated_addr{
+ memory_pool_info.Translate(CpuAddr(command_workbuffer.data()), command_size)};
+
+ auto time_limit_percent{70.0f};
+ if (behavior.IsAudioRendererProcessingTimeLimit80PercentSupported()) {
+ time_limit_percent = 80.0f;
+ } else if (behavior.IsAudioRendererProcessingTimeLimit75PercentSupported()) {
+ time_limit_percent = 75.0f;
+ } else {
+ // result ignored and 70 is used anyway
+ behavior.IsAudioRendererProcessingTimeLimit70PercentSupported();
+ time_limit_percent = 70.0f;
+ }
+
+ ADSP::CommandBuffer command_buffer{
+ .buffer{translated_addr},
+ .size{command_size},
+ .time_limit{
+ static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
+ (static_cast<f32>(render_time_limit_percent) / 100.0f))},
+ .remaining_command_count{remaining_command_count},
+ .reset_buffers{reset_command_buffers},
+ .applet_resource_user_id{applet_resource_user_id},
+ .render_time_taken{adsp.GetRenderTimeTaken(session_id)},
+ };
+
+ adsp.SendCommandBuffer(session_id, command_buffer);
+ reset_command_buffers = false;
+ command_buffer_size = command_size;
+ if (remaining_command_count == 0) {
+ adsp_rendered_event->GetWritableEvent().Signal();
+ }
+ } else {
+ adsp.ClearRemainCount(session_id);
+ terminate_event.Set();
+ }
+ }
+}
+
+u64 System::GenerateCommand(std::span<u8> in_command_buffer,
+ [[maybe_unused]] const u64 command_buffer_size_) {
+ PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count);
+ const auto start_time{core.CoreTiming().GetClockTicks()};
+
+ auto command_list_header{reinterpret_cast<CommandListHeader*>(in_command_buffer.data())};
+
+ command_list_header->buffer_count = static_cast<s16>(voice_channels + mix_buffer_count);
+ command_list_header->sample_count = sample_count;
+ command_list_header->sample_rate = sample_rate;
+ command_list_header->samples_buffer = samples_workbuffer;
+
+ const auto performance_initialized{performance_manager.IsInitialized()};
+ if (performance_initialized) {
+ performance_manager.TapFrame(adsp_behind, num_voices_dropped, render_start_tick);
+ adsp_behind = false;
+ num_voices_dropped = 0;
+ render_start_tick = 0;
+ }
+
+ s8 channel_count{2};
+ if (execution_mode == ExecutionMode::Auto) {
+ const auto& sink{core.AudioCore().GetOutputSink()};
+ channel_count = static_cast<s8>(sink.GetDeviceChannels());
+ }
+
+ AudioRendererSystemContext render_context{
+ .session_id{session_id},
+ .channels{channel_count},
+ .mix_buffer_count{mix_buffer_count},
+ .behavior{&behavior},
+ .depop_buffer{depop_buffer},
+ .upsampler_manager{upsampler_manager},
+ .memory_pool_info{&memory_pool_info},
+ };
+
+ CommandBuffer command_buffer{
+ .command_list{in_command_buffer},
+ .sample_count{sample_count},
+ .sample_rate{sample_rate},
+ .size{sizeof(CommandListHeader)},
+ .count{0},
+ .estimated_process_time{0},
+ .memory_pool{&memory_pool_info},
+ .time_estimator{command_processing_time_estimator.get()},
+ .behavior{&behavior},
+ };
+
+ PerformanceManager* perf_manager{nullptr};
+ if (performance_initialized) {
+ perf_manager = &performance_manager;
+ }
+
+ CommandGenerator command_generator{command_buffer, *command_list_header, render_context,
+ voice_context, mix_context, effect_context,
+ sink_context, splitter_context, perf_manager};
+
+ voice_context.SortInfo();
+
+ const auto start_estimated_time{command_buffer.estimated_process_time};
+
+ command_generator.GenerateVoiceCommands();
+ command_generator.GenerateSubMixCommands();
+ command_generator.GenerateFinalMixCommands();
+ command_generator.GenerateSinkCommands();
+
+ if (drop_voice) {
+ f32 time_limit_percent{70.0f};
+ if (render_context.behavior->IsAudioRendererProcessingTimeLimit80PercentSupported()) {
+ time_limit_percent = 80.0f;
+ } else if (render_context.behavior
+ ->IsAudioRendererProcessingTimeLimit75PercentSupported()) {
+ time_limit_percent = 75.0f;
+ } else {
+ // result is ignored
+ render_context.behavior->IsAudioRendererProcessingTimeLimit70PercentSupported();
+ time_limit_percent = 70.0f;
+ }
+ const auto time_limit{static_cast<u32>(
+ static_cast<f32>(start_estimated_time - command_buffer.estimated_process_time) +
+ (((time_limit_percent / 100.0f) * 2'880'000.0) *
+ (static_cast<f32>(render_time_limit_percent) / 100.0f)))};
+ num_voices_dropped = DropVoices(command_buffer, start_estimated_time, time_limit);
+ }
+
+ command_list_header->buffer_size = command_buffer.size;
+ command_list_header->command_count = command_buffer.count;
+
+ voice_context.UpdateStateByDspShared();
+
+ if (render_context.behavior->IsEffectInfoVersion2Supported()) {
+ effect_context.UpdateStateByDspShared();
+ }
+
+ const auto end_time{core.CoreTiming().GetClockTicks()};
+ total_ticks_elapsed += end_time - start_time;
+ num_command_lists_generated++;
+ render_start_tick = adsp.GetRenderingStartTick(session_id);
+ frames_elapsed++;
+
+ return command_buffer.size;
+}
+
+u32 System::DropVoices(CommandBuffer& command_buffer, const u32 estimated_process_time,
+ const u32 time_limit) {
+ u32 i{0};
+ auto command_list{command_buffer.command_list.data() + sizeof(CommandListHeader)};
+ ICommand* cmd{};
+
+ for (; i < command_buffer.count; i++) {
+ cmd = reinterpret_cast<ICommand*>(command_list);
+ if (cmd->type != CommandId::Performance &&
+ cmd->type != CommandId::DataSourcePcmInt16Version1 &&
+ cmd->type != CommandId::DataSourcePcmInt16Version2 &&
+ cmd->type != CommandId::DataSourcePcmFloatVersion1 &&
+ cmd->type != CommandId::DataSourcePcmFloatVersion2 &&
+ cmd->type != CommandId::DataSourceAdpcmVersion1 &&
+ cmd->type != CommandId::DataSourceAdpcmVersion2) {
+ break;
+ }
+ command_list += cmd->size;
+ }
+
+ if (cmd == nullptr || command_buffer.count == 0 || i >= command_buffer.count) {
+ return 0;
+ }
+
+ auto voices_dropped{0};
+ while (i < command_buffer.count) {
+ const auto node_id{cmd->node_id};
+ const auto node_id_type{cmd->node_id >> 28};
+ const auto node_id_base{cmd->node_id & 0xFFF};
+
+ if (estimated_process_time <= time_limit) {
+ break;
+ }
+
+ if (node_id_type != 1) {
+ break;
+ }
+
+ auto& voice_info{voice_context.GetInfo(node_id_base)};
+ if (voice_info.priority == HighestVoicePriority) {
+ break;
+ }
+
+ voices_dropped++;
+ voice_info.voice_dropped = true;
+
+ if (i < command_buffer.count) {
+ while (cmd->node_id == node_id) {
+ if (cmd->type == CommandId::DepopPrepare) {
+ cmd->enabled = true;
+ } else if (cmd->type == CommandId::Performance || !cmd->enabled) {
+ cmd->enabled = false;
+ }
+ i++;
+ command_list += cmd->size;
+ cmd = reinterpret_cast<ICommand*>(command_list);
+ }
+ }
+ }
+ return voices_dropped;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system.h b/src/audio_core/renderer/system.h
new file mode 100644
index 000000000..bcbe65b07
--- /dev/null
+++ b/src/audio_core/renderer/system.h
@@ -0,0 +1,307 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <span>
+
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/command/command_processing_time_estimator.h"
+#include "audio_core/renderer/effect/effect_context.h"
+#include "audio_core/renderer/memory/memory_pool_info.h"
+#include "audio_core/renderer/mix/mix_context.h"
+#include "audio_core/renderer/performance/performance_manager.h"
+#include "audio_core/renderer/sink/sink_context.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+#include "audio_core/renderer/upsampler/upsampler_manager.h"
+#include "audio_core/renderer/voice/voice_context.h"
+#include "common/thread.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+namespace Memory {
+class Memory;
+}
+class System;
+} // namespace Core
+
+namespace Kernel {
+class KEvent;
+class KTransferMemory;
+} // namespace Kernel
+
+namespace AudioCore {
+struct AudioRendererParameterInternal;
+
+namespace AudioRenderer {
+class CommandBuffer;
+namespace ADSP {
+class ADSP;
+}
+
+/**
+ * Audio Renderer System, the main worker for audio rendering.
+ */
+class System {
+ enum class State {
+ Started = 0,
+ Stopped = 2,
+ };
+
+public:
+ explicit System(Core::System& core, Kernel::KEvent* adsp_rendered_event);
+
+ /**
+ * Calculate the total size required for all audio render workbuffers.
+ *
+ * @param params - Input parameters with the numbers of voices/mixes/sinks/etc.
+ * @return Size (in bytes) required for the audio renderer.
+ */
+ static u64 GetWorkBufferSize(const AudioRendererParameterInternal& params);
+
+ /**
+ * Initialize the renderer system.
+ * Allocates workbuffers and initializes everything to a default state, ready to receive a
+ * RequestUpdate.
+ *
+ * @param params - Input parameters to initialize the system with.
+ * @param transfer_memory - Game-supplied memory for all workbuffers. Unused.
+ * @param transfer_memory_size - Size of the transfer memory. Unused.
+ * @param process_handle - Process handle, also used for memory. Unused.
+ * @param applet_resource_user_id - Applet id for this renderer. Unused.
+ * @param session_id - Session id of this renderer.
+ * @return Result code.
+ */
+ Result Initialize(const AudioRendererParameterInternal& params,
+ Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
+ u32 process_handle, u64 applet_resource_user_id, s32 session_id);
+
+ /**
+ * Finalize the system.
+ */
+ void Finalize();
+
+ /**
+ * Start the system.
+ */
+ void Start();
+
+ /**
+ * Stop the system.
+ */
+ void Stop();
+
+ /**
+ * Update the system.
+ *
+ * @param input - Inout buffer containing the update data.
+ * @param performance - Optional buffer for writing back performance metrics.
+ * @param output - Output information from rendering.
+ * @return Result code.
+ */
+ Result Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output);
+
+ /**
+ * Get the time limit (percent) for rendering
+ *
+ * @return Time limit as a percent.
+ */
+ u32 GetRenderingTimeLimit() const;
+
+ /**
+ * Set the time limit (percent) for rendering
+ *
+ * @param limit - New time limit.
+ */
+ void SetRenderingTimeLimit(u32 limit);
+
+ /**
+ * Get the session id for this system.
+ *
+ * @return Session id of this system.
+ */
+ u32 GetSessionId() const;
+
+ /**
+ * Get the sample rate of this system.
+ *
+ * @return Sample rate of this system.
+ */
+ u32 GetSampleRate() const;
+
+ /**
+ * Get the sample count of this system.
+ *
+ * @return Sample count of this system.
+ */
+ u32 GetSampleCount() const;
+
+ /**
+ * Get the number of mix buffers for this system.
+ *
+ * @return Number of mix buffers in the system.
+ */
+ u32 GetMixBufferCount() const;
+
+ /**
+ * Get the execution mode of this system.
+ * Note: Only Auto is implemented.
+ *
+ * @return Execution mode for this system.
+ */
+ ExecutionMode GetExecutionMode() const;
+
+ /**
+ * Get the rendering deivce for this system.
+ * This is unused.
+ *
+ * @return Rendering device for this system.
+ */
+ u32 GetRenderingDevice() const;
+
+ /**
+ * Check if this system is currently active.
+ *
+ * @return True if active, otherwise false.
+ */
+ bool IsActive() const;
+
+ /**
+ * Prepare and generate a list of commands for the AudioRenderer based on current state,
+ * signalling the buffer event when all processed.
+ */
+ void SendCommandToDsp();
+
+ /**
+ * Generate a list of commands for the AudioRenderer based on current state.
+ *
+ * @param command_buffer - Buffer for commands to be written to.
+ * @param command_buffer_size - Size of the command_buffer.
+ *
+ * @return Number of bytes written.
+ */
+ u64 GenerateCommand(std::span<u8> command_buffer, u64 command_buffer_size);
+
+ /**
+ * Try to drop some voices if the AudioRenderer fell behind.
+ *
+ * @param command_buffer - Command buffer to drop voices from.
+ * @param estimated_process_time - Current estimated processing time of all commands.
+ * @param time_limit - Time limit for rendering, voices are dropped if estimated
+ * exceeds this.
+ *
+ * @return Number of voices dropped.
+ */
+ u32 DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time, u32 time_limit);
+
+private:
+ /// Core system
+ Core::System& core;
+ /// Reference to the ADSP for communication
+ ADSP::ADSP& adsp;
+ /// Is this system initialized?
+ bool initialized{};
+ /// Is this system currently active?
+ std::atomic<bool> active{};
+ /// State of the system
+ State state{State::Stopped};
+ /// Sample rate for the system
+ u32 sample_rate{};
+ /// Sample count of the system
+ u32 sample_count{};
+ /// Number of mix buffers in use by the system
+ s16 mix_buffer_count{};
+ /// Workbuffer for mix buffers, used by the AudioRenderer
+ std::span<s32> samples_workbuffer{};
+ /// Depop samples for depopping commands
+ std::span<s32> depop_buffer{};
+ /// Number of memory pools in the buffer
+ u32 memory_pool_count{};
+ /// Workbuffer for memory pools
+ std::span<MemoryPoolInfo> memory_pool_workbuffer{};
+ /// System memory pool info
+ MemoryPoolInfo memory_pool_info{};
+ /// Workbuffer that commands will be generated into
+ std::span<u8> command_workbuffer{};
+ /// Size of command workbuffer
+ u64 command_workbuffer_size{};
+ /// Numebr of commands in the workbuffer
+ u64 command_buffer_size{};
+ /// Manager for upsamplers
+ UpsamplerManager* upsampler_manager{};
+ /// Upsampler workbuffer
+ std::span<UpsamplerInfo> upsampler_infos{};
+ /// Number of upsamplers in the workbuffer
+ u32 upsampler_count{};
+ /// Holds and controls all voices
+ VoiceContext voice_context{};
+ /// Holds and controls all mixes
+ MixContext mix_context{};
+ /// Holds and controls all effects
+ EffectContext effect_context{};
+ /// Holds and controls all sinks
+ SinkContext sink_context{};
+ /// Holds and controls all splitters
+ SplitterContext splitter_context{};
+ /// Estimates the time taken for each command
+ std::unique_ptr<ICommandProcessingTimeEstimator> command_processing_time_estimator{};
+ /// Session id of this system
+ s32 session_id{};
+ /// Number of channels in use by voices
+ s32 voice_channels{};
+ /// Event to be called when the AudioRenderer processes a command list
+ Kernel::KEvent* adsp_rendered_event{};
+ /// Event signalled on system terminate
+ Common::Event terminate_event{};
+ /// Does what locks do
+ std::mutex lock{};
+ /// Handle for the process for this system, unused
+ u32 process_handle{};
+ /// Applet resource id for this system, unused
+ u64 applet_resource_user_id{};
+ /// Controls performance input and output
+ PerformanceManager performance_manager{};
+ /// Workbuffer for performance metrics
+ std::span<u8> performance_workbuffer{};
+ /// Main workbuffer, from which all other workbuffers here allocate into
+ std::unique_ptr<u8[]> workbuffer{};
+ /// Size of the main workbuffer
+ u64 workbuffer_size{};
+ /// Unknown buffer/marker
+ std::span<u8> unk_2A8{};
+ /// Size of the above unknown buffer/marker
+ u64 unk_2B0{};
+ /// Rendering time limit (percent)
+ u32 render_time_limit_percent{};
+ /// Should any voices be dropped?
+ bool drop_voice{};
+ /// Should the backend stream have its buffers flushed?
+ bool reset_command_buffers{};
+ /// Execution mode of this system, only Auto is supported
+ ExecutionMode execution_mode{ExecutionMode::Auto};
+ /// Render device, unused
+ u32 render_device{};
+ /// Behaviour to check which features are supported by the user revision
+ BehaviorInfo behavior{};
+ /// Total ticks the audio system has been running
+ u64 total_ticks_elapsed{};
+ /// Ticks the system has spent in updates
+ u64 ticks_spent_updating{};
+ /// Number of times a command list was generated
+ u64 num_command_lists_generated{};
+ /// Number of times the system has updated
+ u64 num_times_updated{};
+ /// Number of frames generated, written back to the game
+ std::atomic<u64> frames_elapsed{};
+ /// Is the AudioRenderer running too slow?
+ bool adsp_behind{};
+ /// Number of voices dropped
+ u32 num_voices_dropped{};
+ /// Tick that rendering started
+ u64 render_start_tick{};
+};
+
+} // namespace AudioRenderer
+} // namespace AudioCore
diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp
new file mode 100644
index 000000000..b326819ed
--- /dev/null
+++ b/src/audio_core/renderer/system_manager.cpp
@@ -0,0 +1,162 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <chrono>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/renderer/adsp/adsp.h"
+#include "audio_core/renderer/system_manager.h"
+#include "common/microprofile.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+
+MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
+ MP_RGB(60, 19, 97));
+
+namespace AudioCore::AudioRenderer {
+constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL};
+constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL};
+
+SystemManager::SystemManager(Core::System& core_)
+ : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
+ thread_event{Core::Timing::CreateEvent(
+ "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
+ return ThreadFunc2(time);
+ })} {
+ core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); });
+}
+
+SystemManager::~SystemManager() {
+ Stop();
+}
+
+bool SystemManager::InitializeUnsafe() {
+ if (!active) {
+ if (adsp.Start()) {
+ active = true;
+ thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
+ core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0),
+ BaseRenderTime - RenderTimeOffset, thread_event);
+ }
+ }
+
+ return adsp.GetState() == ADSP::State::Started;
+}
+
+void SystemManager::Stop() {
+ if (!active) {
+ return;
+ }
+ core.CoreTiming().UnscheduleEvent(thread_event, {});
+ active = false;
+ update.store(true);
+ update.notify_all();
+ thread.join();
+ adsp.Stop();
+}
+
+bool SystemManager::Add(System& system_) {
+ std::scoped_lock l2{mutex2};
+
+ if (systems.size() + 1 > MaxRendererSessions) {
+ LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!");
+ return false;
+ }
+
+ {
+ std::scoped_lock l{mutex1};
+ if (systems.empty()) {
+ if (!InitializeUnsafe()) {
+ LOG_ERROR(Service_Audio, "Failed to start the AudioRenderer SystemManager");
+ return false;
+ }
+ }
+ }
+
+ systems.push_back(&system_);
+ return true;
+}
+
+bool SystemManager::Remove(System& system_) {
+ std::scoped_lock l2{mutex2};
+
+ {
+ std::scoped_lock l{mutex1};
+ if (systems.remove(&system_) == 0) {
+ LOG_ERROR(Service_Audio,
+ "Failed to remove a render system, it was not found in the list!");
+ return false;
+ }
+ }
+
+ if (systems.empty()) {
+ Stop();
+ }
+ return true;
+}
+
+void SystemManager::ThreadFunc() {
+ constexpr char name[]{"yuzu:AudioRenderSystemManager"};
+ MicroProfileOnThreadCreate(name);
+ Common::SetCurrentThreadName(name);
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
+ while (active) {
+ {
+ std::scoped_lock l{mutex1};
+
+ MICROPROFILE_SCOPE(Audio_RenderSystemManager);
+
+ for (auto system : systems) {
+ system->SendCommandToDsp();
+ }
+ }
+
+ adsp.Signal();
+ adsp.Wait();
+
+ update.wait(false);
+ update.store(false);
+ }
+}
+
+std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
+ std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt};
+ const auto queue_size{core.AudioCore().GetStreamQueue()};
+ switch (state) {
+ case StreamState::Filling:
+ if (queue_size >= 5) {
+ new_schedule_time = BaseRenderTime;
+ state = StreamState::Steady;
+ }
+ break;
+ case StreamState::Steady:
+ if (queue_size <= 2) {
+ new_schedule_time = BaseRenderTime - RenderTimeOffset;
+ state = StreamState::Filling;
+ } else if (queue_size > 5) {
+ new_schedule_time = BaseRenderTime + RenderTimeOffset;
+ state = StreamState::Draining;
+ }
+ break;
+ case StreamState::Draining:
+ if (queue_size <= 5) {
+ new_schedule_time = BaseRenderTime;
+ state = StreamState::Steady;
+ }
+ break;
+ }
+
+ update.store(true);
+ update.notify_all();
+ return new_schedule_time;
+}
+
+void SystemManager::PauseCallback(bool paused) {
+ if (paused && core.IsPoweredOn() && core.IsShuttingDown()) {
+ update.store(true);
+ update.notify_all();
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h
new file mode 100644
index 000000000..1291e9e0e
--- /dev/null
+++ b/src/audio_core/renderer/system_manager.h
@@ -0,0 +1,113 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <list>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <thread>
+
+#include "audio_core/renderer/system.h"
+
+namespace Core {
+namespace Timing {
+struct EventType;
+}
+class System;
+} // namespace Core
+
+namespace AudioCore::AudioRenderer {
+namespace ADSP {
+class ADSP;
+class AudioRenderer_Mailbox;
+} // namespace ADSP
+
+/**
+ * Manages all audio renderers, responsible for triggering command list generation and signalling
+ * the ADSP.
+ */
+class SystemManager {
+public:
+ explicit SystemManager(Core::System& core);
+ ~SystemManager();
+
+ /**
+ * Initialize the system manager, called when any system is registered.
+ *
+ * @return True if sucessfully initialized, otherwise false.
+ */
+ bool InitializeUnsafe();
+
+ /**
+ * Stop the system manager.
+ */
+ void Stop();
+
+ /**
+ * Add an audio render system to the manager.
+ * The manager does not own the system, so do not free it without calling Remove.
+ *
+ * @param system - The system to add.
+ * @return True if succesfully added, otherwise false.
+ */
+ bool Add(System& system);
+
+ /**
+ * Remove an audio render system from the manager.
+ *
+ * @param system - The system to remove.
+ * @return True if succesfully removed, otherwise false.
+ */
+ bool Remove(System& system);
+
+private:
+ /**
+ * Main thread responsible for command generation.
+ */
+ void ThreadFunc();
+
+ /**
+ * Signalling core timing thread to run ThreadFunc.
+ */
+ std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
+
+ /**
+ * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc.
+ *
+ * @param paused - Are we pausing or resuming?
+ */
+ void PauseCallback(bool paused);
+
+ enum class StreamState {
+ Filling,
+ Steady,
+ Draining,
+ };
+
+ /// Core system
+ Core::System& core;
+ /// List of pointers to managed systems
+ std::list<System*> systems{};
+ /// Main worker thread for generating command lists
+ std::jthread thread;
+ /// Mutex for the systems
+ std::mutex mutex1{};
+ /// Mutex for adding/removing systems
+ std::mutex mutex2{};
+ /// Is the system manager thread active?
+ std::atomic<bool> active{};
+ /// Reference to the ADSP for communication
+ ADSP::ADSP& adsp;
+ /// AudioRenderer mailbox for communication
+ ADSP::AudioRenderer_Mailbox* mailbox{};
+ /// Core timing event to signal main thread
+ std::shared_ptr<Core::Timing::EventType> thread_event;
+ /// Atomic for main thread to wait on
+ std::atomic<bool> update{};
+ /// Current state of the streams
+ StreamState state{StreamState::Filling};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_info.cpp b/src/audio_core/renderer/upsampler/upsampler_info.cpp
new file mode 100644
index 000000000..e3d2f7db0
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_info.cpp
@@ -0,0 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/upsampler/upsampler_info.h"
+
+namespace AudioCore::AudioRenderer {} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_info.h b/src/audio_core/renderer/upsampler/upsampler_info.h
new file mode 100644
index 000000000..a43c15af3
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_info.h
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "audio_core/renderer/upsampler/upsampler_state.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class UpsamplerManager;
+
+/**
+ * Manages information needed to upsample a mix buffer.
+ */
+struct UpsamplerInfo {
+ /// States used by the AudioRenderer across calls.
+ std::array<UpsamplerState, MaxChannels> states{};
+ /// Pointer to the manager
+ UpsamplerManager* manager{};
+ /// Pointer to the samples to be upsampled
+ CpuAddr samples_pos{};
+ /// Target number of samples to upsample to
+ u32 sample_count{};
+ /// Number of channels to upsample
+ u32 input_count{};
+ /// Is this upsampler enabled?
+ bool enabled{};
+ /// Mix buffer indexes to be upsampled
+ std::array<s16, MaxChannels> inputs{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.cpp b/src/audio_core/renderer/upsampler/upsampler_manager.cpp
new file mode 100644
index 000000000..4c76a5066
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_manager.cpp
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/upsampler/upsampler_manager.h"
+
+namespace AudioCore::AudioRenderer {
+
+UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_,
+ std::span<s32> workbuffer_)
+ : count{count_}, upsampler_infos{infos_}, workbuffer{workbuffer_} {}
+
+UpsamplerInfo* UpsamplerManager::Allocate() {
+ std::scoped_lock l{lock};
+
+ if (count == 0) {
+ return nullptr;
+ }
+
+ u32 free_index{0};
+ for (auto& upsampler : upsampler_infos) {
+ if (!upsampler.enabled) {
+ break;
+ }
+ free_index++;
+ }
+
+ if (free_index >= count) {
+ return nullptr;
+ }
+
+ auto& upsampler{upsampler_infos[free_index]};
+ upsampler.manager = this;
+ upsampler.sample_count = TargetSampleCount;
+ upsampler.samples_pos = CpuAddr(&workbuffer[upsampler.sample_count * MaxChannels]);
+ upsampler.enabled = true;
+ return &upsampler;
+}
+
+void UpsamplerManager::Free(UpsamplerInfo* info) {
+ std::scoped_lock l{lock};
+ info->enabled = false;
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h
new file mode 100644
index 000000000..70cd42b08
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_manager.h
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+#include <span>
+
+#include "audio_core/renderer/upsampler/upsampler_info.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Manages and has utility functions for upsampler infos.
+ */
+class UpsamplerManager {
+public:
+ UpsamplerManager(u32 count, std::span<UpsamplerInfo> infos, std::span<s32> workbuffer);
+
+ /**
+ * Allocate a new UpsamplerInfo.
+ *
+ * @return The allocated upsampler, may be nullptr if alloc failed.
+ */
+ UpsamplerInfo* Allocate();
+
+ /**
+ * Free the given upsampler.
+ *
+ * @param The upsampler to be freed.
+ */
+ void Free(UpsamplerInfo* info);
+
+private:
+ /// Maximum number of upsamplers in the buffer
+ const u32 count;
+ /// Upsamplers buffer
+ std::span<UpsamplerInfo> upsampler_infos;
+ /// Workbuffer for upsampling samples
+ std::span<s32> workbuffer;
+ /// Lock for allocate/free
+ std::mutex lock{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_state.h b/src/audio_core/renderer/upsampler/upsampler_state.h
new file mode 100644
index 000000000..28cebe200
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_state.h
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Upsampling state used by the AudioRenderer across calls.
+ */
+struct UpsamplerState {
+ static constexpr u16 HistorySize = 20;
+
+ /// Source data to target data ratio. E.g 48'000/32'000 = 1.5
+ Common::FixedPoint<16, 16> ratio;
+ /// Sample history
+ std::array<Common::FixedPoint<24, 8>, HistorySize> history;
+ /// Size of the sinc coefficient window
+ u16 window_size;
+ /// Read index for the history
+ u16 history_output_index;
+ /// Write index for the history
+ u16 history_input_index;
+ /// Start offset within the history, fixed to 0
+ u16 history_start_index;
+ /// Ebd offset within the history, fixed to HistorySize
+ u16 history_end_index;
+ /// Is this state initialized?
+ bool initialized;
+ /// Index of the current sample.
+ /// E.g 16K -> 48K has a ratio of 3, so this will be 0-2.
+ /// See the Upsample command in the AudioRenderer for more information.
+ u8 sample_index;
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_channel_resource.h b/src/audio_core/renderer/voice/voice_channel_resource.h
new file mode 100644
index 000000000..26ab4ccce
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_channel_resource.h
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Represents one channel for mixing a voice.
+ */
+class VoiceChannelResource {
+public:
+ struct InParameter {
+ /* 0x00 */ u32 id;
+ /* 0x04 */ std::array<f32, MaxMixBuffers> mix_volumes;
+ /* 0x64 */ bool in_use;
+ /* 0x65 */ char unk65[0xB];
+ };
+ static_assert(sizeof(InParameter) == 0x70,
+ "VoiceChannelResource::InParameter has the wrong size!");
+
+ explicit VoiceChannelResource(u32 id_) : id{id_} {}
+
+ /// Current volume for each mix buffer
+ std::array<f32, MaxMixBuffers> mix_volumes{};
+ /// Previous volume for each mix buffer
+ std::array<f32, MaxMixBuffers> prev_mix_volumes{};
+ /// Id of this resource
+ const u32 id;
+ /// Is this resource in use?
+ bool in_use{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_context.cpp b/src/audio_core/renderer/voice/voice_context.cpp
new file mode 100644
index 000000000..eafb51b01
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_context.cpp
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <ranges>
+
+#include "audio_core/renderer/voice/voice_context.h"
+
+namespace AudioCore::AudioRenderer {
+
+VoiceState& VoiceContext::GetDspSharedState(const u32 index) {
+ if (index >= dsp_states.size()) {
+ LOG_ERROR(Service_Audio, "Invalid voice dsp state index {:04X}", index);
+ }
+ return dsp_states[index];
+}
+
+VoiceChannelResource& VoiceContext::GetChannelResource(const u32 index) {
+ if (index >= channel_resources.size()) {
+ LOG_ERROR(Service_Audio, "Invalid voice channel resource index {:04X}", index);
+ }
+ return channel_resources[index];
+}
+
+void VoiceContext::Initialize(std::span<VoiceInfo*> sorted_voice_infos_,
+ std::span<VoiceInfo> voice_infos_,
+ std::span<VoiceChannelResource> voice_channel_resources_,
+ std::span<VoiceState> cpu_states_, std::span<VoiceState> dsp_states_,
+ const u32 voice_count_) {
+ sorted_voice_info = sorted_voice_infos_;
+ voices = voice_infos_;
+ channel_resources = voice_channel_resources_;
+ cpu_states = cpu_states_;
+ dsp_states = dsp_states_;
+ voice_count = voice_count_;
+ active_count = 0;
+}
+
+VoiceInfo* VoiceContext::GetSortedInfo(const u32 index) {
+ if (index >= sorted_voice_info.size()) {
+ LOG_ERROR(Service_Audio, "Invalid voice sorted info index {:04X}", index);
+ }
+ return sorted_voice_info[index];
+}
+
+VoiceInfo& VoiceContext::GetInfo(const u32 index) {
+ if (index >= voices.size()) {
+ LOG_ERROR(Service_Audio, "Invalid voice info index {:04X}", index);
+ }
+ return voices[index];
+}
+
+VoiceState& VoiceContext::GetState(const u32 index) {
+ if (index >= cpu_states.size()) {
+ LOG_ERROR(Service_Audio, "Invalid voice cpu state index {:04X}", index);
+ }
+ return cpu_states[index];
+}
+
+u32 VoiceContext::GetCount() const {
+ return voice_count;
+}
+
+u32 VoiceContext::GetActiveCount() const {
+ return active_count;
+}
+
+void VoiceContext::SetActiveCount(const u32 active_count_) {
+ active_count = active_count_;
+}
+
+void VoiceContext::SortInfo() {
+ for (u32 i = 0; i < voice_count; i++) {
+ sorted_voice_info[i] = &voices[i];
+ }
+
+ std::ranges::sort(sorted_voice_info, [](const VoiceInfo* a, const VoiceInfo* b) {
+ return a->priority != b->priority ? a->priority < b->priority
+ : a->sort_order < b->sort_order;
+ });
+}
+
+void VoiceContext::UpdateStateByDspShared() {
+ std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState));
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_context.h b/src/audio_core/renderer/voice/voice_context.h
new file mode 100644
index 000000000..43b677154
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_context.h
@@ -0,0 +1,126 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/renderer/voice/voice_channel_resource.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "audio_core/renderer/voice/voice_state.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Contains all voices, with utility functions for managing them.
+ */
+class VoiceContext {
+public:
+ /**
+ * Get the AudioRenderer state for a given index
+ *
+ * @param index - State index to get.
+ * @return The requested voice state.
+ */
+ VoiceState& GetDspSharedState(u32 index);
+
+ /**
+ * Get the channel resource for a given index
+ *
+ * @param index - Resource index to get.
+ * @return The requested voice resource.
+ */
+ VoiceChannelResource& GetChannelResource(u32 index);
+
+ /**
+ * Initialize the voice context.
+ *
+ * @param sorted_voice_infos - Workbuffer for the sorted voices.
+ * @param voice_infos - Workbuffer for the voices.
+ * @param voice_channel_resources - Workbuffer for the voice channel resources.
+ * @param cpu_states - Workbuffer for the host-side voice states.
+ * @param dsp_states - Workbuffer for the AudioRenderer-side voice states.
+ * @param voice_count - The number of voices in each workbuffer.
+ */
+ void Initialize(std::span<VoiceInfo*> sorted_voice_infos, std::span<VoiceInfo> voice_infos,
+ std::span<VoiceChannelResource> voice_channel_resources,
+ std::span<VoiceState> cpu_states, std::span<VoiceState> dsp_states,
+ u32 voice_count);
+
+ /**
+ * Get a sorted voice with the given index.
+ *
+ * @param index - The sorted voice index to get.
+ * @return The sorted voice.
+ */
+ VoiceInfo* GetSortedInfo(u32 index);
+
+ /**
+ * Get a voice with the given index.
+ *
+ * @param index - The voice index to get.
+ * @return The voice.
+ */
+ VoiceInfo& GetInfo(u32 index);
+
+ /**
+ * Get a host voice state with the given index.
+ *
+ * @param index - The host voice state index to get.
+ * @return The voice state.
+ */
+ VoiceState& GetState(u32 index);
+
+ /**
+ * Get the maximum number of voices.
+ * Not all voices in the buffers may be in use, see GetActiveCount.
+ *
+ * @return The maximum number of voices.
+ */
+ u32 GetCount() const;
+
+ /**
+ * Get the number of active voices.
+ * Can be less than or equal to the maximum number of voices.
+ *
+ * @return The number of active voices.
+ */
+ u32 GetActiveCount() const;
+
+ /**
+ * Set the number of active voices.
+ * Can be less than or equal to the maximum number of voices.
+ *
+ * @param active_count - The new number of active voices.
+ */
+ void SetActiveCount(u32 active_count);
+
+ /**
+ * Sort all voices. Results are available via GetSortedInfo.
+ * Voices are sorted descendingly, according to priority, and then sort order.
+ */
+ void SortInfo();
+
+ /**
+ * Update all voice states, copying AudioRenderer-side states to host-side states.
+ */
+ void UpdateStateByDspShared();
+
+private:
+ /// Sorted voices
+ std::span<VoiceInfo*> sorted_voice_info{};
+ /// Voices
+ std::span<VoiceInfo> voices{};
+ /// Channel resources
+ std::span<VoiceChannelResource> channel_resources{};
+ /// Host-side voice states
+ std::span<VoiceState> cpu_states{};
+ /// AudioRenderer-side voice states
+ std::span<VoiceState> dsp_states{};
+ /// Maximum number of voices
+ u32 voice_count{};
+ /// Number of active voices
+ u32 active_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_info.cpp b/src/audio_core/renderer/voice/voice_info.cpp
new file mode 100644
index 000000000..1849eeb57
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_info.cpp
@@ -0,0 +1,408 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/renderer/memory/pool_mapper.h"
+#include "audio_core/renderer/voice/voice_context.h"
+#include "audio_core/renderer/voice/voice_info.h"
+#include "audio_core/renderer/voice/voice_state.h"
+
+namespace AudioCore::AudioRenderer {
+
+VoiceInfo::VoiceInfo() {
+ Initialize();
+}
+
+void VoiceInfo::Initialize() {
+ in_use = false;
+ is_new = false;
+ id = 0;
+ node_id = 0;
+ current_play_state = ServerPlayState::Stopped;
+ src_quality = SrcQuality::Medium;
+ priority = LowestVoicePriority;
+ sample_format = SampleFormat::Invalid;
+ sample_rate = 0;
+ channel_count = 0;
+ wave_buffer_count = 0;
+ wave_buffer_index = 0;
+ pitch = 0.0f;
+ volume = 0.0f;
+ prev_volume = 0.0f;
+ mix_id = UnusedMixId;
+ splitter_id = UnusedSplitterId;
+ biquads = {};
+ biquad_initialized = {};
+ voice_dropped = false;
+ data_unmapped = false;
+ buffer_unmapped = false;
+ flush_buffer_count = 0;
+
+ data_address.Setup(0, 0);
+ for (auto& wavebuffer : wavebuffers) {
+ wavebuffer.Initialize();
+ }
+}
+
+bool VoiceInfo::ShouldUpdateParameters(const InParameter& params) const {
+ return data_address.GetCpuAddr() != params.src_data_address ||
+ data_address.GetSize() != params.src_data_size || data_unmapped;
+}
+
+void VoiceInfo::UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
+ const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
+ in_use = params.in_use;
+ id = params.id;
+ node_id = params.node_id;
+ UpdatePlayState(params.play_state);
+ UpdateSrcQuality(params.src_quality);
+ priority = params.priority;
+ sort_order = params.sort_order;
+ sample_rate = params.sample_rate;
+ sample_format = params.sample_format;
+ channel_count = static_cast<s8>(params.channel_count);
+ pitch = params.pitch;
+ volume = params.volume;
+ biquads = params.biquads;
+ wave_buffer_count = params.wave_buffer_count;
+ wave_buffer_index = params.wave_buffer_index;
+
+ if (behavior.IsFlushVoiceWaveBuffersSupported()) {
+ flush_buffer_count += params.flush_buffer_count;
+ }
+
+ mix_id = params.mix_id;
+
+ if (behavior.IsSplitterSupported()) {
+ splitter_id = params.splitter_id;
+ } else {
+ splitter_id = UnusedSplitterId;
+ }
+
+ channel_resource_ids = params.channel_resource_ids;
+
+ flags &= u16(~0b11);
+ if (behavior.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
+ flags |= u16(params.flags.IsVoicePlayedSampleCountResetAtLoopPointSupported);
+ }
+
+ if (behavior.IsVoicePitchAndSrcSkippedSupported()) {
+ flags |= u16(params.flags.IsVoicePitchAndSrcSkippedSupported);
+ }
+
+ if (params.clear_voice_drop) {
+ voice_dropped = false;
+ }
+
+ if (ShouldUpdateParameters(params)) {
+ data_unmapped = !pool_mapper.TryAttachBuffer(error_info, data_address,
+ params.src_data_address, params.src_data_size);
+ } else {
+ error_info.error_code = ResultSuccess;
+ error_info.address = CpuAddr(0);
+ }
+}
+
+void VoiceInfo::UpdatePlayState(const PlayState state) {
+ last_play_state = current_play_state;
+
+ switch (state) {
+ case PlayState::Started:
+ current_play_state = ServerPlayState::Started;
+ break;
+ case PlayState::Stopped:
+ if (current_play_state != ServerPlayState::Stopped) {
+ current_play_state = ServerPlayState::RequestStop;
+ }
+ break;
+ case PlayState::Paused:
+ current_play_state = ServerPlayState::Paused;
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input play state {}", static_cast<u32>(state));
+ break;
+ }
+}
+
+void VoiceInfo::UpdateSrcQuality(const SrcQuality quality) {
+ switch (quality) {
+ case SrcQuality::Medium:
+ src_quality = quality;
+ break;
+ case SrcQuality::High:
+ src_quality = quality;
+ break;
+ case SrcQuality::Low:
+ src_quality = quality;
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid input src quality {}", static_cast<u32>(quality));
+ break;
+ }
+}
+
+void VoiceInfo::UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
+ [[maybe_unused]] u32 error_count, const InParameter& params,
+ std::span<VoiceState*> voice_states,
+ const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
+ if (params.is_new) {
+ for (size_t i = 0; i < wavebuffers.size(); i++) {
+ wavebuffers[i].Initialize();
+ }
+
+ for (s8 channel = 0; channel < static_cast<s8>(params.channel_count); channel++) {
+ voice_states[channel]->wave_buffer_valid.fill(false);
+ }
+ }
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ UpdateWaveBuffer(error_infos[i], wavebuffers[i], params.wave_buffer_internal[i],
+ params.sample_format, voice_states[0]->wave_buffer_valid[i], pool_mapper,
+ behavior);
+ }
+}
+
+void VoiceInfo::UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info,
+ WaveBuffer& wave_buffer,
+ const WaveBufferInternal& wave_buffer_internal,
+ const SampleFormat sample_format_, const bool valid,
+ const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
+ if (!valid && wave_buffer.sent_to_DSP && wave_buffer.buffer_address.GetCpuAddr() != 0) {
+ pool_mapper.ForceUnmapPointer(wave_buffer.buffer_address);
+ wave_buffer.buffer_address.Setup(0, 0);
+ }
+
+ if (!ShouldUpdateWaveBuffer(wave_buffer_internal)) {
+ return;
+ }
+
+ switch (sample_format_) {
+ case SampleFormat::PcmInt16: {
+ constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmInt16)};
+ if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
+ wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
+ LOG_ERROR(Service_Audio, "Invalid PCM16 start/end wavebuffer sizes!");
+ error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
+ error_info[0].address = wave_buffer_internal.address;
+ return;
+ }
+ } break;
+
+ case SampleFormat::PcmFloat: {
+ constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmFloat)};
+ if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
+ wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
+ LOG_ERROR(Service_Audio, "Invalid PCMFloat start/end wavebuffer sizes!");
+ error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
+ error_info[0].address = wave_buffer_internal.address;
+ return;
+ }
+ } break;
+
+ case SampleFormat::Adpcm: {
+ const auto start_frame{wave_buffer_internal.start_offset / 14};
+ auto start_extra{wave_buffer_internal.start_offset % 14 == 0
+ ? 0
+ : (wave_buffer_internal.start_offset % 14) / 2 + 1 +
+ ((wave_buffer_internal.start_offset % 14) % 2)};
+ const auto start{start_frame * 8 + start_extra};
+
+ const auto end_frame{wave_buffer_internal.end_offset / 14};
+ const auto end_extra{wave_buffer_internal.end_offset % 14 == 0
+ ? 0
+ : (wave_buffer_internal.end_offset % 14) / 2 + 1 +
+ ((wave_buffer_internal.end_offset % 14) % 2)};
+ const auto end{end_frame * 8 + end_extra};
+
+ if (start > static_cast<s64>(wave_buffer_internal.size) ||
+ end > static_cast<s64>(wave_buffer_internal.size)) {
+ LOG_ERROR(Service_Audio, "Invalid ADPCM start/end wavebuffer sizes!");
+ error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
+ error_info[0].address = wave_buffer_internal.address;
+ return;
+ }
+ } break;
+
+ default:
+ break;
+ }
+
+ if (wave_buffer_internal.start_offset < 0 || wave_buffer_internal.end_offset < 0) {
+ LOG_ERROR(Service_Audio, "Invalid input start/end wavebuffer sizes!");
+ error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
+ error_info[0].address = wave_buffer_internal.address;
+ return;
+ }
+
+ wave_buffer.start_offset = wave_buffer_internal.start_offset;
+ wave_buffer.end_offset = wave_buffer_internal.end_offset;
+ wave_buffer.loop = wave_buffer_internal.loop;
+ wave_buffer.stream_ended = wave_buffer_internal.stream_ended;
+ wave_buffer.sent_to_DSP = false;
+ wave_buffer.loop_start_offset = wave_buffer_internal.loop_start;
+ wave_buffer.loop_end_offset = wave_buffer_internal.loop_end;
+ wave_buffer.loop_count = wave_buffer_internal.loop_count;
+
+ buffer_unmapped =
+ !pool_mapper.TryAttachBuffer(error_info[0], wave_buffer.buffer_address,
+ wave_buffer_internal.address, wave_buffer_internal.size);
+
+ if (sample_format_ == SampleFormat::Adpcm && behavior.IsAdpcmLoopContextBugFixed() &&
+ wave_buffer_internal.context_address != 0) {
+ buffer_unmapped = !pool_mapper.TryAttachBuffer(error_info[1], wave_buffer.context_address,
+ wave_buffer_internal.context_address,
+ wave_buffer_internal.context_size) ||
+ data_unmapped;
+ } else {
+ wave_buffer.context_address.Setup(0, 0);
+ }
+}
+
+bool VoiceInfo::ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const {
+ return !wave_buffer_internal.sent_to_DSP || buffer_unmapped;
+}
+
+void VoiceInfo::WriteOutStatus(OutStatus& out_status, const InParameter& params,
+ std::span<VoiceState*> voice_states) {
+ if (params.is_new) {
+ is_new = true;
+ }
+
+ if (params.is_new || is_new) {
+ out_status.played_sample_count = 0;
+ out_status.wave_buffers_consumed = 0;
+ out_status.voice_dropped = false;
+ } else {
+ out_status.played_sample_count = voice_states[0]->played_sample_count;
+ out_status.wave_buffers_consumed = voice_states[0]->wave_buffers_consumed;
+ out_status.voice_dropped = voice_dropped;
+ }
+}
+
+bool VoiceInfo::ShouldSkip() const {
+ return !in_use || wave_buffer_count == 0 || data_unmapped || buffer_unmapped || voice_dropped;
+}
+
+bool VoiceInfo::HasAnyConnection() const {
+ return mix_id != UnusedMixId || splitter_id != UnusedSplitterId;
+}
+
+void VoiceInfo::FlushWaveBuffers(const u32 flush_count, std::span<VoiceState*> voice_states,
+ const s8 channel_count_) {
+ auto wave_index{wave_buffer_index};
+
+ for (size_t i = 0; i < flush_count; i++) {
+ wavebuffers[wave_index].sent_to_DSP = true;
+
+ for (s8 j = 0; j < channel_count_; j++) {
+ auto voice_state{voice_states[j]};
+ if (voice_state->wave_buffer_index == wave_index) {
+ voice_state->wave_buffer_index =
+ (voice_state->wave_buffer_index + 1) % MaxWaveBuffers;
+ voice_state->wave_buffers_consumed++;
+ }
+ voice_state->wave_buffer_valid[wave_index] = false;
+ }
+
+ wave_index = (wave_index + 1) % MaxWaveBuffers;
+ }
+}
+
+bool VoiceInfo::UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states) {
+ if (flush_buffer_count > 0) {
+ FlushWaveBuffers(flush_buffer_count, voice_states, channel_count);
+ flush_buffer_count = 0;
+ }
+
+ switch (current_play_state) {
+ case ServerPlayState::Started:
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ if (!wavebuffers[i].sent_to_DSP) {
+ for (s8 channel = 0; channel < channel_count; channel++) {
+ voice_states[channel]->wave_buffer_valid[i] = true;
+ }
+ wavebuffers[i].sent_to_DSP = true;
+ }
+ }
+
+ was_playing = false;
+
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ if (voice_states[0]->wave_buffer_valid[i]) {
+ return true;
+ }
+ }
+ break;
+
+ case ServerPlayState::Stopped:
+ case ServerPlayState::Paused:
+ for (auto& wavebuffer : wavebuffers) {
+ if (!wavebuffer.sent_to_DSP) {
+ wavebuffer.buffer_address.GetReference(true);
+ wavebuffer.context_address.GetReference(true);
+ }
+ }
+
+ if (sample_format == SampleFormat::Adpcm && data_address.GetCpuAddr() != 0) {
+ data_address.GetReference(true);
+ }
+
+ was_playing = last_play_state == ServerPlayState::Started;
+ break;
+
+ case ServerPlayState::RequestStop:
+ for (u32 i = 0; i < MaxWaveBuffers; i++) {
+ wavebuffers[i].sent_to_DSP = true;
+
+ for (s8 channel = 0; channel < channel_count; channel++) {
+ if (voice_states[channel]->wave_buffer_valid[i]) {
+ voice_states[channel]->wave_buffer_index =
+ (voice_states[channel]->wave_buffer_index + 1) % MaxWaveBuffers;
+ voice_states[channel]->wave_buffers_consumed++;
+ }
+ voice_states[channel]->wave_buffer_valid[i] = false;
+ }
+ }
+
+ for (s8 channel = 0; channel < channel_count; channel++) {
+ voice_states[channel]->offset = 0;
+ voice_states[channel]->played_sample_count = 0;
+ voice_states[channel]->adpcm_context = {};
+ voice_states[channel]->sample_history.fill(0);
+ voice_states[channel]->fraction = 0;
+ }
+
+ current_play_state = ServerPlayState::Stopped;
+ was_playing = last_play_state == ServerPlayState::Started;
+ break;
+ }
+
+ return was_playing;
+}
+
+bool VoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
+ std::array<VoiceState*, MaxChannels> voice_states{};
+
+ if (is_new) {
+ ResetResources(voice_context);
+ prev_volume = volume;
+ is_new = false;
+ }
+
+ for (s8 channel = 0; channel < channel_count; channel++) {
+ voice_states[channel] = &voice_context.GetDspSharedState(channel_resource_ids[channel]);
+ }
+
+ return UpdateParametersForCommandGeneration(voice_states);
+}
+
+void VoiceInfo::ResetResources(VoiceContext& voice_context) const {
+ for (s8 channel = 0; channel < channel_count; channel++) {
+ auto& state{voice_context.GetDspSharedState(channel_resource_ids[channel])};
+ state = {};
+
+ auto& channel_resource{voice_context.GetChannelResource(channel_resource_ids[channel])};
+ channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
+ }
+}
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h
new file mode 100644
index 000000000..896723e0c
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_info.h
@@ -0,0 +1,378 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <bitset>
+
+#include "audio_core/common/common.h"
+#include "audio_core/common/wave_buffer.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/memory/address_info.h"
+#include "common/common_types.h"
+
+namespace AudioCore::AudioRenderer {
+class PoolMapper;
+class VoiceContext;
+struct VoiceState;
+
+/**
+ * Represents one voice. Voices are essentially noises, and they can be further mixed and have
+ * effects applied to them, but voices are the basis of all sounds.
+ */
+class VoiceInfo {
+public:
+ enum class ServerPlayState {
+ Started,
+ Stopped,
+ RequestStop,
+ Paused,
+ };
+
+ struct Flags {
+ u8 IsVoicePlayedSampleCountResetAtLoopPointSupported : 1;
+ u8 IsVoicePitchAndSrcSkippedSupported : 1;
+ };
+
+ /**
+ * A wavebuffer contains information on the data source buffers.
+ */
+ struct WaveBuffer {
+ void Copy(WaveBufferVersion1& other) {
+ other.buffer = buffer_address.GetReference(true);
+ other.buffer_size = buffer_address.GetSize();
+ other.start_offset = start_offset;
+ other.end_offset = end_offset;
+ other.loop = loop;
+ other.stream_ended = stream_ended;
+
+ if (context_address.GetCpuAddr()) {
+ other.context = context_address.GetReference(true);
+ other.context_size = context_address.GetSize();
+ } else {
+ other.context = CpuAddr(0);
+ other.context_size = 0;
+ }
+ }
+
+ void Copy(WaveBufferVersion2& other) {
+ other.buffer = buffer_address.GetReference(true);
+ other.buffer_size = buffer_address.GetSize();
+ other.start_offset = start_offset;
+ other.end_offset = end_offset;
+ other.loop_start_offset = loop_start_offset;
+ other.loop_end_offset = loop_end_offset;
+ other.loop = loop;
+ other.loop_count = loop_count;
+ other.stream_ended = stream_ended;
+
+ if (context_address.GetCpuAddr()) {
+ other.context = context_address.GetReference(true);
+ other.context_size = context_address.GetSize();
+ } else {
+ other.context = CpuAddr(0);
+ other.context_size = 0;
+ }
+ }
+
+ void Initialize() {
+ buffer_address.Setup(0, 0);
+ context_address.Setup(0, 0);
+ start_offset = 0;
+ end_offset = 0;
+ loop = false;
+ stream_ended = false;
+ sent_to_DSP = true;
+ loop_start_offset = 0;
+ loop_end_offset = 0;
+ loop_count = 0;
+ }
+ /// Game memory address of the wavebuffer data
+ AddressInfo buffer_address{0, 0};
+ /// Context for decoding, used for ADPCM
+ AddressInfo context_address{0, 0};
+ /// Starting offset for the wavebuffer
+ u32 start_offset{};
+ /// Ending offset the wavebuffer
+ u32 end_offset{};
+ /// Should this wavebuffer loop?
+ bool loop{};
+ /// Has this wavebuffer ended?
+ bool stream_ended{};
+ /// Has this wavebuffer been sent to the AudioRenderer?
+ bool sent_to_DSP{true};
+ /// Starting offset when looping, can differ from start_offset
+ u32 loop_start_offset{};
+ /// Ending offset when looping, can differ from end_offset
+ u32 loop_end_offset{};
+ /// Number of times to loop this wavebuffer
+ s32 loop_count{};
+ };
+
+ struct WaveBufferInternal {
+ /* 0x00 */ CpuAddr address;
+ /* 0x08 */ u64 size;
+ /* 0x10 */ s32 start_offset;
+ /* 0x14 */ s32 end_offset;
+ /* 0x18 */ bool loop;
+ /* 0x19 */ bool stream_ended;
+ /* 0x1A */ bool sent_to_DSP;
+ /* 0x1C */ s32 loop_count;
+ /* 0x20 */ CpuAddr context_address;
+ /* 0x28 */ u64 context_size;
+ /* 0x30 */ u32 loop_start;
+ /* 0x34 */ u32 loop_end;
+ };
+ static_assert(sizeof(WaveBufferInternal) == 0x38,
+ "VoiceInfo::WaveBufferInternal has the wrong size!");
+
+ struct BiquadFilterParameter {
+ /* 0x00 */ bool enabled;
+ /* 0x02 */ std::array<s16, 3> b;
+ /* 0x08 */ std::array<s16, 2> a;
+ };
+ static_assert(sizeof(BiquadFilterParameter) == 0xC,
+ "VoiceInfo::BiquadFilterParameter has the wrong size!");
+
+ struct InParameter {
+ /* 0x000 */ u32 id;
+ /* 0x004 */ u32 node_id;
+ /* 0x008 */ bool is_new;
+ /* 0x009 */ bool in_use;
+ /* 0x00A */ PlayState play_state;
+ /* 0x00B */ SampleFormat sample_format;
+ /* 0x00C */ u32 sample_rate;
+ /* 0x010 */ s32 priority;
+ /* 0x014 */ s32 sort_order;
+ /* 0x018 */ u32 channel_count;
+ /* 0x01C */ f32 pitch;
+ /* 0x020 */ f32 volume;
+ /* 0x024 */ std::array<BiquadFilterParameter, MaxBiquadFilters> biquads;
+ /* 0x03C */ u32 wave_buffer_count;
+ /* 0x040 */ u16 wave_buffer_index;
+ /* 0x042 */ char unk042[0x6];
+ /* 0x048 */ CpuAddr src_data_address;
+ /* 0x050 */ u64 src_data_size;
+ /* 0x058 */ u32 mix_id;
+ /* 0x05C */ u32 splitter_id;
+ /* 0x060 */ std::array<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal;
+ /* 0x140 */ std::array<u32, MaxChannels> channel_resource_ids;
+ /* 0x158 */ bool clear_voice_drop;
+ /* 0x159 */ u8 flush_buffer_count;
+ /* 0x15A */ char unk15A[0x2];
+ /* 0x15C */ Flags flags;
+ /* 0x15D */ char unk15D[0x1];
+ /* 0x15E */ SrcQuality src_quality;
+ /* 0x15F */ char unk15F[0x11];
+ };
+ static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!");
+
+ struct OutStatus {
+ /* 0x00 */ u64 played_sample_count;
+ /* 0x08 */ u32 wave_buffers_consumed;
+ /* 0x0C */ bool voice_dropped;
+ };
+ static_assert(sizeof(OutStatus) == 0x10, "OutStatus::InParameter has the wrong size!");
+
+ VoiceInfo();
+
+ /**
+ * Initialize this voice.
+ */
+ void Initialize();
+
+ /**
+ * Does this voice ned an update?
+ *
+ * @param params - Input parametetrs to check matching.
+ * @return True if this voice needs an update, otherwise false.
+ */
+ bool ShouldUpdateParameters(const InParameter& params) const;
+
+ /**
+ * Update the parameters of this voice.
+ *
+ * @param error_info - Output error code.
+ * @param params - Input parametters to udpate from.
+ * @param pool_mapper - Used to map buffers.
+ * @param behavior - behavior to check supported features.
+ */
+ void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
+ const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
+
+ /**
+ * Update the current play state.
+ *
+ * @param state - New play state for this voice.
+ */
+ void UpdatePlayState(PlayState state);
+
+ /**
+ * Update the current sample rate conversion quality.
+ *
+ * @param quality - New quality.
+ */
+ void UpdateSrcQuality(SrcQuality quality);
+
+ /**
+ * Update all wavebuffers.
+ *
+ * @param error_infos - Output 2D array of errors, 2 per wavebuffer.
+ * @param error_count - Number of errors provided. Unused.
+ * @param params - Input parametters to be used for the update.
+ * @param voice_states - The voice states for each channel in this voice to be updated.
+ * @param pool_mapper - Used to map the wavebuffers.
+ * @param behavior - Used to check for supported features.
+ */
+ void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
+ u32 error_count, const InParameter& params,
+ std::span<VoiceState*> voice_states, const PoolMapper& pool_mapper,
+ const BehaviorInfo& behavior);
+
+ /**
+ * Update a wavebuffer.
+ *
+ * @param error_infos - Output array of errors.
+ * @param wave_buffer - The wavebuffer to be updated.
+ * @param wave_buffer_internal - Input parametters to be used for the update.
+ * @param sample_format - Sample format of the wavebuffer.
+ * @param valid - Is this wavebuffer valid?
+ * @param pool_mapper - Used to map the wavebuffers.
+ * @param behavior - Used to check for supported features.
+ */
+ void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
+ const WaveBufferInternal& wave_buffer_internal,
+ SampleFormat sample_format, bool valid, const PoolMapper& pool_mapper,
+ const BehaviorInfo& behavior);
+
+ /**
+ * Check if the input wavebuffer needs an update.
+ *
+ * @param wave_buffer_internal - Input wavebuffer parameters to check.
+ * @return True if the given wavebuffer needs an update, otherwise false.
+ */
+ bool ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const;
+
+ /**
+ * Write the number of played samples, number of consumed wavebuffers and if this voice was
+ * dropped, to the given out_status.
+ *
+ * @param out_status - Output status to be written to.
+ * @param in_params - Input parameters to check if the wavebuffer is new.
+ * @param voice_states - Current host voice states for this voice, source of the output.
+ */
+ void WriteOutStatus(OutStatus& out_status, const InParameter& in_params,
+ std::span<VoiceState*> voice_states);
+
+ /**
+ * Check if this voice should be skipped for command generation.
+ * Checks various things such as usage state, whether data is mapped etc.
+ *
+ * @return True if this voice should not be generated, otherwise false.
+ */
+ bool ShouldSkip() const;
+
+ /**
+ * Check if this voice has any mixing connections.
+ *
+ * @return True if this voice participes in mixing, otherwise false.
+ */
+ bool HasAnyConnection() const;
+
+ /**
+ * Flush flush_count wavebuffers, marking them as consumed.
+ *
+ * @param flush_count - Number of wavebuffers to flush.
+ * @param voice_states - Voice states for these wavebuffers.
+ * @param channel_count - Number of active channels.
+ */
+ void FlushWaveBuffers(u32 flush_count, std::span<VoiceState*> voice_states, s8 channel_count);
+
+ /**
+ * Update this voice's parameters on command generation,
+ * updating voice states and flushing if needed.
+ *
+ * @param voice_states - Voice states for these wavebuffers.
+ * @return True if this voice should be generated, otherwise false.
+ */
+ bool UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states);
+
+ /**
+ * Update this voice on command generation.
+ *
+ * @param voice_states - Voice states for these wavebuffers.
+ * @return True if this voice should be generated, otherwise false.
+ */
+ bool UpdateForCommandGeneration(VoiceContext& voice_context);
+
+ /**
+ * Reset the AudioRenderer-side voice states, and the channel resources for this voice.
+ *
+ * @param voice_context - Context from which to get the resources.
+ */
+ void ResetResources(VoiceContext& voice_context) const;
+
+ /// Is this voice in use?
+ bool in_use{};
+ /// Is this voice new?
+ bool is_new{};
+ /// Was this voice last playing? Used for depopping
+ bool was_playing{};
+ /// Sample format of the wavebuffers in this voice
+ SampleFormat sample_format{};
+ /// Sample rate of the wavebuffers in this voice
+ u32 sample_rate{};
+ /// Number of channels in this voice
+ s8 channel_count{};
+ /// Id of this voice
+ u32 id{};
+ /// Node id of this voice
+ u32 node_id{};
+ /// Mix id this voice is mixed to
+ u32 mix_id{};
+ /// Play state of this voice
+ ServerPlayState current_play_state{ServerPlayState::Stopped};
+ /// Last play state of this voice
+ ServerPlayState last_play_state{ServerPlayState::Started};
+ /// Priority of this voice, lower is higher
+ s32 priority{};
+ /// Sort order of this voice, used when same priority
+ s32 sort_order{};
+ /// Pitch of this voice (for sample rate conversion)
+ f32 pitch{};
+ /// Current volume of this voice
+ f32 volume{};
+ /// Previous volume of this voice
+ f32 prev_volume{};
+ /// Biquad filters for generating filter commands on this voice
+ std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{};
+ /// Number of active wavebuffers
+ u32 wave_buffer_count{};
+ /// Current playing wavebuffer index
+ u16 wave_buffer_index{};
+ /// Flags controlling decode behavior
+ u16 flags{};
+ /// Game memory for ADPCM coefficients
+ AddressInfo data_address{0, 0};
+ /// Wavebuffers
+ std::array<WaveBuffer, MaxWaveBuffers> wavebuffers{};
+ /// Channel resources for this voice
+ std::array<u32, MaxChannels> channel_resource_ids{};
+ /// Splitter id this voice is connected with
+ s32 splitter_id{UnusedSplitterId};
+ /// Sample rate conversion quality
+ SrcQuality src_quality{SrcQuality::Medium};
+ /// Was this voice dropped due to limited time?
+ bool voice_dropped{};
+ /// Is this voice's coefficient (data_address) unmapped?
+ bool data_unmapped{};
+ /// Is this voice's buffers (wavebuffer data and ADPCM context) unmapped?
+ bool buffer_unmapped{};
+ /// Initialisation state of the biquads
+ std::array<bool, MaxBiquadFilters> biquad_initialized{};
+ /// Number of wavebuffers to flush
+ u8 flush_buffer_count{};
+};
+
+} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_state.h b/src/audio_core/renderer/voice/voice_state.h
new file mode 100644
index 000000000..d5497e2fb
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_state.h
@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/common/common.h"
+#include "common/common_types.h"
+#include "common/fixed_point.h"
+
+namespace AudioCore::AudioRenderer {
+/**
+ * Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer,
+ * host-side is updated on the next iteration.
+ */
+struct VoiceState {
+ /**
+ * State of the voice's biquad filter.
+ */
+ struct BiquadFilterState {
+ Common::FixedPoint<50, 14> s0;
+ Common::FixedPoint<50, 14> s1;
+ Common::FixedPoint<50, 14> s2;
+ Common::FixedPoint<50, 14> s3;
+ };
+
+ /**
+ * Context for ADPCM decoding.
+ */
+ struct AdpcmContext {
+ u16 header;
+ s16 yn0;
+ s16 yn1;
+ };
+
+ /// Number of samples played
+ u64 played_sample_count;
+ /// Current offset from the starting offset
+ u32 offset;
+ /// Currently active wavebuffer index
+ u32 wave_buffer_index;
+ /// Array of which wavebuffers are currently valid
+
+ std::array<bool, MaxWaveBuffers> wave_buffer_valid;
+ /// Number of wavebuffers consumed, given back to the game
+ u32 wave_buffers_consumed;
+ /// History of samples, used for rate conversion
+
+ std::array<s16, MaxWaveBuffers * 2> sample_history;
+ /// Current read fraction, used for resampling
+ Common::FixedPoint<49, 15> fraction;
+ /// Current adpcm context
+ AdpcmContext adpcm_context;
+ /// Current biquad states, used when filtering
+
+ std::array<std::array<BiquadFilterState, MaxBiquadFilters>, MaxBiquadFilters> biquad_states;
+ /// Previous samples
+ std::array<s32, MaxMixBuffers> previous_samples;
+ /// Unused
+ u32 external_context_size;
+ /// Unused
+ bool external_context_enabled;
+ /// Was this voice dropped?
+ bool voice_dropped;
+ /// Number of times the wavebuffer has looped
+ s32 loop_count;
+};
+
+} // namespace AudioCore::AudioRenderer