summaryrefslogtreecommitdiffstats
path: root/src/audio_core/renderer/adsp
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio_core/renderer/adsp')
-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
7 files changed, 968 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