summaryrefslogtreecommitdiff
path: root/src/audio_core/renderer
diff options
context:
space:
mode:
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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/adsp.h"
5#include "audio_core/renderer/adsp/command_buffer.h"
6#include "audio_core/sink/sink.h"
7#include "common/logging/log.h"
8#include "core/core.h"
9#include "core/core_timing.h"
10#include "core/core_timing_util.h"
11#include "core/memory.h"
12
13namespace AudioCore::AudioRenderer::ADSP {
14
15ADSP::ADSP(Core::System& system_, Sink::Sink& sink_)
16 : system{system_}, memory{system.Memory()}, sink{sink_} {}
17
18ADSP::~ADSP() {
19 ClearCommandBuffers();
20}
21
22State ADSP::GetState() const {
23 if (running) {
24 return State::Started;
25 }
26 return State::Stopped;
27}
28
29AudioRenderer_Mailbox* ADSP::GetRenderMailbox() {
30 return &render_mailbox;
31}
32
33void ADSP::ClearRemainCount(const u32 session_id) {
34 render_mailbox.ClearRemainCount(session_id);
35}
36
37u64 ADSP::GetSignalledTick() const {
38 return render_mailbox.GetSignalledTick();
39}
40
41u64 ADSP::GetTimeTaken() const {
42 return render_mailbox.GetRenderTimeTaken();
43}
44
45u64 ADSP::GetRenderTimeTaken(const u32 session_id) {
46 return render_mailbox.GetCommandBuffer(session_id).render_time_taken;
47}
48
49u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
50 return render_mailbox.GetRemainCommandCount(session_id);
51}
52
53void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) {
54 render_mailbox.SetCommandBuffer(session_id, command_buffer);
55}
56
57u64 ADSP::GetRenderingStartTick(const u32 session_id) {
58 return render_mailbox.GetSignalledTick() +
59 render_mailbox.GetCommandBuffer(session_id).render_time_taken;
60}
61
62bool ADSP::Start() {
63 if (running) {
64 return running;
65 }
66
67 running = true;
68 systems_active++;
69 audio_renderer = std::make_unique<AudioRenderer>(system);
70 audio_renderer->Start(&render_mailbox);
71 render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK);
72 if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
73 LOG_ERROR(
74 Service_Audio,
75 "Host Audio Renderer -- Failed to receive initialize message response from ADSP!");
76 }
77 return running;
78}
79
80void ADSP::Stop() {
81 systems_active--;
82 if (running && systems_active == 0) {
83 {
84 std::scoped_lock l{mailbox_lock};
85 render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown);
86 if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) {
87 LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
88 "message response from ADSP!");
89 }
90 }
91 audio_renderer->Stop();
92 running = false;
93 }
94}
95
96void ADSP::Signal() {
97 const auto signalled_tick{system.CoreTiming().GetClockTicks()};
98 render_mailbox.SetSignalledTick(signalled_tick);
99 render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render);
100}
101
102void ADSP::Wait() {
103 std::scoped_lock l{mailbox_lock};
104 auto response{render_mailbox.HostWaitMessage()};
105 if (response != RenderMessage::AudioRenderer_RenderResponse) {
106 LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}",
107 static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse),
108 static_cast<u32>(response));
109 }
110
111 ClearCommandBuffers();
112}
113
114void ADSP::ClearCommandBuffers() {
115 render_mailbox.ClearCommandBuffers();
116}
117
118} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <mutex>
8
9#include "audio_core/renderer/adsp/audio_renderer.h"
10#include "common/common_types.h"
11
12namespace Core {
13namespace Memory {
14class Memory;
15}
16class System;
17} // namespace Core
18
19namespace AudioCore {
20namespace Sink {
21class Sink;
22}
23
24namespace AudioRenderer::ADSP {
25struct CommandBuffer;
26
27enum class State {
28 Started,
29 Stopped,
30};
31
32/**
33 * Represents the ADSP embedded within the audio sysmodule.
34 * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot.
35 *
36 * The kernel will run apps you program for it, Nintendo have the following:
37 *
38 * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all
39 * audio samples end up, and we skip it entirely, since we have very different backends and
40 * mixing is implicitly handled by the OS (but also due to lack of research/simplicity).
41 *
42 * AudioRenderer - Receives command lists generated by the audio render
43 * system, processes them, and sends the samples to Gmix.
44 *
45 * OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix.
46 * Not much research done here, TODO if needed.
47 *
48 * We only implement the AudioRenderer for now.
49 *
50 * Communication for the apps is done through mailboxes, and some shared memory.
51 */
52class ADSP {
53public:
54 explicit ADSP(Core::System& system, Sink::Sink& sink);
55 ~ADSP();
56
57 /**
58 * Start the ADSP.
59 *
60 * @return True if started or already running, otherwise false.
61 */
62 bool Start();
63
64 /**
65 * Stop the ADSP.
66 *
67 * @return True if started or already running, otherwise false.
68 */
69 void Stop();
70
71 /**
72 * Get the ADSP's state.
73 *
74 * @return Started or Stopped.
75 */
76 State GetState() const;
77
78 /**
79 * Get the AudioRenderer mailbox to communicate with it.
80 *
81 * @return The AudioRenderer mailbox.
82 */
83 AudioRenderer_Mailbox* GetRenderMailbox();
84
85 /**
86 * Get the tick the ADSP was signalled.
87 *
88 * @return The tick the ADSP was signalled.
89 */
90 u64 GetSignalledTick() const;
91
92 /**
93 * Get the total time it took for the ADSP to run the last command lists (both command lists).
94 *
95 * @return The tick the ADSP was signalled.
96 */
97 u64 GetTimeTaken() const;
98
99 /**
100 * Get the last time a given command list took to run.
101 *
102 * @param session_id - The session id to check (0 or 1).
103 * @return The time it took.
104 */
105 u64 GetRenderTimeTaken(u32 session_id);
106
107 /**
108 * Clear the remaining command count for a given session.
109 *
110 * @param session_id - The session id to check (0 or 1).
111 */
112 void ClearRemainCount(u32 session_id);
113
114 /**
115 * Get the remaining number of commands left to process for a command list.
116 *
117 * @param session_id - The session id to check (0 or 1).
118 * @return The number of commands remaining.
119 */
120 u32 GetRemainCommandCount(u32 session_id) const;
121
122 /**
123 * Get the last tick a command list started processing.
124 *
125 * @param session_id - The session id to check (0 or 1).
126 * @return The last tick the given command list started.
127 */
128 u64 GetRenderingStartTick(u32 session_id);
129
130 /**
131 * Set a command buffer to be processed.
132 *
133 * @param session_id - The session id to check (0 or 1).
134 * @param command_buffer - The command buffer to process.
135 */
136 void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer);
137
138 /**
139 * Clear the command buffers (does not clear the time taken or the remaining command count)
140 */
141 void ClearCommandBuffers();
142
143 /**
144 * Signal the AudioRenderer to begin processing.
145 */
146 void Signal();
147
148 /**
149 * Wait for the AudioRenderer to finish processing.
150 */
151 void Wait();
152
153private:
154 /// Core system
155 Core::System& system;
156 /// Core memory
157 Core::Memory::Memory& memory;
158 /// Number of systems active, used to prevent accidental shutdowns
159 u8 systems_active{0};
160 /// ADSP running state
161 std::atomic<bool> running{false};
162 /// Output sink used by the ADSP
163 Sink::Sink& sink;
164 /// AudioRenderer app
165 std::unique_ptr<AudioRenderer> audio_renderer{};
166 /// Communication for the AudioRenderer
167 AudioRenderer_Mailbox render_mailbox{};
168 /// Mailbox lock ffor the render mailbox
169 std::mutex mailbox_lock;
170};
171
172} // namespace AudioRenderer::ADSP
173} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <chrono>
6
7#include "audio_core/audio_core.h"
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/adsp/audio_renderer.h"
10#include "audio_core/sink/sink.h"
11#include "common/logging/log.h"
12#include "common/microprofile.h"
13#include "common/thread.h"
14#include "core/core.h"
15#include "core/core_timing.h"
16#include "core/core_timing_util.h"
17
18MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
19
20namespace AudioCore::AudioRenderer::ADSP {
21
22void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) {
23 adsp_messages.enqueue(message_);
24 adsp_event.Set();
25}
26
27RenderMessage AudioRenderer_Mailbox::HostWaitMessage() {
28 host_event.Wait();
29 RenderMessage msg{RenderMessage::Invalid};
30 if (!host_messages.try_dequeue(msg)) {
31 LOG_ERROR(Service_Audio, "Failed to dequeue host message!");
32 }
33 return msg;
34}
35
36void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) {
37 host_messages.enqueue(message_);
38 host_event.Set();
39}
40
41RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
42 adsp_event.Wait();
43 RenderMessage msg{RenderMessage::Invalid};
44 if (!adsp_messages.try_dequeue(msg)) {
45 LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!");
46 }
47 return msg;
48}
49
50CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) {
51 return command_buffers[session_id];
52}
53
54void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) {
55 command_buffers[session_id] = buffer;
56}
57
58u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const {
59 return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken;
60}
61
62u64 AudioRenderer_Mailbox::GetSignalledTick() const {
63 return signalled_tick;
64}
65
66void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) {
67 signalled_tick = tick;
68}
69
70void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) {
71 command_buffers[session_id].remaining_command_count = 0;
72}
73
74u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const {
75 return command_buffers[session_id].remaining_command_count;
76}
77
78void AudioRenderer_Mailbox::ClearCommandBuffers() {
79 command_buffers[0].buffer = 0;
80 command_buffers[0].size = 0;
81 command_buffers[0].reset_buffers = false;
82 command_buffers[1].buffer = 0;
83 command_buffers[1].size = 0;
84 command_buffers[1].reset_buffers = false;
85}
86
87AudioRenderer::AudioRenderer(Core::System& system_)
88 : system{system_}, sink{system.AudioCore().GetOutputSink()} {
89 CreateSinkStreams();
90}
91
92AudioRenderer::~AudioRenderer() {
93 Stop();
94 for (auto& stream : streams) {
95 if (stream) {
96 sink.CloseStream(stream);
97 }
98 stream = nullptr;
99 }
100}
101
102void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
103 if (running) {
104 return;
105 }
106
107 mailbox = mailbox_;
108 thread = std::thread(&AudioRenderer::ThreadFunc, this);
109 for (auto& stream : streams) {
110 stream->Start();
111 }
112 running = true;
113}
114
115void AudioRenderer::Stop() {
116 if (!running) {
117 return;
118 }
119
120 for (auto& stream : streams) {
121 stream->Stop();
122 }
123 thread.join();
124 running = false;
125}
126
127void AudioRenderer::CreateSinkStreams() {
128 u32 channels{sink.GetDeviceChannels()};
129 for (u32 i = 0; i < MaxRendererSessions; i++) {
130 std::string name{fmt::format("ADSP_RenderStream-{}", i)};
131 streams[i] =
132 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
133 }
134}
135
136void AudioRenderer::ThreadFunc() {
137 constexpr char name[]{"yuzu:AudioRenderer"};
138 MicroProfileOnThreadCreate(name);
139 Common::SetCurrentThreadName(name);
140 Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
141 if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
142 LOG_ERROR(Service_Audio,
143 "ADSP Audio Renderer -- Failed to receive initialize message from host!");
144 return;
145 }
146
147 mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
148
149 constexpr u64 max_process_time{2'304'000ULL};
150
151 while (true) {
152 auto message{mailbox->ADSPWaitMessage()};
153 switch (message) {
154 case RenderMessage::AudioRenderer_Shutdown:
155 mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown);
156 return;
157
158 case RenderMessage::AudioRenderer_Render: {
159 std::array<bool, MaxRendererSessions> buffers_reset{};
160 std::array<u64, MaxRendererSessions> render_times_taken{};
161 const auto start_time{system.CoreTiming().GetClockTicks()};
162
163 for (u32 index = 0; index < 2; index++) {
164 auto& command_buffer{mailbox->GetCommandBuffer(index)};
165 auto& command_list_processor{command_list_processors[index]};
166
167 // Check this buffer is valid, as it may not be used.
168 if (command_buffer.buffer != 0) {
169 // If there are no remaining commands (from the previous list),
170 // this is a new command list, initalize it.
171 if (command_buffer.remaining_command_count == 0) {
172 command_list_processor.Initialize(system, command_buffer.buffer,
173 command_buffer.size, streams[index]);
174 }
175
176 if (command_buffer.reset_buffers && !buffers_reset[index]) {
177 streams[index]->ClearQueue();
178 buffers_reset[index] = true;
179 }
180
181 u64 max_time{max_process_time};
182 if (index == 1 && command_buffer.applet_resource_user_id ==
183 mailbox->GetCommandBuffer(0).applet_resource_user_id) {
184 max_time = max_process_time -
185 Core::Timing::CyclesToNs(render_times_taken[0]).count();
186 if (render_times_taken[0] > max_process_time) {
187 max_time = 0;
188 }
189 }
190
191 max_time = std::min(command_buffer.time_limit, max_time);
192 command_list_processor.SetProcessTimeMax(max_time);
193
194 // Process the command list
195 {
196 MICROPROFILE_SCOPE(Audio_Renderer);
197 render_times_taken[index] =
198 command_list_processor.Process(index) - start_time;
199 }
200
201 if (index == 0) {
202 auto stream{command_list_processor.GetOutputSinkStream()};
203 system.AudioCore().SetStreamQueue(stream->GetQueueSize());
204 }
205
206 const auto end_time{system.CoreTiming().GetClockTicks()};
207
208 command_buffer.remaining_command_count =
209 command_list_processor.GetRemainingCommandCount();
210 command_buffer.render_time_taken = end_time - start_time;
211 }
212 }
213
214 mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
215 } break;
216
217 default:
218 LOG_WARNING(Service_Audio,
219 "ADSP AudioRenderer received an invalid message, msg={:02X}!",
220 static_cast<u32>(message));
221 break;
222 }
223 }
224}
225
226} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <memory>
8#include <thread>
9
10#include "audio_core/renderer/adsp/command_buffer.h"
11#include "audio_core/renderer/adsp/command_list_processor.h"
12#include "common/common_types.h"
13#include "common/reader_writer_queue.h"
14#include "common/thread.h"
15
16namespace Core {
17namespace Timing {
18struct EventType;
19}
20class System;
21} // namespace Core
22
23namespace AudioCore {
24namespace Sink {
25class Sink;
26}
27
28namespace AudioRenderer::ADSP {
29
30enum class RenderMessage {
31 /* 0x00 */ Invalid,
32 /* 0x01 */ AudioRenderer_MapUnmap_Map,
33 /* 0x02 */ AudioRenderer_MapUnmap_MapResponse,
34 /* 0x03 */ AudioRenderer_MapUnmap_Unmap,
35 /* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse,
36 /* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache,
37 /* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse,
38 /* 0x07 */ AudioRenderer_MapUnmap_Shutdown,
39 /* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse,
40 /* 0x16 */ AudioRenderer_InitializeOK = 0x16,
41 /* 0x20 */ AudioRenderer_RenderResponse = 0x20,
42 /* 0x2A */ AudioRenderer_Render = 0x2A,
43 /* 0x34 */ AudioRenderer_Shutdown = 0x34,
44};
45
46/**
47 * A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer
48 * running on the ADSP.
49 */
50class AudioRenderer_Mailbox {
51public:
52 /**
53 * Send a message from the host to the AudioRenderer.
54 *
55 * @param message_ - The message to send to the AudioRenderer.
56 */
57 void HostSendMessage(RenderMessage message);
58
59 /**
60 * Host wait for a message from the AudioRenderer.
61 *
62 * @return The message returned from the AudioRenderer.
63 */
64 RenderMessage HostWaitMessage();
65
66 /**
67 * Send a message from the AudioRenderer to the host.
68 *
69 * @param message_ - The message to send to the host.
70 */
71 void ADSPSendMessage(RenderMessage message);
72
73 /**
74 * AudioRenderer wait for a message from the host.
75 *
76 * @return The message returned from the AudioRenderer.
77 */
78 RenderMessage ADSPWaitMessage();
79
80 /**
81 * Get the command buffer with the given session id (0 or 1).
82 *
83 * @param session_id - The session id to get (0 or 1).
84 * @return The command buffer.
85 */
86 CommandBuffer& GetCommandBuffer(s32 session_id);
87
88 /**
89 * Set the command buffer with the given session id (0 or 1).
90 *
91 * @param session_id - The session id to get (0 or 1).
92 * @param buffer - The command buffer to set.
93 */
94 void SetCommandBuffer(u32 session_id, CommandBuffer& buffer);
95
96 /**
97 * Get the total render time taken for the last command lists sent.
98 *
99 * @return Total render time taken for the last command lists.
100 */
101 u64 GetRenderTimeTaken() const;
102
103 /**
104 * Get the tick the AudioRenderer was signalled.
105 *
106 * @return The tick the AudioRenderer was signalled.
107 */
108 u64 GetSignalledTick() const;
109
110 /**
111 * Set the tick the AudioRenderer was signalled.
112 *
113 * @param tick - The tick the AudioRenderer was signalled.
114 */
115 void SetSignalledTick(u64 tick);
116
117 /**
118 * Clear the remaining command count.
119 *
120 * @param session_id - Index for which command list to clear (0 or 1).
121 */
122 void ClearRemainCount(u32 session_id);
123
124 /**
125 * Get the remaining command count for a given command list.
126 *
127 * @param session_id - Index for which command list to clear (0 or 1).
128 * @return The remaining command count.
129 */
130 u32 GetRemainCommandCount(u32 session_id) const;
131
132 /**
133 * Clear the command buffers (does not clear the time taken or the remaining command count).
134 */
135 void ClearCommandBuffers();
136
137private:
138 /// Host signalling event
139 Common::Event host_event{};
140 /// AudioRenderer signalling event
141 Common::Event adsp_event{};
142 /// Host message queue
143
144 Common::ReaderWriterQueue<RenderMessage> host_messages{};
145 /// AudioRenderer message queue
146
147 Common::ReaderWriterQueue<RenderMessage> adsp_messages{};
148 /// Command buffers
149
150 std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
151 /// Tick the AudioRnederer was signalled
152 u64 signalled_tick{};
153};
154
155/**
156 * The AudioRenderer application running on the ADSP.
157 */
158class AudioRenderer {
159public:
160 explicit AudioRenderer(Core::System& system);
161 ~AudioRenderer();
162
163 /**
164 * Start the AudioRenderer.
165 *
166 * @param The mailbox to use for this session.
167 */
168 void Start(AudioRenderer_Mailbox* mailbox);
169
170 /**
171 * Stop the AudioRenderer.
172 */
173 void Stop();
174
175private:
176 /**
177 * Main AudioRenderer thread, responsible for processing the command lists.
178 */
179 void ThreadFunc();
180
181 /**
182 * Creates the streams which will receive the processed samples.
183 */
184 void CreateSinkStreams();
185
186 /// Core system
187 Core::System& system;
188 /// Main thread
189 std::thread thread{};
190 /// The current state
191 std::atomic<bool> running{};
192 /// The active mailbox
193 AudioRenderer_Mailbox* mailbox{};
194 /// The command lists to process
195 std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
196 /// The output sink the AudioRenderer will use
197 Sink::Sink& sink;
198 /// The streams which will receive the processed samples
199 std::array<Sink::SinkStream*, MaxRendererSessions> streams;
200};
201
202} // namespace AudioRenderer::ADSP
203} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/common/common.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer::ADSP {
10
11struct CommandBuffer {
12 CpuAddr buffer;
13 u64 size;
14 u64 time_limit;
15 u32 remaining_command_count;
16 bool reset_buffers;
17 u64 applet_resource_user_id;
18 u64 render_time_taken;
19};
20
21} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <string>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/command_list_header.h"
8#include "audio_core/renderer/command/commands.h"
9#include "common/settings.h"
10#include "core/core.h"
11#include "core/core_timing.h"
12#include "core/core_timing_util.h"
13#include "core/memory.h"
14
15namespace AudioCore::AudioRenderer::ADSP {
16
17void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size,
18 Sink::SinkStream* stream_) {
19 system = &system_;
20 memory = &system->Memory();
21 stream = stream_;
22 header = reinterpret_cast<CommandListHeader*>(buffer);
23 commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
24 commands_buffer_size = size;
25 command_count = header->command_count;
26 sample_count = header->sample_count;
27 target_sample_rate = header->sample_rate;
28 mix_buffers = header->samples_buffer;
29 buffer_count = header->buffer_count;
30 processed_command_count = 0;
31}
32
33void CommandListProcessor::SetProcessTimeMax(const u64 time) {
34 max_process_time = time;
35}
36
37u32 CommandListProcessor::GetRemainingCommandCount() const {
38 return command_count - processed_command_count;
39}
40
41void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
42 commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
43 commands_buffer_size = size;
44}
45
46Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
47 return stream;
48}
49
50u64 CommandListProcessor::Process(u32 session_id) {
51 const auto start_time_{system->CoreTiming().GetClockTicks()};
52 const auto command_base{CpuAddr(commands)};
53
54 if (processed_command_count > 0) {
55 current_processing_time += start_time_ - end_time;
56 } else {
57 start_time = start_time_;
58 current_processing_time = 0;
59 }
60
61 std::string dump{fmt::format("\nSession {}\n", session_id)};
62
63 for (u32 index = 0; index < command_count; index++) {
64 auto& command{*reinterpret_cast<ICommand*>(commands)};
65
66 if (command.magic != 0xCAFEBABE) {
67 LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}",
68 command.magic);
69 return system->CoreTiming().GetClockTicks() - start_time_;
70 }
71
72 auto current_offset{CpuAddr(commands) - command_base};
73
74 if (current_offset + command.size > commands_buffer_size) {
75 LOG_ERROR(Service_Audio,
76 "Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}",
77 commands_buffer_size,
78 CpuAddr(commands) + command.size - sizeof(CommandListHeader));
79 return system->CoreTiming().GetClockTicks() - start_time_;
80 }
81
82 if (Settings::values.dump_audio_commands) {
83 command.Dump(*this, dump);
84 }
85
86 if (!command.Verify(*this)) {
87 break;
88 }
89
90 if (command.enabled) {
91 command.Process(*this);
92 } else {
93 dump += fmt::format("\tDisabled!\n");
94 }
95
96 processed_command_count++;
97 commands += command.size;
98 }
99
100 if (Settings::values.dump_audio_commands && dump != last_dump) {
101 LOG_WARNING(Service_Audio, "{}", dump);
102 last_dump = dump;
103 }
104
105 end_time = system->CoreTiming().GetClockTicks();
106 return end_time - start_time_;
107}
108
109} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10
11namespace Core {
12namespace Memory {
13class Memory;
14}
15class System;
16} // namespace Core
17
18namespace AudioCore {
19namespace Sink {
20class SinkStream;
21}
22
23namespace AudioRenderer {
24struct CommandListHeader;
25
26namespace ADSP {
27
28/**
29 * A processor for command lists given to the AudioRenderer.
30 */
31class CommandListProcessor {
32public:
33 /**
34 * Initialize the processor.
35 *
36 * @param system_ - The core system.
37 * @param buffer - The command buffer to process.
38 * @param size - The size of the buffer.
39 * @param stream_ - The stream to be used for sending the samples.
40 */
41 void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
42
43 /**
44 * Set the maximum processing time for this command list.
45 *
46 * @param time - The maximum process time.
47 */
48 void SetProcessTimeMax(u64 time);
49
50 /**
51 * Get the remaining command count for this list.
52 *
53 * @return The remaining command count.
54 */
55 u32 GetRemainingCommandCount() const;
56
57 /**
58 * Set the command buffer.
59 *
60 * @param buffer - The buffer to use.
61 * @param size - The size of the buffer.
62 */
63 void SetBuffer(CpuAddr buffer, u64 size);
64
65 /**
66 * Get the stream for this command list.
67 *
68 * @return The stream associated with this command list.
69 */
70 Sink::SinkStream* GetOutputSinkStream() const;
71
72 /**
73 * Process the command list.
74 *
75 * @param index - Index of the current command list.
76 * @return The time taken to process.
77 */
78 u64 Process(u32 session_id);
79
80 /// Core system
81 Core::System* system{};
82 /// Core memory
83 Core::Memory::Memory* memory{};
84 /// Stream for the processed samples
85 Sink::SinkStream* stream{};
86 /// Header info for this command list
87 CommandListHeader* header{};
88 /// The command buffer
89 u8* commands{};
90 /// The command buffer size
91 u64 commands_buffer_size{};
92 /// The maximum processing time alloted
93 u64 max_process_time{};
94 /// The number of commands in the buffer
95 u32 command_count{};
96 /// The target sample count for output
97 u32 sample_count{};
98 /// The target sample rate for output
99 u32 target_sample_rate{};
100 /// The mixing buffers used by the commands
101 std::span<s32> mix_buffers{};
102 /// The number of mix buffers
103 u32 buffer_count{};
104 /// The number of processed commands so far
105 u32 processed_command_count{};
106 /// The processing start time of this list
107 u64 start_time{};
108 /// The current processing time for this list
109 u64 current_processing_time{};
110 /// The end processing time for this list
111 u64 end_time{};
112 /// Last command list string generated, used for dumping audio commands to console
113 std::string last_dump{};
114};
115
116} // namespace ADSP
117} // namespace AudioRenderer
118} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_core.h"
5#include "audio_core/common/feature_support.h"
6#include "audio_core/renderer/audio_device.h"
7#include "audio_core/sink/sink.h"
8#include "core/core.h"
9
10namespace AudioCore::AudioRenderer {
11
12AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
13 const u32 revision)
14 : output_sink{system.AudioCore().GetOutputSink()},
15 applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
16
17u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
18 const size_t max_count) {
19 std::span<AudioDeviceName> names{};
20
21 if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
22 names = usb_device_names;
23 } else {
24 names = device_names;
25 }
26
27 u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
28 for (u32 i = 0; i < out_count; i++) {
29 out_buffer.push_back(names[i]);
30 }
31 return out_count;
32}
33
34u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
35 const size_t max_count) {
36 u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
37
38 for (u32 i = 0; i < out_count; i++) {
39 out_buffer.push_back(output_device_names[i]);
40 }
41 return out_count;
42}
43
44void AudioDevice::SetDeviceVolumes(const f32 volume) {
45 output_sink.SetDeviceVolume(volume);
46}
47
48f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) {
49 return output_sink.GetDeviceVolume();
50}
51
52} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/audio_render_manager.h"
9
10namespace Core {
11class System;
12}
13
14namespace AudioCore {
15namespace Sink {
16class Sink;
17}
18
19namespace AudioRenderer {
20/**
21 * An interface to an output audio device available to the Switch.
22 */
23class AudioDevice {
24public:
25 struct AudioDeviceName {
26 std::array<char, 0x100> name;
27
28 AudioDeviceName(const char* name_) {
29 std::strncpy(name.data(), name_, name.size());
30 }
31 };
32
33 std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput",
34 "AudioBuiltInSpeakerOutput", "AudioTvOutput",
35 "AudioUsbDeviceOutput"};
36 std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput",
37 "AudioBuiltInSpeakerOutput", "AudioTvOutput"};
38 std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput",
39 "AudioExternalOutput"};
40
41 explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
42
43 /**
44 * Get a list of the available output devices.
45 *
46 * @param out_buffer - Output buffer to write the available device names.
47 * @param max_count - Maximum number of devices to write (count of out_buffer).
48 * @return Number of device names written.
49 */
50 u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
51
52 /**
53 * Get a list of the available output devices.
54 * Different to above somehow...
55 *
56 * @param out_buffer - Output buffer to write the available device names.
57 * @param max_count - Maximum number of devices to write (count of out_buffer).
58 * @return Number of device names written.
59 */
60 u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
61
62 /**
63 * Set the volume of all streams in the backend sink.
64 *
65 * @param volume - Volume to set.
66 */
67 void SetDeviceVolumes(f32 volume);
68
69 /**
70 * Get the volume for a given device name.
71 * Note: This is not fully implemented, we only assume 1 device for all streams.
72 *
73 * @param name - Name of the device to check. Unused.
74 * @return Volume of the device.
75 */
76 f32 GetDeviceVolume(std::string_view name);
77
78private:
79 /// Backend output sink for the device
80 Sink::Sink& output_sink;
81 /// Resource id this device is used for
82 const u64 applet_resource_user_id;
83 /// User audio renderer revision
84 const u32 user_revision;
85};
86
87} // namespace AudioRenderer
88} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_render_manager.h"
5#include "audio_core/common/audio_renderer_parameter.h"
6#include "audio_core/renderer/audio_renderer.h"
7#include "audio_core/renderer/system_manager.h"
8#include "core/core.h"
9#include "core/hle/kernel/k_transfer_memory.h"
10#include "core/hle/service/audio/errors.h"
11
12namespace AudioCore::AudioRenderer {
13
14Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event)
15 : core{system_}, manager{manager_}, system{system_, rendered_event} {}
16
17Result Renderer::Initialize(const AudioRendererParameterInternal& params,
18 Kernel::KTransferMemory* transfer_memory,
19 const u64 transfer_memory_size, const u32 process_handle,
20 const u64 applet_resource_user_id, const s32 session_id) {
21 if (params.execution_mode == ExecutionMode::Auto) {
22 if (!manager.AddSystem(system)) {
23 LOG_ERROR(Service_Audio,
24 "Both Audio Render sessions are in use, cannot create any more");
25 return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
26 }
27 system_registered = true;
28 }
29
30 initialized = true;
31 system.Initialize(params, transfer_memory, transfer_memory_size, process_handle,
32 applet_resource_user_id, session_id);
33
34 return ResultSuccess;
35}
36
37void Renderer::Finalize() {
38 auto session_id{system.GetSessionId()};
39
40 system.Finalize();
41
42 if (system_registered) {
43 manager.RemoveSystem(system);
44 system_registered = false;
45 }
46
47 manager.ReleaseSessionId(session_id);
48}
49
50System& Renderer::GetSystem() {
51 return system;
52}
53
54void Renderer::Start() {
55 system.Start();
56}
57
58void Renderer::Stop() {
59 system.Stop();
60}
61
62Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance,
63 std::span<u8> output) {
64 return system.Update(input, performance, output);
65}
66
67} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/system.h"
9#include "core/hle/service/audio/errors.h"
10
11namespace Core {
12class System;
13}
14
15namespace Kernel {
16class KTransferMemory;
17}
18
19namespace AudioCore {
20struct AudioRendererParameterInternal;
21
22namespace AudioRenderer {
23class Manager;
24
25/**
26 * Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls.
27 */
28class Renderer {
29public:
30 explicit Renderer(Core::System& system, Manager& manager, Kernel::KEvent* rendered_event);
31
32 /**
33 * Initialize the renderer.
34 * Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes
35 * everything to a default state.
36 *
37 * @param params - Input parameters to initialize the system with.
38 * @param transfer_memory - Game-supplied memory for all workbuffers. Unused.
39 * @param transfer_memory_size - Size of the transfer memory. Unused.
40 * @param process_handle - Process handle, also used for memory. Unused.
41 * @param applet_resource_user_id - Applet id for this renderer. Unused.
42 * @param session_id - Session id of this renderer.
43 * @return Result code.
44 */
45 Result Initialize(const AudioRendererParameterInternal& params,
46 Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
47 u32 process_handle, u64 applet_resource_user_id, s32 session_id);
48
49 /**
50 * Finalize the renderer for shutdown.
51 */
52 void Finalize();
53
54 /**
55 * Get the renderer's system.
56 *
57 * @return Reference to the system.
58 */
59 System& GetSystem();
60
61 /**
62 * Start the renderer.
63 */
64 void Start();
65
66 /**
67 * Stop the renderer.
68 */
69 void Stop();
70
71 /**
72 * Update the audio renderer with new information.
73 * Called via RequestUpdate from the AudRen:U service.
74 *
75 * @param input - Input buffer containing the new data.
76 * @param performance - Optional performance buffer for outputting performance metrics.
77 * @param output - Output data from the renderer.
78 * @return Result code.
79 */
80 Result RequestUpdate(std::span<const u8> input, std::span<u8> performance,
81 std::span<u8> output);
82
83private:
84 /// System core
85 Core::System& core;
86 /// Manager this renderer is registered with
87 Manager& manager;
88 /// Is the audio renderer initialized?
89 bool initialized{};
90 /// Is the system registered with the manager?
91 bool system_registered{};
92 /// Audio render system, main driver of audio rendering
93 System system;
94};
95
96} // namespace AudioRenderer
97} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/common/feature_support.h"
5#include "audio_core/renderer/behavior/behavior_info.h"
6
7namespace AudioCore::AudioRenderer {
8
9BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {}
10
11u32 BehaviorInfo::GetProcessRevisionNum() const {
12 return process_revision;
13}
14
15u32 BehaviorInfo::GetProcessRevision() const {
16 return Common::MakeMagic('R', 'E', 'V',
17 static_cast<char>(static_cast<u8>('0') + process_revision));
18}
19
20u32 BehaviorInfo::GetUserRevisionNum() const {
21 return user_revision;
22}
23
24u32 BehaviorInfo::GetUserRevision() const {
25 return Common::MakeMagic('R', 'E', 'V',
26 static_cast<char>(static_cast<u8>('0') + user_revision));
27}
28
29void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) {
30 user_revision = GetRevisionNum(user_revision_);
31}
32
33void BehaviorInfo::ClearError() {
34 error_count = 0;
35}
36
37void BehaviorInfo::AppendError(ErrorInfo& error) {
38 LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
39 error.error_code.raw, error.address);
40 if (error_count < MaxErrors) {
41 errors[error_count++] = error;
42 }
43}
44
45void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) {
46 auto error_count_{std::min(error_count, MaxErrors)};
47 std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo));
48
49 for (size_t i = 0; i < error_count_; i++) {
50 out_errors[i] = errors[i];
51 }
52 out_count = error_count_;
53}
54
55void BehaviorInfo::UpdateFlags(const Flags flags_) {
56 flags = flags_;
57}
58
59bool BehaviorInfo::IsMemoryForceMappingEnabled() const {
60 return flags.IsMemoryForceMappingEnabled;
61}
62
63bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
64 return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision);
65}
66
67bool BehaviorInfo::IsSplitterSupported() const {
68 return CheckFeatureSupported(SupportTags::Splitter, user_revision);
69}
70
71bool BehaviorInfo::IsSplitterBugFixed() const {
72 return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision);
73}
74
75bool BehaviorInfo::IsEffectInfoVersion2Supported() const {
76 return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision);
77}
78
79bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const {
80 return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize,
81 user_revision);
82}
83
84bool BehaviorInfo::IsWaveBufferVer2Supported() const {
85 return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision);
86}
87
88bool BehaviorInfo::IsLongSizePreDelaySupported() const {
89 return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision);
90}
91
92bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const {
93 return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2,
94 user_revision);
95}
96
97bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const {
98 return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3,
99 user_revision);
100}
101
102bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const {
103 return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
104 user_revision);
105}
106
107bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const {
108 return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
109 user_revision);
110}
111
112bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
113 return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent,
114 user_revision);
115}
116
117bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
118 return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent,
119 user_revision);
120}
121
122bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
123 return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent,
124 user_revision);
125}
126
127bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
128 return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision);
129}
130
131bool BehaviorInfo::IsElapsedFrameCountSupported() const {
132 return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision);
133}
134
135bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const {
136 return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision);
137}
138
139size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const {
140 if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) {
141 return 2;
142 }
143 return 1;
144}
145
146bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
147 return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision);
148}
149
150bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
151 return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint,
152 user_revision);
153}
154
155bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const {
156 return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision);
157}
158
159bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const {
160 return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision);
161}
162
163bool BehaviorInfo::UseBiquadFilterFloatProcessing() const {
164 return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision);
165}
166
167bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
168 return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision);
169}
170
171bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const {
172 return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision);
173}
174
175bool BehaviorInfo::IsDeviceApiVersion2Supported() const {
176 return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision);
177}
178
179bool BehaviorInfo::IsDelayChannelMappingChanged() const {
180 return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision);
181}
182
183bool BehaviorInfo::IsReverbChannelMappingChanged() const {
184 return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision);
185}
186
187bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const {
188 return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision);
189}
190
191} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8
9#include "audio_core/common/common.h"
10#include "common/common_types.h"
11#include "core/hle/service/audio/errors.h"
12
13namespace AudioCore::AudioRenderer {
14/**
15 * Holds host and user revisions, checks whether render features can be enabled, and reports errors.
16 */
17class BehaviorInfo {
18 static constexpr u32 MaxErrors = 10;
19
20public:
21 struct ErrorInfo {
22 /* 0x00 */ Result error_code{0};
23 /* 0x04 */ u32 unk_04;
24 /* 0x08 */ CpuAddr address;
25 };
26 static_assert(sizeof(ErrorInfo) == 0x10, "BehaviorInfo::ErrorInfo has the wrong size!");
27
28 struct Flags {
29 u64 IsMemoryForceMappingEnabled : 1;
30 };
31
32 struct InParameter {
33 /* 0x00 */ u32 revision;
34 /* 0x08 */ Flags flags;
35 };
36 static_assert(sizeof(InParameter) == 0x10, "BehaviorInfo::InParameter has the wrong size!");
37
38 struct OutStatus {
39 /* 0x00 */ std::array<ErrorInfo, MaxErrors> errors;
40 /* 0xA0 */ u32 error_count;
41 /* 0xA4 */ char unkA4[0xC];
42 };
43 static_assert(sizeof(OutStatus) == 0xB0, "BehaviorInfo::OutStatus has the wrong size!");
44
45 BehaviorInfo();
46
47 /**
48 * Get the host revision as a number.
49 *
50 * @return The host revision.
51 */
52 u32 GetProcessRevisionNum() const;
53
54 /**
55 * Get the host revision in chars, e.g REV8.
56 * Rev 10 and higher use the ascii characters above 9.
57 * E.g:
58 * Rev 10 = REV:
59 * Rev 11 = REV;
60 *
61 * @return The host revision.
62 */
63 u32 GetProcessRevision() const;
64
65 /**
66 * Get the user revision as a number.
67 *
68 * @return The user revision.
69 */
70 u32 GetUserRevisionNum() const;
71
72 /**
73 * Get the user revision in chars, e.g REV8.
74 * Rev 10 and higher use the ascii characters above 9. REV: REV; etc.
75 *
76 * @return The user revision.
77 */
78 u32 GetUserRevision() const;
79
80 /**
81 * Set the user revision.
82 *
83 * @param user_revision - The user's revision.
84 */
85 void SetUserLibRevision(u32 user_revision);
86
87 /**
88 * Clear the current error count.
89 */
90 void ClearError();
91
92 /**
93 * Append an error to the error list.
94 *
95 * @param error - The new error.
96 */
97 void AppendError(ErrorInfo& error);
98
99 /**
100 * Copy errors to the given output container.
101 *
102 * @param out_errors - Output container to receive the errors.
103 * @param out_count - The number of errors written.
104 */
105 void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count);
106
107 /**
108 * Update the behaviour flags.
109 *
110 * @param flags - New flags to use.
111 */
112 void UpdateFlags(Flags flags);
113
114 /**
115 * Check if memory pools can be forcibly mapped.
116 *
117 * @return True if enabled, otherwise false.
118 */
119 bool IsMemoryForceMappingEnabled() const;
120
121 /**
122 * Check if the ADPCM context bug is fixed.
123 * The ADPCM context was not being sent to the AudioRenderer, leading to incorrect scaling being
124 * used.
125 *
126 * @return True if fixed, otherwise false.
127 */
128 bool IsAdpcmLoopContextBugFixed() const;
129
130 /**
131 * Check if the splitter is supported.
132 *
133 * @return True if supported, otherwise false.
134 */
135 bool IsSplitterSupported() const;
136
137 /**
138 * Check if the splitter bug is fixed.
139 * Update is given the wrong number of splitter destinations, leading to invalid data
140 * being processed.
141 *
142 * @return True if supported, otherwise false.
143 */
144 bool IsSplitterBugFixed() const;
145
146 /**
147 * Check if effects version 2 are supported.
148 * This gives support for returning effect states from the AudioRenderer, currently only used
149 * for Limiter statistics.
150 *
151 * @return True if supported, otherwise false.
152 */
153 bool IsEffectInfoVersion2Supported() const;
154
155 /**
156 * Check if a variadic command buffer is supported.
157 * As of Rev 5 with the added optional performance metric logging, the command
158 * buffer can be a variable size, so take that into account for calcualting its size.
159 *
160 * @return True if supported, otherwise false.
161 */
162 bool IsVariadicCommandBufferSizeSupported() const;
163
164 /**
165 * Check if wave buffers version 2 are supported.
166 * See WaveBufferVersion1 and WaveBufferVersion2.
167 *
168 * @return True if supported, otherwise false.
169 */
170 bool IsWaveBufferVer2Supported() const;
171
172 /**
173 * Check if long size pre delay is supported.
174 * This allows a longer initial delay time for the Reverb command.
175 *
176 * @return True if supported, otherwise false.
177 */
178 bool IsLongSizePreDelaySupported() const;
179
180 /**
181 * Check if the command time estimator version 2 is supported.
182 *
183 * @return True if supported, otherwise false.
184 */
185 bool IsCommandProcessingTimeEstimatorVersion2Supported() const;
186
187 /**
188 * Check if the command time estimator version 3 is supported.
189 *
190 * @return True if supported, otherwise false.
191 */
192 bool IsCommandProcessingTimeEstimatorVersion3Supported() const;
193
194 /**
195 * Check if the command time estimator version 4 is supported.
196 *
197 * @return True if supported, otherwise false.
198 */
199 bool IsCommandProcessingTimeEstimatorVersion4Supported() const;
200
201 /**
202 * Check if the command time estimator version 5 is supported.
203 *
204 * @return True if supported, otherwise false.
205 */
206 bool IsCommandProcessingTimeEstimatorVersion5Supported() const;
207
208 /**
209 * Check if the AudioRenderer can use up to 70% of the allocated processing timeslice.
210 *
211 * @return True if supported, otherwise false.
212 */
213 bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
214
215 /**
216 * Check if the AudioRenderer can use up to 75% of the allocated processing timeslice.
217 *
218 * @return True if supported, otherwise false.
219 */
220 bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
221
222 /**
223 * Check if the AudioRenderer can use up to 80% of the allocated processing timeslice.
224 *
225 * @return True if supported, otherwise false.
226 */
227 bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
228
229 /**
230 * Check if voice flushing is supported
231 * This allowws low-priority voices to be dropped if the AudioRenderer is running behind.
232 *
233 * @return True if supported, otherwise false.
234 */
235 bool IsFlushVoiceWaveBuffersSupported() const;
236
237 /**
238 * Check if counting the number of elapsed frames is supported.
239 * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
240 * processed a command list.
241 *
242 * @return True if supported, otherwise false.
243 */
244 bool IsElapsedFrameCountSupported() const;
245
246 /**
247 * Check if performance metrics version 2 are supported.
248 * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
249 * (Unused?).
250 *
251 * @return True if supported, otherwise false.
252 */
253 bool IsPerformanceMetricsDataFormatVersion2Supported() const;
254
255 /**
256 * Get the supported performance metrics version.
257 * Version 2 logs some extra fields in output, such as number of voices dropped,
258 * processing start time, if the AudioRenderer exceeded its time, etc.
259 *
260 * @return Version supported, either 1 or 2.
261 */
262 size_t GetPerformanceMetricsDataFormat() const;
263
264 /**
265 * Check if skipping voice pitch and sample rate conversion is supported.
266 * This speeds up the data source commands by skipping resampling if unwanted.
267 * See AudioCore::AudioRenderer::DecodeFromWaveBuffers
268 *
269 * @return True if supported, otherwise false.
270 */
271 bool IsVoicePitchAndSrcSkippedSupported() const;
272
273 /**
274 * Check if resetting played sample count at loop points is supported.
275 * This resets the number of samples played in a voice state when a loop point is reached.
276 * See AudioCore::AudioRenderer::DecodeFromWaveBuffers
277 *
278 * @return True if supported, otherwise false.
279 */
280 bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
281
282 /**
283 * Check if the clear state bug for biquad filters is fixed.
284 * The biquad state was not marked as needing re-initialisation when the effect was updated, it
285 * was only initialized once with a new effect.
286 *
287 * @return True if fixed, otherwise false.
288 */
289 bool IsBiquadFilterEffectStateClearBugFixed() const;
290
291 /**
292 * Check if Q23 precision is supported for fixed point.
293 *
294 * @return True if supported, otherwise false.
295 */
296 bool IsVolumeMixParameterPrecisionQ23Supported() const;
297
298 /**
299 * Check if float processing for biuad filters is supported.
300 *
301 * @return True if supported, otherwise false.
302 */
303 bool UseBiquadFilterFloatProcessing() const;
304
305 /**
306 * Check if dirty-only mix updates are supported.
307 * This saves a lot of buffer size as mixes can be large and not change much.
308 *
309 * @return True if supported, otherwise false.
310 */
311 bool IsMixInParameterDirtyOnlyUpdateSupported() const;
312
313 /**
314 * Check if multi-tap biquad filters are supported.
315 *
316 * @return True if supported, otherwise false.
317 */
318 bool UseMultiTapBiquadFilterProcessing() const;
319
320 /**
321 * Check if device api version 2 is supported.
322 * In the SDK but not in any sysmodule? Not sure, left here for completeness anyway.
323 *
324 * @return True if supported, otherwise false.
325 */
326 bool IsDeviceApiVersion2Supported() const;
327
328 /**
329 * Check if new channel mappings are used for Delay commands.
330 * Older commands used:
331 * front left/front right/back left/back right/center/lfe
332 * Whereas everywhere else in the code uses:
333 * front left/front right/center/lfe/back left/back right
334 * This corrects that and makes everything standardised.
335 *
336 * @return True if supported, otherwise false.
337 */
338 bool IsDelayChannelMappingChanged() const;
339
340 /**
341 * Check if new channel mappings are used for Reverb commands.
342 * Older commands used:
343 * front left/front right/back left/back right/center/lfe
344 * Whereas everywhere else in the code uses:
345 * front left/front right/center/lfe/back left/back right
346 * This corrects that and makes everything standardised.
347 *
348 * @return True if supported, otherwise false.
349 */
350 bool IsReverbChannelMappingChanged() const;
351
352 /**
353 * Check if new channel mappings are used for I3dl2Reverb commands.
354 * Older commands used:
355 * front left/front right/back left/back right/center/lfe
356 * Whereas everywhere else in the code uses:
357 * front left/front right/center/lfe/back left/back right
358 * This corrects that and makes everything standardised.
359 *
360 * @return True if supported, otherwise false.
361 */
362 bool IsI3dl2ReverbChannelMappingChanged() const;
363
364 /// Host version
365 u32 process_revision;
366 /// User version
367 u32 user_revision{};
368 /// Behaviour flags
369 Flags flags{};
370 /// Errors generated and reported during Update
371 std::array<ErrorInfo, MaxErrors> errors{};
372 /// Error count
373 u32 error_count{};
374};
375
376} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/common/feature_support.h"
5#include "audio_core/renderer/behavior/behavior_info.h"
6#include "audio_core/renderer/behavior/info_updater.h"
7#include "audio_core/renderer/effect/effect_context.h"
8#include "audio_core/renderer/effect/effect_reset.h"
9#include "audio_core/renderer/memory/memory_pool_info.h"
10#include "audio_core/renderer/mix/mix_context.h"
11#include "audio_core/renderer/performance/performance_manager.h"
12#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
13#include "audio_core/renderer/sink/device_sink_info.h"
14#include "audio_core/renderer/sink/sink_context.h"
15#include "audio_core/renderer/splitter/splitter_context.h"
16#include "audio_core/renderer/voice/voice_context.h"
17
18namespace AudioCore::AudioRenderer {
19
20InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_,
21 const u32 process_handle_, BehaviorInfo& behaviour_)
22 : input{input_.data() + sizeof(UpdateDataHeader)},
23 input_origin{input_}, output{output_.data() + sizeof(UpdateDataHeader)},
24 output_origin{output_}, in_header{reinterpret_cast<const UpdateDataHeader*>(
25 input_origin.data())},
26 out_header{reinterpret_cast<UpdateDataHeader*>(output_origin.data())},
27 expected_input_size{input_.size()}, expected_output_size{output_.size()},
28 process_handle{process_handle_}, behaviour{behaviour_} {
29 std::construct_at<UpdateDataHeader>(out_header, behaviour.GetProcessRevision());
30}
31
32Result InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
33 const auto voice_count{voice_context.GetCount()};
34 std::span<const VoiceChannelResource::InParameter> in_params{
35 reinterpret_cast<const VoiceChannelResource::InParameter*>(input), voice_count};
36
37 for (u32 i = 0; i < voice_count; i++) {
38 auto& resource{voice_context.GetChannelResource(i)};
39 resource.in_use = in_params[i].in_use;
40 if (in_params[i].in_use) {
41 resource.mix_volumes = in_params[i].mix_volumes;
42 }
43 }
44
45 const auto consumed_input_size{voice_count *
46 static_cast<u32>(sizeof(VoiceChannelResource::InParameter))};
47 if (consumed_input_size != in_header->voice_resources_size) {
48 LOG_ERROR(Service_Audio,
49 "Consumed an incorrect voice resource size, header size={}, consumed={}",
50 in_header->voice_resources_size, consumed_input_size);
51 return Service::Audio::ERR_INVALID_UPDATE_DATA;
52 }
53
54 input += consumed_input_size;
55 return ResultSuccess;
56}
57
58Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
59 std::span<MemoryPoolInfo> memory_pools,
60 const u32 memory_pool_count) {
61 const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
62 behaviour.IsMemoryForceMappingEnabled());
63 const auto voice_count{voice_context.GetCount()};
64 std::span<const VoiceInfo::InParameter> in_params{
65 reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count};
66 std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output),
67 voice_count};
68
69 for (u32 i = 0; i < voice_count; i++) {
70 auto& voice_info{voice_context.GetInfo(i)};
71 voice_info.in_use = false;
72 }
73
74 u32 new_voice_count{0};
75
76 for (u32 i = 0; i < voice_count; i++) {
77 const auto& in_param{in_params[i]};
78 std::array<VoiceState*, MaxChannels> voice_states{};
79
80 if (!in_param.in_use) {
81 continue;
82 }
83
84 auto& voice_info{voice_context.GetInfo(in_param.id)};
85
86 for (u32 channel = 0; channel < in_param.channel_count; channel++) {
87 voice_states[channel] = &voice_context.GetState(in_param.channel_resource_ids[channel]);
88 }
89
90 if (in_param.is_new) {
91 voice_info.Initialize();
92
93 for (u32 channel = 0; channel < in_param.channel_count; channel++) {
94 std::memset(voice_states[channel], 0, sizeof(VoiceState));
95 }
96 }
97
98 BehaviorInfo::ErrorInfo update_error{};
99 voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour);
100
101 if (!update_error.error_code.IsSuccess()) {
102 behaviour.AppendError(update_error);
103 }
104
105 std::array<std::array<BehaviorInfo::ErrorInfo, 2>, MaxWaveBuffers> wavebuffer_errors{};
106 voice_info.UpdateWaveBuffers(wavebuffer_errors, MaxWaveBuffers * 2, in_param, voice_states,
107 pool_mapper, behaviour);
108
109 for (auto& wavebuffer_error : wavebuffer_errors) {
110 for (auto& error : wavebuffer_error) {
111 if (error.error_code.IsError()) {
112 behaviour.AppendError(error);
113 }
114 }
115 }
116
117 voice_info.WriteOutStatus(out_params[i], in_param, voice_states);
118 new_voice_count += in_param.channel_count;
119 }
120
121 auto consumed_input_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::InParameter))};
122 auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))};
123 if (consumed_input_size != in_header->voices_size) {
124 LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}",
125 in_header->voices_size, consumed_input_size);
126 return Service::Audio::ERR_INVALID_UPDATE_DATA;
127 }
128
129 out_header->voices_size = consumed_output_size;
130 out_header->size += consumed_output_size;
131 input += consumed_input_size;
132 output += consumed_output_size;
133
134 voice_context.SetActiveCount(new_voice_count);
135
136 return ResultSuccess;
137}
138
139Result InfoUpdater::UpdateEffects(EffectContext& effect_context, const bool renderer_active,
140 std::span<MemoryPoolInfo> memory_pools,
141 const u32 memory_pool_count) {
142 if (behaviour.IsEffectInfoVersion2Supported()) {
143 return UpdateEffectsVersion2(effect_context, renderer_active, memory_pools,
144 memory_pool_count);
145 } else {
146 return UpdateEffectsVersion1(effect_context, renderer_active, memory_pools,
147 memory_pool_count);
148 }
149}
150
151Result InfoUpdater::UpdateEffectsVersion1(EffectContext& effect_context, const bool renderer_active,
152 std::span<MemoryPoolInfo> memory_pools,
153 const u32 memory_pool_count) {
154 PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
155 behaviour.IsMemoryForceMappingEnabled());
156
157 const auto effect_count{effect_context.GetCount()};
158
159 std::span<const EffectInfoBase::InParameterVersion1> in_params{
160 reinterpret_cast<const EffectInfoBase::InParameterVersion1*>(input), effect_count};
161 std::span<EffectInfoBase::OutStatusVersion1> out_params{
162 reinterpret_cast<EffectInfoBase::OutStatusVersion1*>(output), effect_count};
163
164 for (u32 i = 0; i < effect_count; i++) {
165 auto effect_info{&effect_context.GetInfo(i)};
166 if (effect_info->GetType() != in_params[i].type) {
167 effect_info->ForceUnmapBuffers(pool_mapper);
168 ResetEffect(effect_info, in_params[i].type);
169 }
170
171 BehaviorInfo::ErrorInfo error_info{};
172 effect_info->Update(error_info, in_params[i], pool_mapper);
173 if (error_info.error_code.IsError()) {
174 behaviour.AppendError(error_info);
175 }
176
177 effect_info->StoreStatus(out_params[i], renderer_active);
178 }
179
180 auto consumed_input_size{effect_count *
181 static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion1))};
182 auto consumed_output_size{effect_count *
183 static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion1))};
184 if (consumed_input_size != in_header->effects_size) {
185 LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}",
186 in_header->effects_size, consumed_input_size);
187 return Service::Audio::ERR_INVALID_UPDATE_DATA;
188 }
189
190 out_header->effects_size = consumed_output_size;
191 out_header->size += consumed_output_size;
192 input += consumed_input_size;
193 output += consumed_output_size;
194
195 return ResultSuccess;
196}
197
198Result InfoUpdater::UpdateEffectsVersion2(EffectContext& effect_context, const bool renderer_active,
199 std::span<MemoryPoolInfo> memory_pools,
200 const u32 memory_pool_count) {
201 PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
202 behaviour.IsMemoryForceMappingEnabled());
203
204 const auto effect_count{effect_context.GetCount()};
205
206 std::span<const EffectInfoBase::InParameterVersion2> in_params{
207 reinterpret_cast<const EffectInfoBase::InParameterVersion2*>(input), effect_count};
208 std::span<EffectInfoBase::OutStatusVersion2> out_params{
209 reinterpret_cast<EffectInfoBase::OutStatusVersion2*>(output), effect_count};
210
211 for (u32 i = 0; i < effect_count; i++) {
212 auto effect_info{&effect_context.GetInfo(i)};
213 if (effect_info->GetType() != in_params[i].type) {
214 effect_info->ForceUnmapBuffers(pool_mapper);
215 ResetEffect(effect_info, in_params[i].type);
216 }
217
218 BehaviorInfo::ErrorInfo error_info{};
219 effect_info->Update(error_info, in_params[i], pool_mapper);
220
221 if (error_info.error_code.IsError()) {
222 behaviour.AppendError(error_info);
223 }
224
225 effect_info->StoreStatus(out_params[i], renderer_active);
226
227 if (in_params[i].is_new) {
228 effect_info->InitializeResultState(effect_context.GetDspSharedResultState(i));
229 effect_info->InitializeResultState(effect_context.GetResultState(i));
230 }
231 effect_info->UpdateResultState(out_params[i].result_state,
232 effect_context.GetResultState(i));
233 }
234
235 auto consumed_input_size{effect_count *
236 static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion2))};
237 auto consumed_output_size{effect_count *
238 static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion2))};
239 if (consumed_input_size != in_header->effects_size) {
240 LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}",
241 in_header->effects_size, consumed_input_size);
242 return Service::Audio::ERR_INVALID_UPDATE_DATA;
243 }
244
245 out_header->effects_size = consumed_output_size;
246 out_header->size += consumed_output_size;
247 input += consumed_input_size;
248 output += consumed_output_size;
249
250 return ResultSuccess;
251}
252
253Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_count,
254 EffectContext& effect_context, SplitterContext& splitter_context) {
255 s32 mix_count{0};
256 u32 consumed_input_size{0};
257
258 if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
259 auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)};
260 mix_count = in_dirty_params->count;
261 input += sizeof(MixInfo::InDirtyParameter);
262 consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) +
263 mix_count * sizeof(MixInfo::InParameter));
264 } else {
265 mix_count = mix_context.GetCount();
266 consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
267 }
268
269 if (mix_buffer_count == 0) {
270 return Service::Audio::ERR_INVALID_UPDATE_DATA;
271 }
272
273 std::span<const MixInfo::InParameter> in_params{
274 reinterpret_cast<const MixInfo::InParameter*>(input), static_cast<size_t>(mix_count)};
275
276 u32 total_buffer_count{0};
277 for (s32 i = 0; i < mix_count; i++) {
278 const auto& params{in_params[i]};
279
280 if (params.in_use) {
281 total_buffer_count += params.buffer_count;
282 if (params.dest_mix_id > static_cast<s32>(mix_context.GetCount()) &&
283 params.dest_mix_id != UnusedMixId && params.mix_id != FinalMixId) {
284 return Service::Audio::ERR_INVALID_UPDATE_DATA;
285 }
286 }
287 }
288
289 if (total_buffer_count > mix_buffer_count) {
290 return Service::Audio::ERR_INVALID_UPDATE_DATA;
291 }
292
293 bool mix_dirty{false};
294 for (s32 i = 0; i < mix_count; i++) {
295 const auto& params{in_params[i]};
296
297 s32 mix_id{i};
298 if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
299 mix_id = params.mix_id;
300 }
301
302 auto mix_info{mix_context.GetInfo(mix_id)};
303 if (mix_info->in_use != params.in_use) {
304 mix_info->in_use = params.in_use;
305 if (!params.in_use) {
306 mix_info->ClearEffectProcessingOrder();
307 }
308 mix_dirty = true;
309 }
310
311 if (params.in_use) {
312 mix_dirty |= mix_info->Update(mix_context.GetEdgeMatrix(), params, effect_context,
313 splitter_context, behaviour);
314 }
315 }
316
317 if (mix_dirty) {
318 if (behaviour.IsSplitterSupported() && splitter_context.UsingSplitter()) {
319 if (!mix_context.TSortInfo(splitter_context)) {
320 return Service::Audio::ERR_INVALID_UPDATE_DATA;
321 }
322 } else {
323 mix_context.SortInfo();
324 }
325 }
326
327 if (consumed_input_size != in_header->mix_size) {
328 LOG_ERROR(Service_Audio, "Consumed an incorrect mixes size, header size={}, consumed={}",
329 in_header->mix_size, consumed_input_size);
330 return Service::Audio::ERR_INVALID_UPDATE_DATA;
331 }
332
333 input += mix_count * sizeof(MixInfo::InParameter);
334
335 return ResultSuccess;
336}
337
338Result InfoUpdater::UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
339 const u32 memory_pool_count) {
340 PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
341 behaviour.IsMemoryForceMappingEnabled());
342
343 std::span<const SinkInfoBase::InParameter> in_params{
344 reinterpret_cast<const SinkInfoBase::InParameter*>(input), memory_pool_count};
345 std::span<SinkInfoBase::OutStatus> out_params{
346 reinterpret_cast<SinkInfoBase::OutStatus*>(output), memory_pool_count};
347
348 const auto sink_count{sink_context.GetCount()};
349
350 for (u32 i = 0; i < sink_count; i++) {
351 const auto& params{in_params[i]};
352 auto sink_info{sink_context.GetInfo(i)};
353
354 if (sink_info->GetType() != params.type) {
355 sink_info->CleanUp();
356 switch (params.type) {
357 case SinkInfoBase::Type::Invalid:
358 std::construct_at<SinkInfoBase>(reinterpret_cast<SinkInfoBase*>(sink_info));
359 break;
360 case SinkInfoBase::Type::DeviceSink:
361 std::construct_at<DeviceSinkInfo>(reinterpret_cast<DeviceSinkInfo*>(sink_info));
362 break;
363 case SinkInfoBase::Type::CircularBufferSink:
364 std::construct_at<CircularBufferSinkInfo>(
365 reinterpret_cast<CircularBufferSinkInfo*>(sink_info));
366 break;
367 default:
368 LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(params.type));
369 break;
370 }
371 }
372
373 BehaviorInfo::ErrorInfo error_info{};
374 sink_info->Update(error_info, out_params[i], params, pool_mapper);
375
376 if (error_info.error_code.IsError()) {
377 behaviour.AppendError(error_info);
378 }
379 }
380
381 const auto consumed_input_size{sink_count *
382 static_cast<u32>(sizeof(SinkInfoBase::InParameter))};
383 const auto consumed_output_size{sink_count * static_cast<u32>(sizeof(SinkInfoBase::OutStatus))};
384 if (consumed_input_size != in_header->sinks_size) {
385 LOG_ERROR(Service_Audio, "Consumed an incorrect sinks size, header size={}, consumed={}",
386 in_header->sinks_size, consumed_input_size);
387 return Service::Audio::ERR_INVALID_UPDATE_DATA;
388 }
389
390 input += consumed_input_size;
391 output += consumed_output_size;
392 out_header->sinks_size = consumed_output_size;
393 out_header->size += consumed_output_size;
394
395 return ResultSuccess;
396}
397
398Result InfoUpdater::UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools,
399 const u32 memory_pool_count) {
400 PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
401 behaviour.IsMemoryForceMappingEnabled());
402 std::span<const MemoryPoolInfo::InParameter> in_params{
403 reinterpret_cast<const MemoryPoolInfo::InParameter*>(input), memory_pool_count};
404 std::span<MemoryPoolInfo::OutStatus> out_params{
405 reinterpret_cast<MemoryPoolInfo::OutStatus*>(output), memory_pool_count};
406
407 for (size_t i = 0; i < memory_pool_count; i++) {
408 auto state{pool_mapper.Update(memory_pools[i], in_params[i], out_params[i])};
409 if (state != MemoryPoolInfo::ResultState::Success &&
410 state != MemoryPoolInfo::ResultState::BadParam &&
411 state != MemoryPoolInfo::ResultState::MapFailed &&
412 state != MemoryPoolInfo::ResultState::InUse) {
413 LOG_WARNING(Service_Audio, "Invalid ResultState from updating memory pools");
414 return Service::Audio::ERR_INVALID_UPDATE_DATA;
415 }
416 }
417
418 const auto consumed_input_size{memory_pool_count *
419 static_cast<u32>(sizeof(MemoryPoolInfo::InParameter))};
420 const auto consumed_output_size{memory_pool_count *
421 static_cast<u32>(sizeof(MemoryPoolInfo::OutStatus))};
422 if (consumed_input_size != in_header->memory_pool_size) {
423 LOG_ERROR(Service_Audio,
424 "Consumed an incorrect memory pool size, header size={}, consumed={}",
425 in_header->memory_pool_size, consumed_input_size);
426 return Service::Audio::ERR_INVALID_UPDATE_DATA;
427 }
428
429 input += consumed_input_size;
430 output += consumed_output_size;
431 out_header->memory_pool_size = consumed_output_size;
432 out_header->size += consumed_output_size;
433 return ResultSuccess;
434}
435
436Result InfoUpdater::UpdatePerformanceBuffer(std::span<u8> performance_output,
437 const u64 performance_output_size,
438 PerformanceManager* performance_manager) {
439 auto in_params{reinterpret_cast<const PerformanceManager::InParameter*>(input)};
440 auto out_params{reinterpret_cast<PerformanceManager::OutStatus*>(output)};
441
442 if (performance_manager != nullptr) {
443 out_params->history_size =
444 performance_manager->CopyHistories(performance_output.data(), performance_output_size);
445 performance_manager->SetDetailTarget(in_params->target_node_id);
446 } else {
447 out_params->history_size = 0;
448 }
449
450 const auto consumed_input_size{static_cast<u32>(sizeof(PerformanceManager::InParameter))};
451 const auto consumed_output_size{static_cast<u32>(sizeof(PerformanceManager::OutStatus))};
452 if (consumed_input_size != in_header->performance_buffer_size) {
453 LOG_ERROR(Service_Audio,
454 "Consumed an incorrect performance size, header size={}, consumed={}",
455 in_header->performance_buffer_size, consumed_input_size);
456 return Service::Audio::ERR_INVALID_UPDATE_DATA;
457 }
458
459 input += consumed_input_size;
460 output += consumed_output_size;
461 out_header->performance_buffer_size = consumed_output_size;
462 out_header->size += consumed_output_size;
463 return ResultSuccess;
464}
465
466Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) {
467 const auto in_params{reinterpret_cast<const BehaviorInfo::InParameter*>(input)};
468
469 if (!CheckValidRevision(in_params->revision)) {
470 return Service::Audio::ERR_INVALID_UPDATE_DATA;
471 }
472
473 if (in_params->revision != behaviour_.GetUserRevision()) {
474 return Service::Audio::ERR_INVALID_UPDATE_DATA;
475 }
476
477 behaviour_.ClearError();
478 behaviour_.UpdateFlags(in_params->flags);
479
480 if (in_header->behaviour_size != sizeof(BehaviorInfo::InParameter)) {
481 return Service::Audio::ERR_INVALID_UPDATE_DATA;
482 }
483
484 input += sizeof(BehaviorInfo::InParameter);
485 return ResultSuccess;
486}
487
488Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) {
489 auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)};
490 behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count);
491
492 const auto consumed_output_size{static_cast<u32>(sizeof(BehaviorInfo::OutStatus))};
493
494 output += consumed_output_size;
495 out_header->behaviour_size = consumed_output_size;
496 out_header->size += consumed_output_size;
497 return ResultSuccess;
498}
499
500Result InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
501 u32 consumed_size{0};
502 if (!splitter_context.Update(input, consumed_size)) {
503 return Service::Audio::ERR_INVALID_UPDATE_DATA;
504 }
505
506 input += consumed_size;
507
508 return ResultSuccess;
509}
510
511Result InfoUpdater::UpdateRendererInfo(const u64 elapsed_frames) {
512 struct RenderInfo {
513 /* 0x00 */ u64 frames_elapsed;
514 /* 0x08 */ char unk08[0x8];
515 };
516 static_assert(sizeof(RenderInfo) == 0x10, "RenderInfo has the wrong size!");
517
518 auto out_params{reinterpret_cast<RenderInfo*>(output)};
519 out_params->frames_elapsed = elapsed_frames;
520
521 const auto consumed_output_size{static_cast<u32>(sizeof(RenderInfo))};
522
523 output += consumed_output_size;
524 out_header->render_info_size = consumed_output_size;
525 out_header->size += consumed_output_size;
526
527 return ResultSuccess;
528}
529
530Result InfoUpdater::CheckConsumedSize() {
531 if (CpuAddr(input) - CpuAddr(input_origin.data()) != expected_input_size) {
532 return Service::Audio::ERR_INVALID_UPDATE_DATA;
533 } else if (CpuAddr(output) - CpuAddr(output_origin.data()) != expected_output_size) {
534 return Service::Audio::ERR_INVALID_UPDATE_DATA;
535 }
536 return ResultSuccess;
537}
538
539} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "common/common_types.h"
9#include "core/hle/service/audio/errors.h"
10
11namespace AudioCore::AudioRenderer {
12class BehaviorInfo;
13class VoiceContext;
14class MixContext;
15class SinkContext;
16class SplitterContext;
17class EffectContext;
18class MemoryPoolInfo;
19class PerformanceManager;
20
21class InfoUpdater {
22 struct UpdateDataHeader {
23 explicit UpdateDataHeader(u32 revision_) : revision{revision_} {}
24
25 /* 0x00 */ u32 revision;
26 /* 0x04 */ u32 behaviour_size{};
27 /* 0x08 */ u32 memory_pool_size{};
28 /* 0x0C */ u32 voices_size{};
29 /* 0x10 */ u32 voice_resources_size{};
30 /* 0x14 */ u32 effects_size{};
31 /* 0x18 */ u32 mix_size{};
32 /* 0x1C */ u32 sinks_size{};
33 /* 0x20 */ u32 performance_buffer_size{};
34 /* 0x24 */ char unk24[4];
35 /* 0x28 */ u32 render_info_size{};
36 /* 0x2C */ char unk2C[0x10];
37 /* 0x3C */ u32 size{sizeof(UpdateDataHeader)};
38 };
39 static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has the wrong size!");
40
41public:
42 explicit InfoUpdater(std::span<const u8> input, std::span<u8> output, u32 process_handle,
43 BehaviorInfo& behaviour);
44
45 /**
46 * Update the voice channel resources.
47 *
48 * @param voice_context - Voice context to update.
49 * @return Result code.
50 */
51 Result UpdateVoiceChannelResources(VoiceContext& voice_context);
52
53 /**
54 * Update voices.
55 *
56 * @param voice_context - Voice context to update.
57 * @param memory_pools - Memory pools to use for these voices.
58 * @param memory_pool_count - Number of memory pools.
59 * @return Result code.
60 */
61 Result UpdateVoices(VoiceContext& voice_context, std::span<MemoryPoolInfo> memory_pools,
62 u32 memory_pool_count);
63
64 /**
65 * Update effects.
66 *
67 * @param effect_context - Effect context to update.
68 * @param renderer_active - Whether the AudioRenderer is active.
69 * @param memory_pools - Memory pools to use for these voices.
70 * @param memory_pool_count - Number of memory pools.
71 * @return Result code.
72 */
73 Result UpdateEffects(EffectContext& effect_context, bool renderer_active,
74 std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
75
76 /**
77 * Update mixes.
78 *
79 * @param mix_context - Mix context to update.
80 * @param mix_buffer_count - Number of mix buffers.
81 * @param effect_context - Effect context to update effort order.
82 * @param splitter_context - Splitter context for the mixes.
83 * @return Result code.
84 */
85 Result UpdateMixes(MixContext& mix_context, u32 mix_buffer_count, EffectContext& effect_context,
86 SplitterContext& splitter_context);
87
88 /**
89 * Update sinks.
90 *
91 * @param sink_context - Sink context to update.
92 * @param memory_pools - Memory pools to use for these voices.
93 * @param memory_pool_count - Number of memory pools.
94 * @return Result code.
95 */
96 Result UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
97 u32 memory_pool_count);
98
99 /**
100 * Update memory pools.
101 *
102 * @param memory_pools - Memory pools to use for these voices.
103 * @param memory_pool_count - Number of memory pools.
104 * @return Result code.
105 */
106 Result UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
107
108 /**
109 * Update the performance buffer.
110 *
111 * @param output - Output buffer for performance metrics.
112 * @param output_size - Output buffer size.
113 * @param performance_manager - Performance manager..
114 * @return Result code.
115 */
116 Result UpdatePerformanceBuffer(std::span<u8> output, u64 output_size,
117 PerformanceManager* performance_manager);
118
119 /**
120 * Update behaviour.
121 *
122 * @param behaviour - Behaviour to update.
123 * @return Result code.
124 */
125 Result UpdateBehaviorInfo(BehaviorInfo& behaviour);
126
127 /**
128 * Update errors.
129 *
130 * @param behaviour - Behaviour to update.
131 * @return Result code.
132 */
133 Result UpdateErrorInfo(BehaviorInfo& behaviour);
134
135 /**
136 * Update splitter.
137 *
138 * @param splitter_context - Splitter context to update.
139 * @return Result code.
140 */
141 Result UpdateSplitterInfo(SplitterContext& splitter_context);
142
143 /**
144 * Update renderer info.
145 *
146 * @param elapsed_frames - Number of elapsed frames.
147 * @return Result code.
148 */
149 Result UpdateRendererInfo(u64 elapsed_frames);
150
151 /**
152 * Check that the input.output sizes match their expected values.
153 *
154 * @return Result code.
155 */
156 Result CheckConsumedSize();
157
158private:
159 /**
160 * Update effects version 1.
161 *
162 * @param effect_context - Effect context to update.
163 * @param renderer_active - Is the AudioRenderer active?
164 * @param memory_pools - Memory pools to use for these voices.
165 * @param memory_pool_count - Number of memory pools.
166 * @return Result code.
167 */
168 Result UpdateEffectsVersion1(EffectContext& effect_context, bool renderer_active,
169 std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
170
171 /**
172 * Update effects version 2.
173 *
174 * @param effect_context - Effect context to update.
175 * @param renderer_active - Is the AudioRenderer active?
176 * @param memory_pools - Memory pools to use for these voices.
177 * @param memory_pool_count - Number of memory pools.
178 * @return Result code.
179 */
180 Result UpdateEffectsVersion2(EffectContext& effect_context, bool renderer_active,
181 std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
182
183 /// Input buffer
184 u8 const* input;
185 /// Input buffer start
186 std::span<const u8> input_origin;
187 /// Output buffer start
188 u8* output;
189 /// Output buffer start
190 std::span<u8> output_origin;
191 /// Input header
192 const UpdateDataHeader* in_header;
193 /// Output header
194 UpdateDataHeader* out_header;
195 /// Expected input size, see CheckConsumedSize
196 u64 expected_input_size;
197 /// Expected output size, see CheckConsumedSize
198 u64 expected_output_size;
199 /// Unused
200 u32 process_handle;
201 /// Behaviour
202 BehaviorInfo& behaviour;
203};
204
205} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/behavior/behavior_info.h"
5#include "audio_core/renderer/command/command_buffer.h"
6#include "audio_core/renderer/command/command_list_header.h"
7#include "audio_core/renderer/command/command_processing_time_estimator.h"
8#include "audio_core/renderer/effect/biquad_filter.h"
9#include "audio_core/renderer/effect/delay.h"
10#include "audio_core/renderer/effect/reverb.h"
11#include "audio_core/renderer/memory/memory_pool_info.h"
12#include "audio_core/renderer/mix/mix_info.h"
13#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
14#include "audio_core/renderer/sink/device_sink_info.h"
15#include "audio_core/renderer/sink/sink_info_base.h"
16#include "audio_core/renderer/voice/voice_info.h"
17#include "audio_core/renderer/voice/voice_state.h"
18
19namespace AudioCore::AudioRenderer {
20
21template <typename T, CommandId Id>
22T& CommandBuffer::GenerateStart(const s32 node_id) {
23 if (size + sizeof(T) >= command_list.size_bytes()) {
24 LOG_ERROR(
25 Service_Audio,
26 "Attempting to write commands beyond the end of allocated command buffer memory!");
27 UNREACHABLE();
28 }
29
30 auto& cmd{*std::construct_at<T>(reinterpret_cast<T*>(&command_list[size]))};
31
32 cmd.magic = CommandMagic;
33 cmd.enabled = true;
34 cmd.type = Id;
35 cmd.size = sizeof(T);
36 cmd.node_id = node_id;
37
38 return cmd;
39}
40
41template <typename T>
42void CommandBuffer::GenerateEnd(T& cmd) {
43 cmd.estimated_process_time = time_estimator->Estimate(cmd);
44 estimated_process_time += cmd.estimated_process_time;
45 size += sizeof(T);
46 count++;
47}
48
49void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id,
50 const MemoryPoolInfo& memory_pool_,
51 VoiceInfo& voice_info,
52 const VoiceState& voice_state,
53 const s16 buffer_count, const s8 channel) {
54 auto& cmd{
55 GenerateStart<PcmInt16DataSourceVersion1Command, CommandId::DataSourcePcmInt16Version1>(
56 node_id)};
57
58 cmd.src_quality = voice_info.src_quality;
59 cmd.output_index = buffer_count + channel;
60 cmd.flags = voice_info.flags & 3;
61 cmd.sample_rate = voice_info.sample_rate;
62 cmd.pitch = voice_info.pitch;
63 cmd.channel_index = channel;
64 cmd.channel_count = voice_info.channel_count;
65
66 for (u32 i = 0; i < MaxWaveBuffers; i++) {
67 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
68 }
69
70 cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
71
72 GenerateEnd<PcmInt16DataSourceVersion1Command>(cmd);
73}
74
75void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info,
76 const VoiceState& voice_state,
77 const s16 buffer_count, const s8 channel) {
78 auto& cmd{
79 GenerateStart<PcmInt16DataSourceVersion2Command, CommandId::DataSourcePcmInt16Version2>(
80 node_id)};
81
82 cmd.src_quality = voice_info.src_quality;
83 cmd.output_index = buffer_count + channel;
84 cmd.flags = voice_info.flags & 3;
85 cmd.sample_rate = voice_info.sample_rate;
86 cmd.pitch = voice_info.pitch;
87 cmd.channel_index = channel;
88 cmd.channel_count = voice_info.channel_count;
89
90 for (u32 i = 0; i < MaxWaveBuffers; i++) {
91 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
92 }
93
94 cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
95
96 GenerateEnd<PcmInt16DataSourceVersion2Command>(cmd);
97}
98
99void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id,
100 const MemoryPoolInfo& memory_pool_,
101 VoiceInfo& voice_info,
102 const VoiceState& voice_state,
103 const s16 buffer_count, const s8 channel) {
104 auto& cmd{
105 GenerateStart<PcmFloatDataSourceVersion1Command, CommandId::DataSourcePcmFloatVersion1>(
106 node_id)};
107
108 cmd.src_quality = voice_info.src_quality;
109 cmd.output_index = buffer_count + channel;
110 cmd.flags = voice_info.flags & 3;
111 cmd.sample_rate = voice_info.sample_rate;
112 cmd.pitch = voice_info.pitch;
113 cmd.channel_index = channel;
114 cmd.channel_count = voice_info.channel_count;
115
116 for (u32 i = 0; i < MaxWaveBuffers; i++) {
117 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
118 }
119
120 cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
121
122 GenerateEnd<PcmFloatDataSourceVersion1Command>(cmd);
123}
124
125void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info,
126 const VoiceState& voice_state,
127 const s16 buffer_count, const s8 channel) {
128 auto& cmd{
129 GenerateStart<PcmFloatDataSourceVersion2Command, CommandId::DataSourcePcmFloatVersion2>(
130 node_id)};
131
132 cmd.src_quality = voice_info.src_quality;
133 cmd.output_index = buffer_count + channel;
134 cmd.flags = voice_info.flags & 3;
135 cmd.sample_rate = voice_info.sample_rate;
136 cmd.pitch = voice_info.pitch;
137 cmd.channel_index = channel;
138 cmd.channel_count = voice_info.channel_count;
139
140 for (u32 i = 0; i < MaxWaveBuffers; i++) {
141 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
142 }
143
144 cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
145
146 GenerateEnd<PcmFloatDataSourceVersion2Command>(cmd);
147}
148
149void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id,
150 const MemoryPoolInfo& memory_pool_,
151 VoiceInfo& voice_info,
152 const VoiceState& voice_state,
153 const s16 buffer_count, const s8 channel) {
154 auto& cmd{
155 GenerateStart<AdpcmDataSourceVersion1Command, CommandId::DataSourceAdpcmVersion1>(node_id)};
156
157 cmd.src_quality = voice_info.src_quality;
158 cmd.output_index = buffer_count + channel;
159 cmd.flags = voice_info.flags & 3;
160 cmd.sample_rate = voice_info.sample_rate;
161 cmd.pitch = voice_info.pitch;
162
163 for (u32 i = 0; i < MaxWaveBuffers; i++) {
164 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
165 }
166
167 cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
168 cmd.data_address = voice_info.data_address.GetReference(true);
169 cmd.data_size = voice_info.data_address.GetSize();
170
171 GenerateEnd<AdpcmDataSourceVersion1Command>(cmd);
172}
173
174void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info,
175 const VoiceState& voice_state,
176 const s16 buffer_count, const s8 channel) {
177 auto& cmd{
178 GenerateStart<AdpcmDataSourceVersion2Command, CommandId::DataSourceAdpcmVersion2>(node_id)};
179
180 cmd.src_quality = voice_info.src_quality;
181 cmd.output_index = buffer_count + channel;
182 cmd.flags = voice_info.flags & 3;
183 cmd.sample_rate = voice_info.sample_rate;
184 cmd.pitch = voice_info.pitch;
185 cmd.channel_index = channel;
186 cmd.channel_count = voice_info.channel_count;
187
188 for (u32 i = 0; i < MaxWaveBuffers; i++) {
189 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
190 }
191
192 cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
193 cmd.data_address = voice_info.data_address.GetReference(true);
194 cmd.data_size = voice_info.data_address.GetSize();
195
196 GenerateEnd<AdpcmDataSourceVersion2Command>(cmd);
197}
198
199void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset,
200 const s16 input_index, const f32 volume,
201 const u8 precision) {
202 auto& cmd{GenerateStart<VolumeCommand, CommandId::Volume>(node_id)};
203
204 cmd.precision = precision;
205 cmd.input_index = buffer_offset + input_index;
206 cmd.output_index = buffer_offset + input_index;
207 cmd.volume = volume;
208
209 GenerateEnd<VolumeCommand>(cmd);
210}
211
212void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info,
213 const s16 buffer_count, const u8 precision) {
214 auto& cmd{GenerateStart<VolumeRampCommand, CommandId::VolumeRamp>(node_id)};
215
216 cmd.input_index = buffer_count;
217 cmd.output_index = buffer_count;
218 cmd.prev_volume = voice_info.prev_volume;
219 cmd.volume = voice_info.volume;
220 cmd.precision = precision;
221
222 GenerateEnd<VolumeRampCommand>(cmd);
223}
224
225void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
226 const VoiceState& voice_state,
227 const s16 buffer_count, const s8 channel,
228 const u32 biquad_index,
229 const bool use_float_processing) {
230 auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
231
232 cmd.input = buffer_count + channel;
233 cmd.output = buffer_count + channel;
234
235 cmd.biquad = voice_info.biquads[biquad_index];
236
237 cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()),
238 MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
239
240 cmd.needs_init = !voice_info.biquad_initialized[biquad_index];
241 cmd.use_float_processing = use_float_processing;
242
243 GenerateEnd<BiquadFilterCommand>(cmd);
244}
245
246void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info,
247 const s16 buffer_offset, const s8 channel,
248 const bool needs_init,
249 const bool use_float_processing) {
250 auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
251
252 const auto& parameter{
253 *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
254 const auto state{
255 reinterpret_cast<VoiceState::BiquadFilterState*>(effect_info.GetStateBuffer())};
256
257 cmd.input = buffer_offset + parameter.inputs[channel];
258 cmd.output = buffer_offset + parameter.outputs[channel];
259
260 cmd.biquad.b = parameter.b;
261 cmd.biquad.a = parameter.a;
262
263 cmd.state = memory_pool->Translate(CpuAddr(state),
264 MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
265
266 cmd.needs_init = needs_init;
267 cmd.use_float_processing = use_float_processing;
268
269 GenerateEnd<BiquadFilterCommand>(cmd);
270}
271
272void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index,
273 const s16 output_index, const s16 buffer_offset,
274 const f32 volume, const u8 precision) {
275 auto& cmd{GenerateStart<MixCommand, CommandId::Mix>(node_id)};
276
277 cmd.input_index = input_index;
278 cmd.output_index = output_index;
279 cmd.volume = volume;
280 cmd.precision = precision;
281
282 GenerateEnd<MixCommand>(cmd);
283}
284
285void CommandBuffer::GenerateMixRampCommand(const s32 node_id,
286 [[maybe_unused]] const s16 buffer_count,
287 const s16 input_index, const s16 output_index,
288 const f32 volume, const f32 prev_volume,
289 const CpuAddr prev_samples, const u8 precision) {
290 if (volume == 0.0f && prev_volume == 0.0f) {
291 return;
292 }
293
294 auto& cmd{GenerateStart<MixRampCommand, CommandId::MixRamp>(node_id)};
295
296 cmd.input_index = input_index;
297 cmd.output_index = output_index;
298 cmd.prev_volume = prev_volume;
299 cmd.volume = volume;
300 cmd.previous_sample = prev_samples;
301 cmd.precision = precision;
302
303 GenerateEnd<MixRampCommand>(cmd);
304}
305
306void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count,
307 const s16 input_index, s16 output_index,
308 std::span<const f32> volumes,
309 std::span<const f32> prev_volumes,
310 const CpuAddr prev_samples, const u8 precision) {
311 auto& cmd{GenerateStart<MixRampGroupedCommand, CommandId::MixRampGrouped>(node_id)};
312
313 cmd.buffer_count = buffer_count;
314
315 for (s32 i = 0; i < buffer_count; i++) {
316 cmd.inputs[i] = input_index;
317 cmd.outputs[i] = output_index++;
318 cmd.prev_volumes[i] = prev_volumes[i];
319 cmd.volumes[i] = volumes[i];
320 }
321
322 cmd.previous_samples = prev_samples;
323 cmd.precision = precision;
324
325 GenerateEnd<MixRampGroupedCommand>(cmd);
326}
327
328void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state,
329 std::span<const s32> buffer, const s16 buffer_count,
330 s16 buffer_offset, const bool was_playing) {
331 auto& cmd{GenerateStart<DepopPrepareCommand, CommandId::DepopPrepare>(node_id)};
332
333 cmd.enabled = was_playing;
334
335 for (u32 i = 0; i < MaxMixBuffers; i++) {
336 cmd.inputs[i] = buffer_offset++;
337 }
338
339 cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()),
340 MaxMixBuffers * sizeof(s32));
341 cmd.buffer_count = buffer_count;
342 cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer_count * sizeof(s32));
343
344 GenerateEnd<DepopPrepareCommand>(cmd);
345}
346
347void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info,
348 std::span<const s32> depop_buffer) {
349 auto& cmd{GenerateStart<DepopForMixBuffersCommand, CommandId::DepopForMixBuffers>(node_id)};
350
351 cmd.input = mix_info.buffer_offset;
352 cmd.count = mix_info.buffer_count;
353 cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f;
354 cmd.depop_buffer =
355 memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32));
356
357 GenerateEnd<DepopForMixBuffersCommand>(cmd);
358}
359
360void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info,
361 const s16 buffer_offset) {
362 auto& cmd{GenerateStart<DelayCommand, CommandId::Delay>(node_id)};
363
364 const auto& parameter{
365 *reinterpret_cast<DelayInfo::ParameterVersion1*>(effect_info.GetParameter())};
366 const auto state{effect_info.GetStateBuffer()};
367
368 if (IsChannelCountValid(parameter.channel_count)) {
369 const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))};
370 if (state_buffer) {
371 for (s16 channel = 0; channel < parameter.channel_count; channel++) {
372 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
373 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
374 }
375
376 if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) {
377 UseOldChannelMapping(cmd.inputs, cmd.outputs);
378 }
379
380 cmd.parameter = parameter;
381 cmd.effect_enabled = effect_info.IsEnabled();
382 cmd.state = state_buffer;
383 cmd.workbuffer = effect_info.GetWorkbuffer(-1);
384 }
385 }
386
387 GenerateEnd<DelayCommand>(cmd);
388}
389
390void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset,
391 UpsamplerInfo& upsampler_info, const u32 input_count,
392 std::span<const s8> inputs, const s16 buffer_count,
393 const u32 sample_count_, const u32 sample_rate_) {
394 auto& cmd{GenerateStart<UpsampleCommand, CommandId::Upsample>(node_id)};
395
396 cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos,
397 upsampler_info.sample_count * sizeof(s32));
398 cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels);
399 cmd.buffer_count = buffer_count;
400 cmd.unk_20 = 0;
401 cmd.source_sample_count = sample_count_;
402 cmd.source_sample_rate = sample_rate_;
403
404 upsampler_info.input_count = input_count;
405 for (u32 i = 0; i < input_count; i++) {
406 upsampler_info.inputs[i] = buffer_offset + inputs[i];
407 }
408
409 cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo));
410
411 GenerateEnd<UpsampleCommand>(cmd);
412}
413
414void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span<const s8> inputs,
415 const s16 buffer_offset,
416 std::span<const f32> downmix_coeff) {
417 auto& cmd{GenerateStart<DownMix6chTo2chCommand, CommandId::DownMix6chTo2ch>(node_id)};
418
419 for (u32 i = 0; i < MaxChannels; i++) {
420 cmd.inputs[i] = buffer_offset + inputs[i];
421 cmd.outputs[i] = buffer_offset + inputs[i];
422 }
423
424 for (u32 i = 0; i < 4; i++) {
425 cmd.down_mix_coeff[i] = downmix_coeff[i];
426 }
427
428 GenerateEnd<DownMix6chTo2chCommand>(cmd);
429}
430
431void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info,
432 const s16 input_index, const s16 output_index,
433 const s16 buffer_offset, const u32 update_count,
434 const u32 count_max, const u32 write_offset) {
435 auto& cmd{GenerateStart<AuxCommand, CommandId::Aux>(node_id)};
436
437 if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
438 cmd.input = buffer_offset + input_index;
439 cmd.output = buffer_offset + output_index;
440 cmd.send_buffer_info = effect_info.GetSendBufferInfo();
441 cmd.send_buffer = effect_info.GetSendBuffer();
442 cmd.return_buffer_info = effect_info.GetReturnBufferInfo();
443 cmd.return_buffer = effect_info.GetReturnBuffer();
444 cmd.count_max = count_max;
445 cmd.write_offset = write_offset;
446 cmd.update_count = update_count;
447 cmd.effect_enabled = effect_info.IsEnabled();
448 }
449
450 GenerateEnd<AuxCommand>(cmd);
451}
452
453void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset,
454 SinkInfoBase& sink_info, const u32 session_id,
455 std::span<s32> samples_buffer) {
456 auto& cmd{GenerateStart<DeviceSinkCommand, CommandId::DeviceSink>(node_id)};
457 const auto& parameter{
458 *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
459 auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
460
461 cmd.session_id = session_id;
462
463 if (state.upsampler_info != nullptr) {
464 const auto size_{state.upsampler_info->sample_count * parameter.input_count};
465 const auto size_bytes{size_ * sizeof(s32)};
466 const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)};
467 cmd.sample_buffer = {reinterpret_cast<s32*>(addr),
468 parameter.input_count * state.upsampler_info->sample_count};
469 } else {
470 cmd.sample_buffer = samples_buffer;
471 }
472
473 cmd.input_count = parameter.input_count;
474 for (u32 i = 0; i < parameter.input_count; i++) {
475 cmd.inputs[i] = buffer_offset + parameter.inputs[i];
476 }
477
478 GenerateEnd<DeviceSinkCommand>(cmd);
479}
480
481void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info,
482 const s16 buffer_offset) {
483 auto& cmd{GenerateStart<CircularBufferSinkCommand, CommandId::CircularBufferSink>(node_id)};
484 const auto& parameter{*reinterpret_cast<CircularBufferSinkInfo::CircularBufferInParameter*>(
485 sink_info.GetParameter())};
486 auto state{
487 *reinterpret_cast<CircularBufferSinkInfo::CircularBufferState*>(sink_info.GetState())};
488
489 cmd.input_count = parameter.input_count;
490 for (u32 i = 0; i < parameter.input_count; i++) {
491 cmd.inputs[i] = buffer_offset + parameter.inputs[i];
492 }
493
494 cmd.address = state.address_info.GetReference(true);
495 cmd.size = parameter.size;
496 cmd.pos = state.current_pos;
497
498 GenerateEnd<CircularBufferSinkCommand>(cmd);
499}
500
501void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
502 const s16 buffer_offset,
503 const bool long_size_pre_delay_supported) {
504 auto& cmd{GenerateStart<ReverbCommand, CommandId::Reverb>(node_id)};
505
506 const auto& parameter{
507 *reinterpret_cast<ReverbInfo::ParameterVersion2*>(effect_info.GetParameter())};
508 const auto state{effect_info.GetStateBuffer()};
509
510 if (IsChannelCountValid(parameter.channel_count)) {
511 const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))};
512 if (state_buffer) {
513 for (s16 channel = 0; channel < parameter.channel_count; channel++) {
514 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
515 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
516 }
517
518 if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) {
519 UseOldChannelMapping(cmd.inputs, cmd.outputs);
520 }
521
522 cmd.parameter = parameter;
523 cmd.effect_enabled = effect_info.IsEnabled();
524 cmd.state = state_buffer;
525 cmd.workbuffer = effect_info.GetWorkbuffer(-1);
526 cmd.long_size_pre_delay_supported = long_size_pre_delay_supported;
527 }
528 }
529
530 GenerateEnd<ReverbCommand>(cmd);
531}
532
533void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
534 const s16 buffer_offset) {
535 auto& cmd{GenerateStart<I3dl2ReverbCommand, CommandId::I3dl2Reverb>(node_id)};
536
537 const auto& parameter{
538 *reinterpret_cast<I3dl2ReverbInfo::ParameterVersion1*>(effect_info.GetParameter())};
539 const auto state{effect_info.GetStateBuffer()};
540
541 if (IsChannelCountValid(parameter.channel_count)) {
542 const auto state_buffer{
543 memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))};
544 if (state_buffer) {
545 for (s16 channel = 0; channel < parameter.channel_count; channel++) {
546 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
547 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
548 }
549
550 if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) {
551 UseOldChannelMapping(cmd.inputs, cmd.outputs);
552 }
553
554 cmd.parameter = parameter;
555 cmd.effect_enabled = effect_info.IsEnabled();
556 cmd.state = state_buffer;
557 cmd.workbuffer = effect_info.GetWorkbuffer(-1);
558 }
559 }
560
561 GenerateEnd<I3dl2ReverbCommand>(cmd);
562}
563
564void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state,
565 const PerformanceEntryAddresses& entry_addresses) {
566 auto& cmd{GenerateStart<PerformanceCommand, CommandId::Performance>(node_id)};
567
568 cmd.state = state;
569 cmd.entry_address = entry_addresses;
570
571 GenerateEnd<PerformanceCommand>(cmd);
572}
573
574void CommandBuffer::GenerateClearMixCommand(const s32 node_id) {
575 auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)};
576 GenerateEnd<ClearMixBufferCommand>(cmd);
577}
578
579void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info,
580 const s16 buffer_offset, const s8 channel) {
581 auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)};
582
583 const auto& parameter{
584 *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
585 cmd.input_index = buffer_offset + parameter.inputs[channel];
586 cmd.output_index = buffer_offset + parameter.outputs[channel];
587
588 GenerateEnd<CopyMixBufferCommand>(cmd);
589}
590
591void CommandBuffer::GenerateLightLimiterCommand(
592 const s32 node_id, const s16 buffer_offset,
593 const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state,
594 const bool enabled, const CpuAddr workbuffer) {
595 auto& cmd{GenerateStart<LightLimiterVersion1Command, CommandId::LightLimiterVersion1>(node_id)};
596
597 if (IsChannelCountValid(parameter.channel_count)) {
598 const auto state_buffer{
599 memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
600 if (state_buffer) {
601 for (s8 channel = 0; channel < parameter.channel_count; channel++) {
602 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
603 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
604 }
605
606 std::memcpy(&cmd.parameter, &parameter, sizeof(LightLimiterInfo::ParameterVersion1));
607 cmd.effect_enabled = enabled;
608 cmd.state = state_buffer;
609 cmd.workbuffer = workbuffer;
610 }
611 }
612
613 GenerateEnd<LightLimiterVersion1Command>(cmd);
614}
615
616void CommandBuffer::GenerateLightLimiterCommand(
617 const s32 node_id, const s16 buffer_offset,
618 const LightLimiterInfo::ParameterVersion2& parameter,
619 const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state,
620 const bool enabled, const CpuAddr workbuffer) {
621 auto& cmd{GenerateStart<LightLimiterVersion2Command, CommandId::LightLimiterVersion2>(node_id)};
622 if (IsChannelCountValid(parameter.channel_count)) {
623 const auto state_buffer{
624 memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
625 if (state_buffer) {
626 for (s8 channel = 0; channel < parameter.channel_count; channel++) {
627 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
628 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
629 }
630
631 cmd.parameter = parameter;
632 cmd.effect_enabled = enabled;
633 cmd.state = state_buffer;
634 if (cmd.parameter.statistics_enabled) {
635 cmd.result_state = memory_pool->Translate(
636 CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal));
637 } else {
638 cmd.result_state = 0;
639 }
640 cmd.workbuffer = workbuffer;
641 }
642 }
643
644 GenerateEnd<LightLimiterVersion2Command>(cmd);
645}
646
647void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
648 const VoiceState& voice_state,
649 const s16 buffer_count, const s8 channel) {
650 auto& cmd{GenerateStart<MultiTapBiquadFilterCommand, CommandId::MultiTapBiquadFilter>(node_id)};
651
652 cmd.input = buffer_count + channel;
653 cmd.output = buffer_count + channel;
654 cmd.biquads = voice_info.biquads;
655
656 cmd.states[0] =
657 memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()),
658 MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
659 cmd.states[1] =
660 memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()),
661 MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
662
663 cmd.needs_init[0] = !voice_info.biquad_initialized[0];
664 cmd.needs_init[1] = !voice_info.biquad_initialized[1];
665 cmd.filter_tap_count = MaxBiquadFilters;
666
667 GenerateEnd<MultiTapBiquadFilterCommand>(cmd);
668}
669
670void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info,
671 const s16 input_index, const s16 output_index,
672 const s16 buffer_offset, const u32 update_count,
673 const u32 count_max, const u32 write_offset) {
674 auto& cmd{GenerateStart<CaptureCommand, CommandId::Capture>(node_id)};
675
676 if (effect_info.GetSendBuffer()) {
677 cmd.input = buffer_offset + input_index;
678 cmd.output = buffer_offset + output_index;
679 cmd.send_buffer_info = effect_info.GetSendBufferInfo();
680 cmd.send_buffer = effect_info.GetSendBuffer();
681 cmd.count_max = count_max;
682 cmd.write_offset = write_offset;
683 cmd.update_count = update_count;
684 cmd.effect_enabled = effect_info.IsEnabled();
685 }
686
687 GenerateEnd<CaptureCommand>(cmd);
688}
689
690void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info,
691 s32 node_id) {
692 auto& cmd{GenerateStart<CompressorCommand, CommandId::Compressor>(node_id)};
693
694 auto& parameter{
695 *reinterpret_cast<CompressorInfo::ParameterVersion2*>(effect_info.GetParameter())};
696 auto state{reinterpret_cast<CompressorInfo::State*>(effect_info.GetStateBuffer())};
697
698 if (IsChannelCountValid(parameter.channel_count)) {
699 auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))};
700 if (state_buffer) {
701 for (u16 channel = 0; channel < parameter.channel_count; channel++) {
702 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
703 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
704 }
705 cmd.parameter = parameter;
706 cmd.workbuffer = state_buffer;
707 cmd.enabled = effect_info.IsEnabled();
708 }
709 }
710
711 GenerateEnd<CompressorCommand>(cmd);
712}
713
714} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/command/commands.h"
9#include "audio_core/renderer/effect/light_limiter.h"
10#include "audio_core/renderer/performance/performance_manager.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14struct UpsamplerInfo;
15struct VoiceState;
16class EffectInfoBase;
17class ICommandProcessingTimeEstimator;
18class MixInfo;
19class MemoryPoolInfo;
20class SinkInfoBase;
21class VoiceInfo;
22
23/**
24 * Utility functions to generate and add commands into the current command list.
25 */
26class CommandBuffer {
27public:
28 /**
29 * Generate a PCM s16 version 1 command, adding it to the command list.
30 *
31 * @param node_id - Node id of the voice this command is generated for.
32 * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
33 * @param voice_info - The voice info this command is generated from.
34 * @param voice_state - The voice state the DSP will use for this command.
35 * @param buffer_count - Number of mix buffers in use,
36 * data will be read into this index + channel.
37 * @param channel - Channel index for this command.
38 */
39 void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
40 VoiceInfo& voice_info, const VoiceState& voice_state,
41 s16 buffer_count, s8 channel);
42
43 /**
44 * Generate a PCM s16 version 2 command, adding it to the command list.
45 *
46 * @param node_id - Node id of the voice this command is generated for.
47 * @param voice_info - The voice info this command is generated from.
48 * @param voice_state - The voice state the DSP will use for this command.
49 * @param buffer_count - Number of mix buffers in use,
50 * data will be read into this index + channel.
51 * @param channel - Channel index for this command.
52 */
53 void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info,
54 const VoiceState& voice_state, s16 buffer_count,
55 s8 channel);
56
57 /**
58 * Generate a PCM f32 version 1 command, adding it to the command list.
59 *
60 * @param node_id - Node id of the voice this command is generated for.
61 * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
62 * @param voice_info - The voice info this command is generated from.
63 * @param voice_state - The voice state the DSP will use for this command.
64 * @param buffer_count - Number of mix buffers in use,
65 * data will be read into this index + channel.
66 * @param channel - Channel index for this command.
67 */
68 void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
69 VoiceInfo& voice_info, const VoiceState& voice_state,
70 s16 buffer_count, s8 channel);
71
72 /**
73 * Generate a PCM f32 version 2 command, adding it to the command list.
74 *
75 * @param node_id - Node id of the voice this command is generated for.
76 * @param voice_info - The voice info this command is generated from.
77 * @param voice_state - The voice state the DSP will use for this command.
78 * @param buffer_count - Number of mix buffers in use,
79 * data will be read into this index + channel.
80 * @param channel - Channel index for this command.
81 */
82 void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info,
83 const VoiceState& voice_state, s16 buffer_count,
84 s8 channel);
85
86 /**
87 * Generate an ADPCM version 1 command, adding it to the command list.
88 *
89 * @param node_id - Node id of the voice this command is generated for.
90 * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
91 * @param voice_info - The voice info this command is generated from.
92 * @param voice_state - The voice state the DSP will use for this command.
93 * @param buffer_count - Number of mix buffers in use,
94 * data will be read into this index + channel.
95 * @param channel - Channel index for this command.
96 */
97 void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
98 VoiceInfo& voice_info, const VoiceState& voice_state,
99 s16 buffer_count, s8 channel);
100
101 /**
102 * Generate an ADPCM version 2 command, adding it to the command list.
103 *
104 * @param node_id - Node id of the voice this command is generated for.
105 * @param voice_info - The voice info this command is generated from.
106 * @param voice_state - The voice state the DSP will use for this command.
107 * @param buffer_count - Number of mix buffers in use,
108 * data will be read into this index + channel.
109 * @param channel - Channel index for this command.
110 */
111 void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info,
112 const VoiceState& voice_state, s16 buffer_count, s8 channel);
113
114 /**
115 * Generate a volume command, adding it to the command list.
116 *
117 * @param node_id - Node id of the voice this command is generated for.
118 * @param buffer_offset - Base mix buffer index to generate this command at.
119 * @param input_index - Channel index and mix buffer offset for this command.
120 * @param volume - Mix volume added to the input samples.
121 * @param precision - Number of decimal bits for fixed point operations.
122 */
123 void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume,
124 u8 precision);
125
126 /**
127 * Generate a volume ramp command, adding it to the command list.
128 *
129 * @param node_id - Node id of the voice this command is generated for.
130 * @param voice_info - The voice info this command takes its volumes from.
131 * @param buffer_count - Number of active mix buffers, command will generate at this index.
132 * @param precision - Number of decimal bits for fixed point operations.
133 */
134 void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count,
135 u8 precision);
136
137 /**
138 * Generate a biquad filter command from a voice, adding it to the command list.
139 *
140 * @param node_id - Node id of the voice this command is generated for.
141 * @param voice_info - The voice info this command takes biquad parameters from.
142 * @param voice_state - Used by the AudioRenderer to track previous samples.
143 * @param buffer_count - Number of active mix buffers,
144 * command will generate at this index + channel.
145 * @param channel - Channel index for this filter to work on.
146 * @param biquad_index - Which biquad filter to use for this command (0-1).
147 * @param use_float_processing - Should int or float processing be used?
148 */
149 void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
150 const VoiceState& voice_state, s16 buffer_count, s8 channel,
151 u32 biquad_index, bool use_float_processing);
152
153 /**
154 * Generate a biquad filter effect command, adding it to the command list.
155 *
156 * @param node_id - Node id of the voice this command is generated for.
157 * @param effect_info - The effect info this command takes biquad parameters from.
158 * @param buffer_offset - Mix buffer offset this command will use,
159 * command will generate at this index + channel.
160 * @param channel - Channel index for this filter to work on.
161 * @param needs_init - True if the biquad state needs initialisation.
162 * @param use_float_processing - Should int or float processing be used?
163 */
164 void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
165 s8 channel, bool needs_init, bool use_float_processing);
166
167 /**
168 * Generate a mix command, adding it to the command list.
169 *
170 * @param node_id - Node id of the voice this command is generated for.
171 * @param input_index - Input mix buffer index for this command.
172 * Added to the buffer offset.
173 * @param output_index - Output mix buffer index for this command.
174 * Added to the buffer offset.
175 * @param buffer_offset - Mix buffer offset this command will use.
176 * @param volume - Volume to be applied to the input.
177 * @param precision - Number of decimal bits for fixed point operations.
178 */
179 void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset,
180 f32 volume, u8 precision);
181
182 /**
183 * Generate a mix ramp command, adding it to the command list.
184 *
185 * @param node_id - Node id of the voice this command is generated for.
186 * @param buffer_count - Number of active mix buffers.
187 * @param input_index - Input mix buffer index for this command.
188 * Added to buffer_count.
189 * @param output_index - Output mix buffer index for this command.
190 * Added to buffer_count.
191 * @param volume - Current mix volume used for calculating the ramp.
192 * @param prev_volume - Previous mix volume, used for calculating the ramp,
193 * also applied to the input.
194 * @param precision - Number of decimal bits for fixed point operations.
195 */
196 void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
197 f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision);
198
199 /**
200 * Generate a mix ramp grouped command, adding it to the command list.
201 *
202 * @param node_id - Node id of the voice this command is generated for.
203 * @param buffer_count - Number of active mix buffers.
204 * @param input_index - Input mix buffer index for this command.
205 * Added to buffer_count.
206 * @param output_index - Output mix buffer index for this command.
207 * Added to buffer_count.
208 * @param volumes - Current mix volumes used for calculating the ramp.
209 * @param prev_volumes - Previous mix volumes, used for calculating the ramp,
210 * also applied to the input.
211 * @param precision - Number of decimal bits for fixed point operations.
212 */
213 void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
214 s16 output_index, std::span<const f32> volumes,
215 std::span<const f32> prev_volumes, CpuAddr prev_samples,
216 u8 precision);
217
218 /**
219 * Generate a depop prepare command, adding it to the command list.
220 *
221 * @param node_id - Node id of the voice this command is generated for.
222 * @param voice_state - State to track the previous depop samples for each mix buffer.
223 * @param buffer - State to track the current depop samples for each mix buffer.
224 * @param buffer_count - Number of active mix buffers.
225 * @param buffer_offset - Base mix buffer index to generate the channel depops at.
226 * @param was_playing - Command only needs to work if the voice was previously playing.
227 */
228 void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state,
229 std::span<const s32> buffer, s16 buffer_count,
230 s16 buffer_offset, bool was_playing);
231
232 /**
233 * Generate a depop command, adding it to the command list.
234 *
235 * @param node_id - Node id of the voice this command is generated for.
236 * @param mix_info - Mix info to get the buffer count and base offsets from.
237 * @param depop_buffer - Buffer of current depop sample values to be added to the input
238 * channels.
239 */
240 void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info,
241 std::span<const s32> depop_buffer);
242
243 /**
244 * Generate a delay command, adding it to the command list.
245 *
246 * @param node_id - Node id of the voice this command is generated for.
247 * @param effect_info - Delay effect info to generate this command from.
248 * @param buffer_offset - Base mix buffer offset to apply the apply the delay.
249 */
250 void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
251
252 /**
253 * Generate an upsample command, adding it to the command list.
254 *
255 * @param node_id - Node id of the voice this command is generated for.
256 * @param buffer_offset - Base mix buffer offset to upsample.
257 * @param upsampler_info - Upsampler info to control the upsampling.
258 * @param input_count - Number of input channels to upsample.
259 * @param inputs - Input mix buffer indexes.
260 * @param buffer_count - Number of active mix buffers.
261 * @param sample_count - Source sample count of the input.
262 * @param sample_rate - Source sample rate of the input.
263 */
264 void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info,
265 u32 input_count, std::span<const s8> inputs, s16 buffer_count,
266 u32 sample_count, u32 sample_rate);
267
268 /**
269 * Generate a downmix 6 -> 2 command, adding it to the command list.
270 *
271 * @param node_id - Node id of the voice this command is generated for.
272 * @param inputs - Input mix buffer indexes.
273 * @param buffer_offset - Base mix buffer offset of the channels to downmix.
274 * @param downmix_coeff - Downmixing coefficients.
275 */
276 void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset,
277 std::span<const f32> downmix_coeff);
278
279 /**
280 * Generate an aux buffer command, adding it to the command list.
281 *
282 * @param node_id - Node id of the voice this command is generated for.
283 * @param effect_info - Aux effect info to generate this command from.
284 * @param input_index - Input mix buffer index for this command.
285 * Added to buffer_offset.
286 * @param output_index - Output mix buffer index for this command.
287 * Added to buffer_offset.
288 * @param buffer_offset - Base mix buffer offset to use.
289 * @param update_count - Number of samples to write back to the game as updated, can be 0.
290 * @param count_max - Maximum number of samples to read or write.
291 * @param write_offset - Current read or write offset within the buffer.
292 */
293 void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
294 s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max,
295 u32 write_offset);
296
297 /**
298 * Generate a device sink command, adding it to the command list.
299 *
300 * @param node_id - Node id of the voice this command is generated for.
301 * @param buffer_offset - Base mix buffer offset to use.
302 * @param sink_info - The sink_info to generate this command from.
303 * @session_id - System session id this command is generated from.
304 * @samples_buffer - The buffer to be sent to the sink if upsampling is not used.
305 */
306 void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
307 u32 session_id, std::span<s32> samples_buffer);
308
309 /**
310 * Generate a circular buffer sink command, adding it to the command list.
311 *
312 * @param node_id - Node id of the voice this command is generated for.
313 * @param sink_info - The sink_info to generate this command from.
314 * @param buffer_offset - Base mix buffer offset to use.
315 */
316 void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset);
317
318 /**
319 * Generate a reverb command, adding it to the command list.
320 *
321 * @param node_id - Node id of the voice this command is generated for.
322 * @param effect_info - Reverb effect info to generate this command from.
323 * @param buffer_offset - Base mix buffer offset to use.
324 * @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb
325 * begins?
326 */
327 void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
328 bool long_size_pre_delay_supported);
329
330 /**
331 * Generate an I3DL2 reverb command, adding it to the command list.
332 *
333 * @param node_id - Node id of the voice this command is generated for.
334 * @param effect_info - I3DL2Reverb effect info to generate this command from.
335 * @param buffer_offset - Base mix buffer offset to use.
336 */
337 void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
338
339 /**
340 * Generate a performance command, adding it to the command list.
341 *
342 * @param node_id - Node id of the voice this command is generated for.
343 * @param state - State of the performance.
344 * @param entry_addresses - The addresses to be filled in by the AudioRenderer.
345 */
346 void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
347 const PerformanceEntryAddresses& entry_addresses);
348
349 /**
350 * Generate a clear mix command, adding it to the command list.
351 *
352 * @param node_id - Node id of the voice this command is generated for.
353 */
354 void GenerateClearMixCommand(s32 node_id);
355
356 /**
357 * Generate a copy mix command, adding it to the command list.
358 *
359 * @param node_id - Node id of the voice this command is generated for.
360 * @param effect_info - BiquadFilter effect info to generate this command from.
361 * @param buffer_offset - Base mix buffer offset to use.
362 * @param channel - Index to the effect's parameters input indexes for this command.
363 */
364 void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
365 s8 channel);
366
367 /**
368 * Generate a light limiter version 1 command, adding it to the command list.
369 *
370 * @param node_id - Node id of the voice this command is generated for.
371 * @param buffer_offset - Base mix buffer offset to use.
372 * @param parameter - Effect parameter to generate from.
373 * @param state - State used by the AudioRenderer between commands.
374 * @param enabled - Is this command enabled?
375 * @param workbuffer - Game-supplied memory for the state.
376 */
377 void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
378 const LightLimiterInfo::ParameterVersion1& parameter,
379 const LightLimiterInfo::State& state, bool enabled,
380 CpuAddr workbuffer);
381
382 /**
383 * Generate a light limiter version 2 command, adding it to the command list.
384 *
385 * @param node_id - Node id of the voice this command is generated for.
386 * @param buffer_offset - Base mix buffer offset to use.
387 * @param parameter - Effect parameter to generate from.
388 * @param statistics - Statistics reported by the AudioRenderer on the limiter's state.
389 * @param state - State used by the AudioRenderer between commands.
390 * @param enabled - Is this command enabled?
391 * @param workbuffer - Game-supplied memory for the state.
392 */
393 void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
394 const LightLimiterInfo::ParameterVersion2& parameter,
395 const LightLimiterInfo::StatisticsInternal& statistics,
396 const LightLimiterInfo::State& state, bool enabled,
397 CpuAddr workbuffer);
398
399 /**
400 * Generate a multitap biquad filter command, adding it to the command list.
401 *
402 * @param node_id - Node id of the voice this command is generated for.
403 * @param voice_info - The voice info this command takes biquad parameters from.
404 * @param voice_state - Used by the AudioRenderer to track previous samples.
405 * @param buffer_count - Number of active mix buffers,
406 * command will generate at this index + channel.
407 * @param channel - Channel index for this filter to work on.
408 */
409 void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
410 const VoiceState& voice_state, s16 buffer_count,
411 s8 channel);
412
413 /**
414 * Generate a capture command, adding it to the command list.
415 *
416 * @param node_id - Node id of the voice this command is generated for.
417 * @param effect_info - Capture effect info to generate this command from.
418 * @param input_index - Input mix buffer index for this command.
419 * Added to buffer_offset.
420 * @param output_index - Output mix buffer index for this command (unused).
421 * Added to buffer_offset.
422 * @param buffer_offset - Base mix buffer offset to use.
423 * @param update_count - Number of samples to write back to the game as updated, can be 0.
424 * @param count_max - Maximum number of samples to read or write.
425 * @param write_offset - Current read or write offset within the buffer.
426 */
427 void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
428 s16 output_index, s16 buffer_offset, u32 update_count,
429 u32 count_max, u32 write_offset);
430
431 /**
432 * Generate a compressor command, adding it to the command list.
433 *
434 * @param buffer_offset - Base mix buffer offset to use.
435 * @param effect_info - Capture effect info to generate this command from.
436 * @param node_id - Node id of the voice this command is generated for.
437 */
438 void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
439
440 /// Command list buffer generated commands will be added to
441 std::span<u8> command_list{};
442 /// Input sample count, unused
443 u32 sample_count{};
444 /// Input sample rate, unused
445 u32 sample_rate{};
446 /// Current size of the command buffer
447 u64 size{};
448 /// Current number of commands added
449 u32 count{};
450 /// Current estimated processing time for all commands
451 u32 estimated_process_time{};
452 /// Used for mapping buffers for the AudioRenderer
453 MemoryPoolInfo* memory_pool{};
454 /// Used for estimating command process times
455 ICommandProcessingTimeEstimator* time_estimator{};
456 /// Used to check which rendering features are currently enabled
457 BehaviorInfo* behavior{};
458
459private:
460 template <typename T, CommandId Id>
461 T& GenerateStart(const s32 node_id);
462 template <typename T>
463 void GenerateEnd(T& cmd);
464};
465
466} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/common/audio_renderer_parameter.h"
5#include "audio_core/renderer/behavior/behavior_info.h"
6#include "audio_core/renderer/command/command_buffer.h"
7#include "audio_core/renderer/command/command_generator.h"
8#include "audio_core/renderer/command/command_list_header.h"
9#include "audio_core/renderer/effect/aux_.h"
10#include "audio_core/renderer/effect/biquad_filter.h"
11#include "audio_core/renderer/effect/buffer_mixer.h"
12#include "audio_core/renderer/effect/capture.h"
13#include "audio_core/renderer/effect/effect_context.h"
14#include "audio_core/renderer/effect/light_limiter.h"
15#include "audio_core/renderer/mix/mix_context.h"
16#include "audio_core/renderer/performance/detail_aspect.h"
17#include "audio_core/renderer/performance/entry_aspect.h"
18#include "audio_core/renderer/sink/device_sink_info.h"
19#include "audio_core/renderer/sink/sink_context.h"
20#include "audio_core/renderer/splitter/splitter_context.h"
21#include "audio_core/renderer/voice/voice_context.h"
22#include "common/alignment.h"
23
24namespace AudioCore::AudioRenderer {
25
26CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_,
27 const CommandListHeader& command_list_header_,
28 const AudioRendererSystemContext& render_context_,
29 VoiceContext& voice_context_, MixContext& mix_context_,
30 EffectContext& effect_context_, SinkContext& sink_context_,
31 SplitterContext& splitter_context_,
32 PerformanceManager* performance_manager_)
33 : command_buffer{command_buffer_}, command_header{command_list_header_},
34 render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_},
35 effect_context{effect_context_}, sink_context{sink_context_},
36 splitter_context{splitter_context_}, performance_manager{performance_manager_} {
37 command_buffer.GenerateClearMixCommand(InvalidNodeId);
38}
39
40void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info,
41 const VoiceState& voice_state, const s8 channel) {
42 if (voice_info.mix_id == UnusedMixId) {
43 if (voice_info.splitter_id != UnusedSplitterId) {
44 auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)};
45 u32 dest_id{0};
46 while (destination != nullptr) {
47 if (destination->IsConfigured()) {
48 auto mix_id{destination->GetMixId()};
49 if (mix_id < mix_context.GetCount()) {
50 auto mix_info{mix_context.GetInfo(mix_id)};
51 command_buffer.GenerateDepopPrepareCommand(
52 voice_info.node_id, voice_state, render_context.depop_buffer,
53 mix_info->buffer_count, mix_info->buffer_offset,
54 voice_info.was_playing);
55 }
56 }
57 dest_id++;
58 destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id);
59 }
60 }
61 } else {
62 auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
63 command_buffer.GenerateDepopPrepareCommand(
64 voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count,
65 mix_info->buffer_offset, voice_info.was_playing);
66 }
67
68 if (voice_info.was_playing) {
69 return;
70 }
71
72 if (render_context.behavior->IsWaveBufferVer2Supported()) {
73 switch (voice_info.sample_format) {
74 case SampleFormat::PcmInt16:
75 command_buffer.GeneratePcmInt16Version2Command(
76 voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
77 channel);
78 break;
79 case SampleFormat::PcmFloat:
80 command_buffer.GeneratePcmFloatVersion2Command(
81 voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
82 channel);
83 break;
84 case SampleFormat::Adpcm:
85 command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state,
86 render_context.mix_buffer_count, channel);
87 break;
88 default:
89 LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
90 static_cast<u32>(voice_info.sample_format));
91 break;
92 }
93 } else {
94 switch (voice_info.sample_format) {
95 case SampleFormat::PcmInt16:
96 command_buffer.GeneratePcmInt16Version1Command(
97 voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
98 render_context.mix_buffer_count, channel);
99 break;
100 case SampleFormat::PcmFloat:
101 command_buffer.GeneratePcmFloatVersion1Command(
102 voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
103 render_context.mix_buffer_count, channel);
104 break;
105 case SampleFormat::Adpcm:
106 command_buffer.GenerateAdpcmVersion1Command(
107 voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
108 render_context.mix_buffer_count, channel);
109 break;
110 default:
111 LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
112 static_cast<u32>(voice_info.sample_format));
113 break;
114 }
115 }
116}
117
118void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
119 std::span<const f32> prev_mix_volumes,
120 const VoiceState& voice_state, s16 output_index,
121 const s16 buffer_count, const s16 input_index,
122 const s32 node_id) {
123 u8 precision{15};
124 if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
125 precision = 23;
126 }
127
128 if (buffer_count > 8) {
129 const auto prev_samples{render_context.memory_pool_info->Translate(
130 CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))};
131 command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index,
132 output_index, mix_volumes, prev_mix_volumes,
133 prev_samples, precision);
134 } else {
135 for (s16 i = 0; i < buffer_count; i++, output_index++) {
136 const auto prev_samples{render_context.memory_pool_info->Translate(
137 CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))};
138
139 command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index,
140 mix_volumes[i], prev_mix_volumes[i], prev_samples,
141 precision);
142 }
143 }
144}
145
146void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info,
147 const VoiceState& voice_state,
148 const s16 buffer_count, const s8 channel,
149 const s32 node_id) {
150 const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled};
151 const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()};
152
153 if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() &&
154 use_float_processing) {
155 command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state,
156 buffer_count, channel);
157 } else {
158 for (u32 i = 0; i < MaxBiquadFilters; i++) {
159 if (voice_info.biquads[i].enabled) {
160 command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state,
161 buffer_count, channel, i,
162 use_float_processing);
163 }
164 }
165 }
166}
167
168void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) {
169 u8 precision{15};
170 if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
171 precision = 23;
172 }
173
174 for (s8 channel = 0; channel < voice_info.channel_count; channel++) {
175 const auto resource_id{voice_info.channel_resource_ids[channel]};
176 auto& voice_state{voice_context.GetDspSharedState(resource_id)};
177 auto& channel_resource{voice_context.GetChannelResource(resource_id)};
178
179 PerformanceDetailType detail_type{PerformanceDetailType::Invalid};
180 switch (voice_info.sample_format) {
181 case SampleFormat::PcmInt16:
182 detail_type = PerformanceDetailType::Unk1;
183 break;
184 case SampleFormat::PcmFloat:
185 detail_type = PerformanceDetailType::Unk10;
186 break;
187 default:
188 detail_type = PerformanceDetailType::Unk2;
189 break;
190 }
191
192 DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id,
193 detail_type);
194 GenerateDataSourceCommand(voice_info, voice_state, channel);
195
196 if (data_source_detail.initialized) {
197 command_buffer.GeneratePerformanceCommand(data_source_detail.node_id,
198 PerformanceState::Stop,
199 data_source_detail.performance_entry_address);
200 }
201
202 if (voice_info.was_playing) {
203 voice_info.prev_volume = 0.0f;
204 continue;
205 }
206
207 if (!voice_info.HasAnyConnection()) {
208 continue;
209 }
210
211 DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id,
212 PerformanceDetailType::Unk4);
213 GenerateBiquadFilterCommandForVoice(
214 voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id);
215
216 if (biquad_detail_aspect.initialized) {
217 command_buffer.GeneratePerformanceCommand(
218 biquad_detail_aspect.node_id, PerformanceState::Stop,
219 biquad_detail_aspect.performance_entry_address);
220 }
221
222 DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice,
223 voice_info.node_id, PerformanceDetailType::Unk3);
224 command_buffer.GenerateVolumeRampCommand(
225 voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision);
226 if (volume_ramp_detail_aspect.initialized) {
227 command_buffer.GeneratePerformanceCommand(
228 volume_ramp_detail_aspect.node_id, PerformanceState::Stop,
229 volume_ramp_detail_aspect.performance_entry_address);
230 }
231
232 voice_info.prev_volume = voice_info.volume;
233
234 if (voice_info.mix_id == UnusedMixId) {
235 if (voice_info.splitter_id != UnusedSplitterId) {
236 auto i{channel};
237 auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)};
238 while (destination != nullptr) {
239 if (destination->IsConfigured()) {
240 const auto mix_id{destination->GetMixId()};
241 if (mix_id < mix_context.GetCount() &&
242 static_cast<s32>(mix_id) != UnusedSplitterId) {
243 auto mix_info{mix_context.GetInfo(mix_id)};
244 GenerateVoiceMixCommand(
245 destination->GetMixVolume(), destination->GetMixVolumePrev(),
246 voice_state, mix_info->buffer_offset, mix_info->buffer_count,
247 render_context.mix_buffer_count + channel, voice_info.node_id);
248 destination->MarkAsNeedToUpdateInternalState();
249 }
250 }
251 i += voice_info.channel_count;
252 destination = splitter_context.GetDesintationData(voice_info.splitter_id, i);
253 }
254 }
255 } else {
256 DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice,
257 voice_info.node_id, PerformanceDetailType::Unk3);
258 auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
259 GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes,
260 voice_state, mix_info->buffer_offset, mix_info->buffer_count,
261 render_context.mix_buffer_count + channel, voice_info.node_id);
262 if (volume_mix_detail_aspect.initialized) {
263 command_buffer.GeneratePerformanceCommand(
264 volume_mix_detail_aspect.node_id, PerformanceState::Stop,
265 volume_mix_detail_aspect.performance_entry_address);
266 }
267
268 channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
269 }
270 voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled;
271 voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled;
272 }
273}
274
275void CommandGenerator::GenerateVoiceCommands() {
276 const auto voice_count{voice_context.GetCount()};
277
278 for (u32 i = 0; i < voice_count; i++) {
279 auto sorted_info{voice_context.GetSortedInfo(i)};
280
281 if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) {
282 continue;
283 }
284
285 EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id);
286
287 GenerateVoiceCommand(*sorted_info);
288
289 if (voice_entry_aspect.initialized) {
290 command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id,
291 PerformanceState::Stop,
292 voice_entry_aspect.performance_entry_address);
293 }
294 }
295
296 splitter_context.UpdateInternalState();
297}
298
299void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset,
300 EffectInfoBase& effect_info, const s32 node_id) {
301 u8 precision{15};
302 if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
303 precision = 23;
304 }
305
306 if (effect_info.IsEnabled()) {
307 const auto& parameter{
308 *reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())};
309 for (u32 i = 0; i < parameter.mix_count; i++) {
310 if (parameter.volumes[i] != 0.0f) {
311 command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i],
312 buffer_offset + parameter.outputs[i],
313 buffer_offset, parameter.volumes[i], precision);
314 }
315 }
316 }
317}
318
319void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
320 const s32 node_id) {
321 command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset);
322}
323
324void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
325 const s32 node_id,
326 const bool long_size_pre_delay_supported) {
327 command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset,
328 long_size_pre_delay_supported);
329}
330
331void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset,
332 EffectInfoBase& effect_info,
333 const s32 node_id) {
334 command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset);
335}
336
337void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
338 const s32 node_id) {
339
340 if (effect_info.IsEnabled()) {
341 effect_info.GetWorkbuffer(0);
342 effect_info.GetWorkbuffer(1);
343 }
344
345 if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
346 const auto& parameter{
347 *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
348 auto channel_index{parameter.mix_buffer_count - 1};
349 u32 write_offset{0};
350 for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
351 auto new_update_count{command_header.sample_count + write_offset};
352 const auto update_count{channel_index > 0 ? 0 : new_update_count};
353 command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i],
354 parameter.outputs[i], buffer_offset, update_count,
355 parameter.count_max, write_offset);
356 write_offset = new_update_count;
357 }
358 }
359}
360
361void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset,
362 EffectInfoBase& effect_info,
363 const s32 node_id) {
364 const auto& parameter{
365 *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
366 if (effect_info.IsEnabled()) {
367 bool needs_init{false};
368
369 switch (parameter.state) {
370 case EffectInfoBase::ParameterState::Initialized:
371 needs_init = true;
372 break;
373 case EffectInfoBase::ParameterState::Updating:
374 case EffectInfoBase::ParameterState::Updated:
375 if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) {
376 needs_init = false;
377 } else {
378 needs_init = parameter.state == EffectInfoBase::ParameterState::Updating;
379 }
380 break;
381 default:
382 LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}",
383 static_cast<u32>(parameter.state));
384 break;
385 }
386
387 for (s8 channel = 0; channel < parameter.channel_count; channel++) {
388 command_buffer.GenerateBiquadFilterCommand(
389 node_id, effect_info, buffer_offset, channel, needs_init,
390 render_context.behavior->UseBiquadFilterFloatProcessing());
391 }
392 } else {
393 for (s8 channel = 0; channel < parameter.channel_count; channel++) {
394 command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset,
395 channel);
396 }
397 }
398}
399
400void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset,
401 EffectInfoBase& effect_info,
402 const s32 node_id,
403 const u32 effect_index) {
404
405 const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())};
406
407 if (render_context.behavior->IsEffectInfoVersion2Supported()) {
408 const auto& parameter{
409 *reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())};
410 const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(
411 &effect_context.GetDspSharedResultState(effect_index))};
412 command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state,
413 state, effect_info.IsEnabled(),
414 effect_info.GetWorkbuffer(-1));
415 } else {
416 const auto& parameter{
417 *reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())};
418 command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state,
419 effect_info.IsEnabled(),
420 effect_info.GetWorkbuffer(-1));
421 }
422}
423
424void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
425 const s32 node_id) {
426 if (effect_info.IsEnabled()) {
427 effect_info.GetWorkbuffer(0);
428 }
429
430 if (effect_info.GetSendBuffer()) {
431 const auto& parameter{
432 *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
433 auto channel_index{parameter.mix_buffer_count - 1};
434 u32 write_offset{0};
435 for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
436 auto new_update_count{command_header.sample_count + write_offset};
437 const auto update_count{channel_index > 0 ? 0 : new_update_count};
438 command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i],
439 parameter.outputs[i], buffer_offset, update_count,
440 parameter.count_max, write_offset);
441 write_offset = new_update_count;
442 }
443 }
444}
445
446void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset,
447 EffectInfoBase& effect_info, const s32 node_id) {
448 command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id);
449}
450
451void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) {
452 const auto effect_count{effect_context.GetCount()};
453 for (u32 i = 0; i < effect_count; i++) {
454 const auto effect_index{mix_info.effect_order_buffer[i]};
455 if (effect_index == -1) {
456 break;
457 }
458
459 auto& effect_info = effect_context.GetInfo(effect_index);
460 if (effect_info.ShouldSkip()) {
461 continue;
462 }
463
464 const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix
465 : PerformanceEntryType::SubMix};
466
467 switch (effect_info.GetType()) {
468 case EffectInfoBase::Type::Mix: {
469 DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id,
470 PerformanceDetailType::Unk5);
471 GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
472 if (mix_detail_aspect.initialized) {
473 command_buffer.GeneratePerformanceCommand(
474 mix_detail_aspect.node_id, PerformanceState::Stop,
475 mix_detail_aspect.performance_entry_address);
476 }
477 } break;
478
479 case EffectInfoBase::Type::Aux: {
480 DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id,
481 PerformanceDetailType::Unk7);
482 GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
483 if (aux_detail_aspect.initialized) {
484 command_buffer.GeneratePerformanceCommand(
485 aux_detail_aspect.node_id, PerformanceState::Stop,
486 aux_detail_aspect.performance_entry_address);
487 }
488 } break;
489
490 case EffectInfoBase::Type::Delay: {
491 DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id,
492 PerformanceDetailType::Unk6);
493 GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
494 if (delay_detail_aspect.initialized) {
495 command_buffer.GeneratePerformanceCommand(
496 delay_detail_aspect.node_id, PerformanceState::Stop,
497 delay_detail_aspect.performance_entry_address);
498 }
499 } break;
500
501 case EffectInfoBase::Type::Reverb: {
502 DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id,
503 PerformanceDetailType::Unk8);
504 GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
505 render_context.behavior->IsLongSizePreDelaySupported());
506 if (reverb_detail_aspect.initialized) {
507 command_buffer.GeneratePerformanceCommand(
508 reverb_detail_aspect.node_id, PerformanceState::Stop,
509 reverb_detail_aspect.performance_entry_address);
510 }
511 } break;
512
513 case EffectInfoBase::Type::I3dl2Reverb: {
514 DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id,
515 PerformanceDetailType::Unk9);
516 GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
517 if (i3dl2_detail_aspect.initialized) {
518 command_buffer.GeneratePerformanceCommand(
519 i3dl2_detail_aspect.node_id, PerformanceState::Stop,
520 i3dl2_detail_aspect.performance_entry_address);
521 }
522 } break;
523
524 case EffectInfoBase::Type::BiquadFilter: {
525 DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id,
526 PerformanceDetailType::Unk4);
527 GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info,
528 mix_info.node_id);
529 if (biquad_detail_aspect.initialized) {
530 command_buffer.GeneratePerformanceCommand(
531 biquad_detail_aspect.node_id, PerformanceState::Stop,
532 biquad_detail_aspect.performance_entry_address);
533 }
534 } break;
535
536 case EffectInfoBase::Type::LightLimiter: {
537 DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id,
538 PerformanceDetailType::Unk11);
539 GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
540 effect_index);
541 if (light_limiter_detail_aspect.initialized) {
542 command_buffer.GeneratePerformanceCommand(
543 light_limiter_detail_aspect.node_id, PerformanceState::Stop,
544 light_limiter_detail_aspect.performance_entry_address);
545 }
546 } break;
547
548 case EffectInfoBase::Type::Capture: {
549 DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
550 PerformanceDetailType::Unk12);
551 GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
552 if (capture_detail_aspect.initialized) {
553 command_buffer.GeneratePerformanceCommand(
554 capture_detail_aspect.node_id, PerformanceState::Stop,
555 capture_detail_aspect.performance_entry_address);
556 }
557 } break;
558
559 case EffectInfoBase::Type::Compressor: {
560 DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
561 PerformanceDetailType::Unk13);
562 GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
563 if (capture_detail_aspect.initialized) {
564 command_buffer.GeneratePerformanceCommand(
565 capture_detail_aspect.node_id, PerformanceState::Stop,
566 capture_detail_aspect.performance_entry_address);
567 }
568 } break;
569
570 default:
571 LOG_ERROR(Service_Audio, "Invalid effect type {}",
572 static_cast<u32>(effect_info.GetType()));
573 break;
574 }
575
576 effect_info.UpdateForCommandGeneration();
577 }
578}
579
580void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) {
581 u8 precision{15};
582 if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
583 precision = 23;
584 }
585
586 if (!mix_info.HasAnyConnection()) {
587 return;
588 }
589
590 if (mix_info.dst_mix_id == UnusedMixId) {
591 if (mix_info.dst_splitter_id != UnusedSplitterId) {
592 s16 dest_id{0};
593 auto destination{
594 splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)};
595 while (destination != nullptr) {
596 if (destination->IsConfigured()) {
597 auto splitter_mix_id{destination->GetMixId()};
598 if (splitter_mix_id < mix_context.GetCount()) {
599 auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)};
600 const s16 input_index{static_cast<s16>(mix_info.buffer_offset +
601 (dest_id % mix_info.buffer_count))};
602 for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) {
603 auto volume{mix_info.volume * destination->GetMixVolume(i)};
604 if (volume != 0.0f) {
605 command_buffer.GenerateMixCommand(
606 mix_info.node_id, input_index,
607 splitter_mix_info->buffer_offset + i, mix_info.buffer_offset,
608 volume, precision);
609 }
610 }
611 }
612 }
613 dest_id++;
614 destination =
615 splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id);
616 }
617 }
618 } else {
619 auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)};
620 for (s16 i = 0; i < mix_info.buffer_count; i++) {
621 for (s16 j = 0; j < dest_mix_info->buffer_count; j++) {
622 auto volume{mix_info.volume * mix_info.mix_volumes[i][j]};
623 if (volume != 0.0f) {
624 command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i,
625 dest_mix_info->buffer_offset + j,
626 mix_info.buffer_offset, volume, precision);
627 }
628 }
629 }
630 }
631}
632
633void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) {
634 command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info,
635 render_context.depop_buffer);
636 GenerateEffectCommand(mix_info);
637
638 DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id,
639 PerformanceDetailType::Unk5);
640
641 GenerateMixCommands(mix_info);
642
643 if (mix_detail_aspect.initialized) {
644 command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop,
645 mix_detail_aspect.performance_entry_address);
646 }
647}
648
649void CommandGenerator::GenerateSubMixCommands() {
650 const auto submix_count{mix_context.GetCount()};
651 for (s32 i = 0; i < submix_count; i++) {
652 auto sorted_info{mix_context.GetSortedInfo(i)};
653 if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) {
654 continue;
655 }
656
657 EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id);
658
659 GenerateSubMixCommand(*sorted_info);
660
661 if (submix_entry_aspect.initialized) {
662 command_buffer.GeneratePerformanceCommand(
663 submix_entry_aspect.node_id, PerformanceState::Stop,
664 submix_entry_aspect.performance_entry_address);
665 }
666 }
667}
668
669void CommandGenerator::GenerateFinalMixCommand() {
670 auto& final_mix_info{*mix_context.GetFinalMixInfo()};
671
672 command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info,
673 render_context.depop_buffer);
674 GenerateEffectCommand(final_mix_info);
675
676 u8 precision{15};
677 if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
678 precision = 23;
679 }
680
681 for (s16 i = 0; i < final_mix_info.buffer_count; i++) {
682 DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id,
683 PerformanceDetailType::Unk3);
684 command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset,
685 i, final_mix_info.volume, precision);
686 if (volume_aspect.initialized) {
687 command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop,
688 volume_aspect.performance_entry_address);
689 }
690 }
691}
692
693void CommandGenerator::GenerateFinalMixCommands() {
694 auto final_mix_info{mix_context.GetFinalMixInfo()};
695 EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id);
696 GenerateFinalMixCommand();
697 if (final_mix_entry.initialized) {
698 command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop,
699 final_mix_entry.performance_entry_address);
700 }
701}
702
703void CommandGenerator::GenerateSinkCommands() {
704 const auto sink_count{sink_context.GetCount()};
705
706 for (u32 i = 0; i < sink_count; i++) {
707 auto sink_info{sink_context.GetInfo(i)};
708 if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) {
709 auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())};
710 if (command_header.sample_rate != TargetSampleRate &&
711 state->upsampler_info == nullptr) {
712 auto device_state{sink_info->GetDeviceState()};
713 device_state->upsampler_info = render_context.upsampler_manager->Allocate();
714 }
715
716 EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink,
717 sink_info->GetNodeId());
718 auto final_mix{mix_context.GetFinalMixInfo()};
719 GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
720
721 if (device_sink_entry.initialized) {
722 command_buffer.GeneratePerformanceCommand(
723 device_sink_entry.node_id, PerformanceState::Stop,
724 device_sink_entry.performance_entry_address);
725 }
726 }
727 }
728
729 for (u32 i = 0; i < sink_count; i++) {
730 auto sink_info{sink_context.GetInfo(i)};
731 if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) {
732 EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink,
733 sink_info->GetNodeId());
734 auto final_mix{mix_context.GetFinalMixInfo()};
735 GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
736
737 if (circular_buffer_entry.initialized) {
738 command_buffer.GeneratePerformanceCommand(
739 circular_buffer_entry.node_id, PerformanceState::Stop,
740 circular_buffer_entry.performance_entry_address);
741 }
742 }
743 }
744}
745
746void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
747 if (sink_info.ShouldSkip()) {
748 return;
749 }
750
751 switch (sink_info.GetType()) {
752 case SinkInfoBase::Type::DeviceSink:
753 GenerateDeviceSinkCommand(buffer_offset, sink_info);
754 break;
755
756 case SinkInfoBase::Type::CircularBufferSink:
757 command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info,
758 buffer_offset);
759 break;
760
761 default:
762 LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType()));
763 break;
764 }
765
766 sink_info.UpdateForCommandGeneration();
767}
768
769void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
770 auto& parameter{
771 *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
772 auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
773
774 if (render_context.channels == 2 && parameter.downmix_enabled) {
775 command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs,
776 buffer_offset, parameter.downmix_coeff);
777 }
778
779 if (state.upsampler_info != nullptr) {
780 command_buffer.GenerateUpsampleCommand(
781 InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count,
782 parameter.inputs, command_header.buffer_count, command_header.sample_count,
783 command_header.sample_rate);
784 }
785
786 command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info,
787 render_context.session_id,
788 command_header.samples_buffer);
789}
790
791void CommandGenerator::GeneratePerformanceCommand(
792 s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) {
793 command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses);
794}
795
796} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/command/commands.h"
9#include "audio_core/renderer/performance/performance_manager.h"
10#include "common/common_types.h"
11
12namespace AudioCore {
13struct AudioRendererSystemContext;
14
15namespace AudioRenderer {
16class CommandBuffer;
17struct CommandListHeader;
18class VoiceContext;
19class MixContext;
20class EffectContext;
21class SplitterContext;
22class SinkContext;
23class BehaviorInfo;
24class VoiceInfo;
25struct VoiceState;
26class MixInfo;
27class SinkInfoBase;
28
29/**
30 * Generates all commands to build up a command list, which are sent to the AudioRender for
31 * processing.
32 */
33class CommandGenerator {
34public:
35 explicit CommandGenerator(CommandBuffer& command_buffer,
36 const CommandListHeader& command_list_header,
37 const AudioRendererSystemContext& render_context,
38 VoiceContext& voice_context, MixContext& mix_context,
39 EffectContext& effect_context, SinkContext& sink_context,
40 SplitterContext& splitter_context,
41 PerformanceManager* performance_manager);
42
43 /**
44 * Calculate the buffer size needed for commands.
45 *
46 * @param behavior - Used to check what features are enabled.
47 * @param params - Input rendering parameters for numbers of voices/mixes/sinks etc.
48 */
49 static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior,
50 const AudioRendererParameterInternal& params) {
51 u64 size{0};
52
53 // Effects
54 size += params.effects * sizeof(EffectInfoBase);
55
56 // Voices
57 u64 voice_size{0};
58 if (behavior.IsWaveBufferVer2Supported()) {
59 voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command),
60 sizeof(PcmInt16DataSourceVersion2Command)),
61 sizeof(PcmFloatDataSourceVersion2Command));
62 } else {
63 voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command),
64 sizeof(PcmInt16DataSourceVersion1Command)),
65 sizeof(PcmFloatDataSourceVersion1Command));
66 }
67 voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters;
68 voice_size += sizeof(VolumeRampCommand);
69 voice_size += sizeof(MixRampGroupedCommand);
70
71 size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size);
72
73 // Sub mixes
74 size += sizeof(DepopForMixBuffersCommand) +
75 (sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers;
76
77 // Final mix
78 size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers;
79
80 // Splitters
81 size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers;
82
83 // Sinks
84 size +=
85 params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand));
86
87 // Performance
88 size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 +
89 PerformanceManager::MaxDetailEntries) *
90 sizeof(PerformanceCommand);
91 return size;
92 }
93
94 /**
95 * Get the current command buffer used to generate commands.
96 *
97 * @return The command buffer.
98 */
99 CommandBuffer& GetCommandBuffer() {
100 return command_buffer;
101 }
102
103 /**
104 * Get the current performance manager,
105 *
106 * @return The performance manager. May be nullptr.
107 */
108 PerformanceManager* GetPerformanceManager() {
109 return performance_manager;
110 }
111
112 /**
113 * Generate a data source command.
114 * These are the basis for all audio output.
115 *
116 * @param voice_info - Generate the command from this voice.
117 * @param voice_state - State used by the AudioRenderer across calls.
118 * @param channel - Channel index to generate the command into.
119 */
120 void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state,
121 s8 channel);
122
123 /**
124 * Generate voice mixing commands.
125 * These are used to mix buffers together, to mix one input to many outputs,
126 * and also used as copy commands to move data around and prevent it being accidentally
127 * overwritten, e.g by another data source command into the same channel.
128 *
129 * @param mix_volumes - Current volumes of the mix.
130 * @param prev_mix_volumes - Previous volumes of the mix.
131 * @param voice_state - State used by the AudioRenderer across calls.
132 * @param output_index - Output mix buffer index.
133 * @param buffer_count - Number of active mix buffers.
134 * @param input_index - Input mix buffer index.
135 * @param node_id - Node id of the voice this command is generated for.
136 */
137 void GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
138 std::span<const f32> prev_mix_volumes,
139 const VoiceState& voice_state, s16 output_index, s16 buffer_count,
140 s16 input_index, s32 node_id);
141
142 /**
143 * Generate a biquad filter command for a voice.
144 *
145 * @param voice_info - Voice info this command is generated from.
146 * @param voice_state - State used by the AudioRenderer across calls.
147 * @param buffer_count - Number of active mix buffers.
148 * @param channel - Channel index of this command.
149 * @param node_id - Node id of the voice this command is generated for.
150 */
151 void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state,
152 s16 buffer_count, s8 channel, s32 node_id);
153
154 /**
155 * Generate commands for a voice.
156 * Includes a data source, biquad filter, volume and mixing.
157 *
158 * @param voice_info - Voice info these commands are generated from.
159 */
160 void GenerateVoiceCommand(VoiceInfo& voice_info);
161
162 /**
163 * Generate commands for all voices.
164 */
165 void GenerateVoiceCommands();
166
167 /**
168 * Generate a mixing command.
169 *
170 * @param buffer_offset - Base mix buffer offset to use.
171 * @param effect_info_base - BufferMixer effect info.
172 * @param node_id - Node id of the mix this command is generated for.
173 */
174 void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base,
175 s32 node_id);
176
177 /**
178 * Generate a delay effect command.
179 *
180 * @param buffer_offset - Base mix buffer offset to use.
181 * @param effect_info_base - Delay effect info.
182 * @param node_id - Node id of the mix this command is generated for.
183 */
184 void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id);
185
186 /**
187 * Generate a reverb effect command.
188 *
189 * @param buffer_offset - Base mix buffer offset to use.
190 * @param effect_info_base - Reverb effect info.
191 * @param node_id - Node id of the mix this command is generated for.
192 * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts.
193 */
194 void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id,
195 bool long_size_pre_delay_supported);
196
197 /**
198 * Generate an I3DL2 reverb effect command.
199 *
200 * @param buffer_offset - Base mix buffer offset to use.
201 * @param effect_info_base - I3DL2Reverb effect info.
202 * @param node_id - Node id of the mix this command is generated for.
203 */
204 void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
205 s32 node_id);
206
207 /**
208 * Generate an aux effect command.
209 *
210 * @param buffer_offset - Base mix buffer offset to use.
211 * @param effect_info_base - Aux effect info.
212 * @param node_id - Node id of the mix this command is generated for.
213 */
214 void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
215
216 /**
217 * Generate a biquad filter effect command.
218 *
219 * @param buffer_offset - Base mix buffer offset to use.
220 * @param effect_info_base - Aux effect info.
221 * @param node_id - Node id of the mix this command is generated for.
222 */
223 void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
224 s32 node_id);
225
226 /**
227 * Generate a light limiter effect command.
228 *
229 * @param buffer_offset - Base mix buffer offset to use.
230 * @param effect_info_base - Limiter effect info.
231 * @param node_id - Node id of the mix this command is generated for.
232 * @param effect_index - Index for the statistics state.
233 */
234 void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
235 s32 node_id, u32 effect_index);
236
237 /**
238 * Generate a capture effect command.
239 * Writes a mix buffer back to game memory.
240 *
241 * @param buffer_offset - Base mix buffer offset to use.
242 * @param effect_info_base - Capture effect info.
243 * @param node_id - Node id of the mix this command is generated for.
244 */
245 void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
246
247 /**
248 * Generate a compressor effect command.
249 *
250 * @param buffer_offset - Base mix buffer offset to use.
251 * @param effect_info_base - Compressor effect info.
252 * @param node_id - Node id of the mix this command is generated for.
253 */
254 void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
255 const s32 node_id);
256
257 /**
258 * Generate all effect commands for a mix.
259 *
260 * @param mix_info - Mix to generate effects from.
261 */
262 void GenerateEffectCommand(MixInfo& mix_info);
263
264 /**
265 * Generate all mix commands.
266 *
267 * @param mix_info - Mix to generate effects from.
268 */
269 void GenerateMixCommands(MixInfo& mix_info);
270
271 /**
272 * Generate a submix command.
273 * Generates all effects and all mixing commands.
274 *
275 * @param mix_info - Mix to generate effects from.
276 */
277 void GenerateSubMixCommand(MixInfo& mix_info);
278
279 /**
280 * Generate all submix command.
281 */
282 void GenerateSubMixCommands();
283
284 /**
285 * Generate the final mix.
286 */
287 void GenerateFinalMixCommand();
288
289 /**
290 * Generate the final mix commands.
291 */
292 void GenerateFinalMixCommands();
293
294 /**
295 * Generate all sink commands.
296 */
297 void GenerateSinkCommands();
298
299 /**
300 * Generate a sink command.
301 * Sends samples out to the backend, or a game-supplied circular buffer.
302 *
303 * @param buffer_offset - Base mix buffer offset to use.
304 * @param sink_info - Sink info to generate the commands from.
305 */
306 void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
307
308 /**
309 * Generate a device sink command.
310 * Sends samples out to the backend.
311 *
312 * @param buffer_offset - Base mix buffer offset to use.
313 * @param sink_info - Sink info to generate the commands from.
314 */
315 void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
316
317 /**
318 * Generate a performance command.
319 * Used to report performance metrics of the AudioRenderer back to the game.
320 *
321 * @param buffer_offset - Base mix buffer offset to use.
322 * @param sink_info - Sink info to generate the commands from.
323 */
324 void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
325 const PerformanceEntryAddresses& entry_addresses);
326
327private:
328 /// Commands will be written by this buffer
329 CommandBuffer& command_buffer;
330 /// Header information for the commands generated
331 const CommandListHeader& command_header;
332 /// Various things to control generation
333 const AudioRendererSystemContext& render_context;
334 /// Used for generating voices
335 VoiceContext& voice_context;
336 /// Used for generating mixes
337 MixContext& mix_context;
338 /// Used for generating effects
339 EffectContext& effect_context;
340 /// Used for generating sinks
341 SinkContext& sink_context;
342 /// Used for generating submixes
343 SplitterContext& splitter_context;
344 /// Used for generating performance
345 PerformanceManager* performance_manager;
346};
347
348} // namespace AudioRenderer
349} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12
13struct CommandListHeader {
14 u64 buffer_size;
15 u32 command_count;
16 std::span<s32> samples_buffer;
17 s16 buffer_count;
18 u32 sample_count;
19 u32 sample_rate;
20};
21
22} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/command/command_processing_time_estimator.h"
5
6namespace AudioCore::AudioRenderer {
7
8u32 CommandProcessingTimeEstimatorVersion1::Estimate(
9 const PcmInt16DataSourceVersion1Command& command) const {
10 return static_cast<u32>(command.pitch * 0.25f * 1.2f);
11}
12
13u32 CommandProcessingTimeEstimatorVersion1::Estimate(
14 const PcmInt16DataSourceVersion2Command& command) const {
15 return static_cast<u32>(command.pitch * 0.25f * 1.2f);
16}
17
18u32 CommandProcessingTimeEstimatorVersion1::Estimate(
19 [[maybe_unused]] const PcmFloatDataSourceVersion1Command& command) const {
20 return 0;
21}
22
23u32 CommandProcessingTimeEstimatorVersion1::Estimate(
24 [[maybe_unused]] const PcmFloatDataSourceVersion2Command& command) const {
25 return 0;
26}
27
28u32 CommandProcessingTimeEstimatorVersion1::Estimate(
29 const AdpcmDataSourceVersion1Command& command) const {
30 return static_cast<u32>(command.pitch * 0.25f * 1.2f);
31}
32
33u32 CommandProcessingTimeEstimatorVersion1::Estimate(
34 const AdpcmDataSourceVersion2Command& command) const {
35 return static_cast<u32>(command.pitch * 0.25f * 1.2f);
36}
37
38u32 CommandProcessingTimeEstimatorVersion1::Estimate(
39 [[maybe_unused]] const VolumeCommand& command) const {
40 return static_cast<u32>((static_cast<f32>(sample_count) * 8.8f) * 1.2f);
41}
42
43u32 CommandProcessingTimeEstimatorVersion1::Estimate(
44 [[maybe_unused]] const VolumeRampCommand& command) const {
45 return static_cast<u32>((static_cast<f32>(sample_count) * 9.8f) * 1.2f);
46}
47
48u32 CommandProcessingTimeEstimatorVersion1::Estimate(
49 [[maybe_unused]] const BiquadFilterCommand& command) const {
50 return static_cast<u32>((static_cast<f32>(sample_count) * 58.0f) * 1.2f);
51}
52
53u32 CommandProcessingTimeEstimatorVersion1::Estimate(
54 [[maybe_unused]] const MixCommand& command) const {
55 return static_cast<u32>((static_cast<f32>(sample_count) * 10.0f) * 1.2f);
56}
57
58u32 CommandProcessingTimeEstimatorVersion1::Estimate(
59 [[maybe_unused]] const MixRampCommand& command) const {
60 return static_cast<u32>((static_cast<f32>(sample_count) * 14.4f) * 1.2f);
61}
62
63u32 CommandProcessingTimeEstimatorVersion1::Estimate(const MixRampGroupedCommand& command) const {
64 u32 count{0};
65 for (u32 i = 0; i < command.buffer_count; i++) {
66 if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
67 count++;
68 }
69 }
70
71 return static_cast<u32>(((static_cast<f32>(sample_count) * 14.4f) * 1.2f) *
72 static_cast<f32>(count));
73}
74
75u32 CommandProcessingTimeEstimatorVersion1::Estimate(
76 [[maybe_unused]] const DepopPrepareCommand& command) const {
77 return 1080;
78}
79
80u32 CommandProcessingTimeEstimatorVersion1::Estimate(
81 const DepopForMixBuffersCommand& command) const {
82 return static_cast<u32>((static_cast<f32>(sample_count) * 8.9f) *
83 static_cast<f32>(command.count));
84}
85
86u32 CommandProcessingTimeEstimatorVersion1::Estimate(const DelayCommand& command) const {
87 return static_cast<u32>((static_cast<f32>(sample_count) * command.parameter.channel_count) *
88 202.5f);
89}
90
91u32 CommandProcessingTimeEstimatorVersion1::Estimate(
92 [[maybe_unused]] const UpsampleCommand& command) const {
93 return 357915;
94}
95
96u32 CommandProcessingTimeEstimatorVersion1::Estimate(
97 [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
98 return 16108;
99}
100
101u32 CommandProcessingTimeEstimatorVersion1::Estimate(const AuxCommand& command) const {
102 if (command.enabled) {
103 return 15956;
104 }
105 return 3765;
106}
107
108u32 CommandProcessingTimeEstimatorVersion1::Estimate(
109 [[maybe_unused]] const DeviceSinkCommand& command) const {
110 return 10042;
111}
112
113u32 CommandProcessingTimeEstimatorVersion1::Estimate(
114 [[maybe_unused]] const CircularBufferSinkCommand& command) const {
115 return 55;
116}
117
118u32 CommandProcessingTimeEstimatorVersion1::Estimate(const ReverbCommand& command) const {
119 if (command.enabled) {
120 return static_cast<u32>(
121 (command.parameter.channel_count * static_cast<f32>(sample_count) * 750) * 1.2f);
122 }
123 return 0;
124}
125
126u32 CommandProcessingTimeEstimatorVersion1::Estimate(const I3dl2ReverbCommand& command) const {
127 if (command.enabled) {
128 return static_cast<u32>(
129 (command.parameter.channel_count * static_cast<f32>(sample_count) * 530) * 1.2f);
130 }
131 return 0;
132}
133
134u32 CommandProcessingTimeEstimatorVersion1::Estimate(
135 [[maybe_unused]] const PerformanceCommand& command) const {
136 return 1454;
137}
138
139u32 CommandProcessingTimeEstimatorVersion1::Estimate(
140 [[maybe_unused]] const ClearMixBufferCommand& command) const {
141 return static_cast<u32>(
142 ((static_cast<f32>(sample_count) * 0.83f) * static_cast<f32>(buffer_count)) * 1.2f);
143}
144
145u32 CommandProcessingTimeEstimatorVersion1::Estimate(
146 [[maybe_unused]] const CopyMixBufferCommand& command) const {
147 return 0;
148}
149
150u32 CommandProcessingTimeEstimatorVersion1::Estimate(
151 [[maybe_unused]] const LightLimiterVersion1Command& command) const {
152 return 0;
153}
154
155u32 CommandProcessingTimeEstimatorVersion1::Estimate(
156 [[maybe_unused]] const LightLimiterVersion2Command& command) const {
157 return 0;
158}
159
160u32 CommandProcessingTimeEstimatorVersion1::Estimate(
161 [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
162 return 0;
163}
164
165u32 CommandProcessingTimeEstimatorVersion1::Estimate(
166 [[maybe_unused]] const CaptureCommand& command) const {
167 return 0;
168}
169
170u32 CommandProcessingTimeEstimatorVersion1::Estimate(
171 [[maybe_unused]] const CompressorCommand& command) const {
172 return 0;
173}
174
175u32 CommandProcessingTimeEstimatorVersion2::Estimate(
176 const PcmInt16DataSourceVersion1Command& command) const {
177 switch (sample_count) {
178 case 160:
179 return static_cast<u32>(
180 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
181 (command.pitch * 2.0f) * 749.269f +
182 6138.94f);
183 case 240:
184 return static_cast<u32>(
185 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
186 (command.pitch * 2.0f) * 1195.456f +
187 7797.047f);
188 default:
189 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
190 return 0;
191 }
192}
193
194u32 CommandProcessingTimeEstimatorVersion2::Estimate(
195 const PcmInt16DataSourceVersion2Command& command) const {
196 switch (sample_count) {
197 case 160:
198 return static_cast<u32>(
199 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
200 (command.pitch * 2.0f) * 749.269f +
201 6138.94f);
202 case 240:
203 return static_cast<u32>(
204 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
205 (command.pitch * 2.0f) * 1195.456f +
206 7797.047f);
207 default:
208 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
209 return 0;
210 }
211}
212
213u32 CommandProcessingTimeEstimatorVersion2::Estimate(
214 const PcmFloatDataSourceVersion1Command& command) const {
215 switch (sample_count) {
216 case 160:
217 return static_cast<u32>(
218 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
219 (command.pitch * 2.0f) * 749.269f +
220 6138.94f);
221 case 240:
222 return static_cast<u32>(
223 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
224 (command.pitch * 2.0f) * 1195.456f +
225 7797.047f);
226 default:
227 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
228 return 0;
229 }
230}
231
232u32 CommandProcessingTimeEstimatorVersion2::Estimate(
233 const PcmFloatDataSourceVersion2Command& command) const {
234 switch (sample_count) {
235 case 160:
236 return static_cast<u32>(
237 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
238 (command.pitch * 2.0f) * 749.269f +
239 6138.94f);
240 case 240:
241 return static_cast<u32>(
242 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
243 (command.pitch * 2.0f) * 1195.456f +
244 7797.047f);
245 default:
246 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
247 return 0;
248 }
249}
250
251u32 CommandProcessingTimeEstimatorVersion2::Estimate(
252 const AdpcmDataSourceVersion1Command& command) const {
253 switch (sample_count) {
254 case 160:
255 return static_cast<u32>(
256 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
257 (command.pitch * 2.0f) * 2125.588f +
258 9039.47f);
259 case 240:
260 return static_cast<u32>(
261 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
262 (command.pitch * 2.0f) * 3564.088 +
263 6225.471);
264 default:
265 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
266 return 0;
267 }
268}
269
270u32 CommandProcessingTimeEstimatorVersion2::Estimate(
271 const AdpcmDataSourceVersion2Command& command) const {
272 switch (sample_count) {
273 case 160:
274 return static_cast<u32>(
275 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
276 (command.pitch * 2.0f) * 2125.588f +
277 9039.47f);
278 case 240:
279 return static_cast<u32>(
280 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
281 (command.pitch * 2.0f) * 3564.088 +
282 6225.471);
283 default:
284 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
285 return 0;
286 }
287}
288
289u32 CommandProcessingTimeEstimatorVersion2::Estimate(
290 [[maybe_unused]] const VolumeCommand& command) const {
291 switch (sample_count) {
292 case 160:
293 return static_cast<u32>(1280.3f);
294 case 240:
295 return static_cast<u32>(1737.8f);
296 default:
297 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
298 return 0;
299 }
300}
301
302u32 CommandProcessingTimeEstimatorVersion2::Estimate(
303 [[maybe_unused]] const VolumeRampCommand& command) const {
304 switch (sample_count) {
305 case 160:
306 return static_cast<u32>(1403.9f);
307 case 240:
308 return static_cast<u32>(1884.3f);
309 default:
310 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
311 return 0;
312 }
313}
314
315u32 CommandProcessingTimeEstimatorVersion2::Estimate(
316 [[maybe_unused]] const BiquadFilterCommand& command) const {
317 switch (sample_count) {
318 case 160:
319 return static_cast<u32>(4813.2f);
320 case 240:
321 return static_cast<u32>(6915.4f);
322 default:
323 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
324 return 0;
325 }
326}
327
328u32 CommandProcessingTimeEstimatorVersion2::Estimate(
329 [[maybe_unused]] const MixCommand& command) const {
330 switch (sample_count) {
331 case 160:
332 return static_cast<u32>(1342.2f);
333 case 240:
334 return static_cast<u32>(1833.2f);
335 default:
336 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
337 return 0;
338 }
339}
340
341u32 CommandProcessingTimeEstimatorVersion2::Estimate(
342 [[maybe_unused]] const MixRampCommand& command) const {
343 switch (sample_count) {
344 case 160:
345 return static_cast<u32>(1859.0f);
346 case 240:
347 return static_cast<u32>(2286.1f);
348 default:
349 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
350 return 0;
351 }
352}
353
354u32 CommandProcessingTimeEstimatorVersion2::Estimate(const MixRampGroupedCommand& command) const {
355 u32 count{0};
356 for (u32 i = 0; i < command.buffer_count; i++) {
357 if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
358 count++;
359 }
360 }
361
362 switch (sample_count) {
363 case 160:
364 return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) *
365 static_cast<f32>(count));
366 case 240:
367 return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) *
368 static_cast<f32>(count));
369 default:
370 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
371 return 0;
372 }
373}
374
375u32 CommandProcessingTimeEstimatorVersion2::Estimate(
376 [[maybe_unused]] const DepopPrepareCommand& command) const {
377 switch (sample_count) {
378 case 160:
379 return static_cast<u32>(306.62f);
380 case 240:
381 return static_cast<u32>(293.22f);
382 default:
383 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
384 return 0;
385 }
386}
387
388u32 CommandProcessingTimeEstimatorVersion2::Estimate(
389 [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
390 switch (sample_count) {
391 case 160:
392 return static_cast<u32>(762.96f);
393 case 240:
394 return static_cast<u32>(726.96f);
395 default:
396 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
397 return 0;
398 }
399}
400
401u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DelayCommand& command) const {
402 switch (sample_count) {
403 case 160:
404 if (command.enabled) {
405 switch (command.parameter.channel_count) {
406 case 1:
407 return static_cast<u32>(41635.555f);
408 case 2:
409 return static_cast<u32>(97861.211f);
410 case 4:
411 return static_cast<u32>(192515.516f);
412 case 6:
413 return static_cast<u32>(301755.969f);
414 default:
415 LOG_ERROR(Service_Audio, "Invalid channel count {}",
416 command.parameter.channel_count);
417 return 0;
418 }
419 }
420 switch (command.parameter.channel_count) {
421 case 1:
422 return static_cast<u32>(578.529f);
423 case 2:
424 return static_cast<u32>(663.064f);
425 case 4:
426 return static_cast<u32>(703.983f);
427 case 6:
428 return static_cast<u32>(760.032f);
429 default:
430 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
431 return 0;
432 }
433 case 240:
434 if (command.enabled) {
435 switch (command.parameter.channel_count) {
436 case 1:
437 return static_cast<u32>(8770.345f);
438 case 2:
439 return static_cast<u32>(25741.18f);
440 case 4:
441 return static_cast<u32>(47551.168f);
442 case 6:
443 return static_cast<u32>(81629.219f);
444 default:
445 LOG_ERROR(Service_Audio, "Invalid channel count {}",
446 command.parameter.channel_count);
447 return 0;
448 }
449 }
450 switch (command.parameter.channel_count) {
451 case 1:
452 return static_cast<u32>(521.283f);
453 case 2:
454 return static_cast<u32>(585.396f);
455 case 4:
456 return static_cast<u32>(629.884f);
457 case 6:
458 return static_cast<u32>(713.57f);
459 default:
460 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
461 return 0;
462 }
463 default:
464 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
465 return 0;
466 }
467}
468
469u32 CommandProcessingTimeEstimatorVersion2::Estimate(
470 [[maybe_unused]] const UpsampleCommand& command) const {
471 switch (sample_count) {
472 case 160:
473 return static_cast<u32>(292000.0f);
474 case 240:
475 return static_cast<u32>(0.0f);
476 default:
477 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
478 return 0;
479 }
480}
481
482u32 CommandProcessingTimeEstimatorVersion2::Estimate(
483 [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
484 switch (sample_count) {
485 case 160:
486 return static_cast<u32>(10009.0f);
487 case 240:
488 return static_cast<u32>(14577.0f);
489 default:
490 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
491 return 0;
492 }
493}
494
495u32 CommandProcessingTimeEstimatorVersion2::Estimate(const AuxCommand& command) const {
496 // Is this function bugged, returning the wrong time?
497 // Surely the larger time should be returned when enabled...
498 // CMP W8, #0
499 // MOV W8, #0x60; // 489.163f
500 // MOV W10, #0x64; // 7177.936f
501 // CSEL X8, X10, X8, EQ
502
503 switch (sample_count) {
504 case 160:
505 if (command.enabled) {
506 return static_cast<u32>(489.163f);
507 }
508 return static_cast<u32>(7177.936f);
509 case 240:
510 if (command.enabled) {
511 return static_cast<u32>(485.562f);
512 }
513 return static_cast<u32>(9499.822f);
514 default:
515 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
516 return 0;
517 }
518}
519
520u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DeviceSinkCommand& command) const {
521 switch (command.input_count) {
522 case 2:
523 switch (sample_count) {
524 case 160:
525 return static_cast<u32>(9261.545f);
526 case 240:
527 return static_cast<u32>(9336.054f);
528 default:
529 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
530 return 0;
531 }
532 case 6:
533 switch (sample_count) {
534 case 160:
535 return static_cast<u32>(9336.054f);
536 case 240:
537 return static_cast<u32>(9566.728f);
538 default:
539 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
540 return 0;
541 }
542 default:
543 LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
544 return 0;
545 }
546}
547
548u32 CommandProcessingTimeEstimatorVersion2::Estimate(
549 const CircularBufferSinkCommand& command) const {
550 switch (sample_count) {
551 case 160:
552 return static_cast<u32>(static_cast<f32>(command.input_count) * 853.629f + 1284.517f);
553 case 240:
554 return static_cast<u32>(static_cast<f32>(command.input_count) * 1726.021f + 1369.683f);
555 default:
556 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
557 return 0;
558 }
559}
560
561u32 CommandProcessingTimeEstimatorVersion2::Estimate(const ReverbCommand& command) const {
562 switch (sample_count) {
563 case 160:
564 if (command.enabled) {
565 switch (command.parameter.channel_count) {
566 case 1:
567 return static_cast<u32>(97192.227f);
568 case 2:
569 return static_cast<u32>(103278.555f);
570 case 4:
571 return static_cast<u32>(109579.039f);
572 case 6:
573 return static_cast<u32>(115065.438f);
574 default:
575 LOG_ERROR(Service_Audio, "Invalid channel count {}",
576 command.parameter.channel_count);
577 return 0;
578 }
579 }
580 switch (command.parameter.channel_count) {
581 case 1:
582 return static_cast<u32>(492.009f);
583 case 2:
584 return static_cast<u32>(554.463f);
585 case 4:
586 return static_cast<u32>(595.864f);
587 case 6:
588 return static_cast<u32>(656.617f);
589 default:
590 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
591 return 0;
592 }
593 case 240:
594 if (command.enabled) {
595 switch (command.parameter.channel_count) {
596 case 1:
597 return static_cast<u32>(136463.641f);
598 case 2:
599 return static_cast<u32>(145749.047f);
600 case 4:
601 return static_cast<u32>(154796.938f);
602 case 6:
603 return static_cast<u32>(161968.406f);
604 default:
605 LOG_ERROR(Service_Audio, "Invalid channel count {}",
606 command.parameter.channel_count);
607 return 0;
608 }
609 }
610 switch (command.parameter.channel_count) {
611 case 1:
612 return static_cast<u32>(495.789f);
613 case 2:
614 return static_cast<u32>(527.163f);
615 case 4:
616 return static_cast<u32>(598.752f);
617 case 6:
618 return static_cast<u32>(666.025f);
619 default:
620 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
621 return 0;
622 }
623 default:
624 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
625 return 0;
626 }
627}
628
629u32 CommandProcessingTimeEstimatorVersion2::Estimate(const I3dl2ReverbCommand& command) const {
630 switch (sample_count) {
631 case 160:
632 if (command.enabled) {
633 switch (command.parameter.channel_count) {
634 case 1:
635 return static_cast<u32>(138836.484f);
636 case 2:
637 return static_cast<u32>(135428.172f);
638 case 4:
639 return static_cast<u32>(199181.844f);
640 case 6:
641 return static_cast<u32>(247345.906f);
642 default:
643 LOG_ERROR(Service_Audio, "Invalid channel count {}",
644 command.parameter.channel_count);
645 return 0;
646 }
647 }
648 switch (command.parameter.channel_count) {
649 case 1:
650 return static_cast<u32>(718.704f);
651 case 2:
652 return static_cast<u32>(751.296f);
653 case 4:
654 return static_cast<u32>(797.464f);
655 case 6:
656 return static_cast<u32>(867.426f);
657 default:
658 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
659 return 0;
660 }
661 case 240:
662 if (command.enabled) {
663 switch (command.parameter.channel_count) {
664 case 1:
665 return static_cast<u32>(199952.734f);
666 case 2:
667 return static_cast<u32>(195199.5f);
668 case 4:
669 return static_cast<u32>(290575.875f);
670 case 6:
671 return static_cast<u32>(363494.531f);
672 default:
673 LOG_ERROR(Service_Audio, "Invalid channel count {}",
674 command.parameter.channel_count);
675 return 0;
676 }
677 }
678 switch (command.parameter.channel_count) {
679 case 1:
680 return static_cast<u32>(534.24f);
681 case 2:
682 return static_cast<u32>(570.874f);
683 case 4:
684 return static_cast<u32>(660.933f);
685 case 6:
686 return static_cast<u32>(694.596f);
687 default:
688 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
689 return 0;
690 }
691 default:
692 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
693 return 0;
694 }
695}
696
697u32 CommandProcessingTimeEstimatorVersion2::Estimate(
698 [[maybe_unused]] const PerformanceCommand& command) const {
699 switch (sample_count) {
700 case 160:
701 return static_cast<u32>(489.35f);
702 case 240:
703 return static_cast<u32>(491.18f);
704 default:
705 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
706 return 0;
707 }
708}
709
710u32 CommandProcessingTimeEstimatorVersion2::Estimate(
711 [[maybe_unused]] const ClearMixBufferCommand& command) const {
712 switch (sample_count) {
713 case 160:
714 return static_cast<u32>(static_cast<f32>(buffer_count) * 260.4f + 139.65f);
715 case 240:
716 return static_cast<u32>(static_cast<f32>(buffer_count) * 668.85f + 193.2f);
717 default:
718 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
719 return 0;
720 }
721}
722
723u32 CommandProcessingTimeEstimatorVersion2::Estimate(
724 [[maybe_unused]] const CopyMixBufferCommand& command) const {
725 switch (sample_count) {
726 case 160:
727 return static_cast<u32>(836.32f);
728 case 240:
729 return static_cast<u32>(1000.9f);
730 default:
731 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
732 return 0;
733 }
734}
735
736u32 CommandProcessingTimeEstimatorVersion2::Estimate(
737 [[maybe_unused]] const LightLimiterVersion1Command& command) const {
738 return 0;
739}
740
741u32 CommandProcessingTimeEstimatorVersion2::Estimate(
742 [[maybe_unused]] const LightLimiterVersion2Command& command) const {
743 return 0;
744}
745
746u32 CommandProcessingTimeEstimatorVersion2::Estimate(
747 [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
748 return 0;
749}
750
751u32 CommandProcessingTimeEstimatorVersion2::Estimate(
752 [[maybe_unused]] const CaptureCommand& command) const {
753 return 0;
754}
755
756u32 CommandProcessingTimeEstimatorVersion2::Estimate(
757 [[maybe_unused]] const CompressorCommand& command) const {
758 return 0;
759}
760
761u32 CommandProcessingTimeEstimatorVersion3::Estimate(
762 const PcmInt16DataSourceVersion1Command& command) const {
763 switch (sample_count) {
764 case 160:
765 return static_cast<u32>(
766 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
767 (command.pitch * 0.000030518f)) *
768 427.52f +
769 6329.442f);
770 case 240:
771 return static_cast<u32>(
772 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
773 (command.pitch * 0.000030518f)) *
774 710.143f +
775 7853.286f);
776 default:
777 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
778 return 0;
779 }
780}
781
782u32 CommandProcessingTimeEstimatorVersion3::Estimate(
783 const PcmInt16DataSourceVersion2Command& command) const {
784 switch (sample_count) {
785 case 160:
786 switch (command.src_quality) {
787 case SrcQuality::Medium:
788 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
789 static_cast<f32>(sample_count)) *
790 (command.pitch * 0.000030518f)) -
791 1.0f) *
792 427.52f +
793 6329.442f);
794 case SrcQuality::High:
795 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
796 static_cast<f32>(sample_count)) *
797 (command.pitch * 0.000030518f)) -
798 1.0f) *
799 371.876f +
800 8049.415f);
801 case SrcQuality::Low:
802 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
803 static_cast<f32>(sample_count)) *
804 (command.pitch * 0.000030518f)) -
805 1.0f) *
806 423.43f +
807 5062.659f);
808 default:
809 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
810 static_cast<u32>(command.src_quality));
811 return 0;
812 }
813
814 case 240:
815 switch (command.src_quality) {
816 case SrcQuality::Medium:
817 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
818 static_cast<f32>(sample_count)) *
819 (command.pitch * 0.000030518f)) -
820 1.0f) *
821 710.143f +
822 7853.286f);
823 case SrcQuality::High:
824 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
825 static_cast<f32>(sample_count)) *
826 (command.pitch * 0.000030518f)) -
827 1.0f) *
828 610.487f +
829 10138.842f);
830 case SrcQuality::Low:
831 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
832 static_cast<f32>(sample_count)) *
833 (command.pitch * 0.000030518f)) -
834 1.0f) *
835 676.722f +
836 5810.962f);
837 default:
838 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
839 static_cast<u32>(command.src_quality));
840 return 0;
841 }
842
843 default:
844 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
845 return 0;
846 }
847}
848
849u32 CommandProcessingTimeEstimatorVersion3::Estimate(
850 const PcmFloatDataSourceVersion1Command& command) const {
851 switch (sample_count) {
852 case 160:
853 return static_cast<u32>(
854 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
855 (command.pitch * 0.000030518f)) *
856 1672.026f +
857 7681.211f);
858 case 240:
859 return static_cast<u32>(
860 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
861 (command.pitch * 0.000030518f)) *
862 2550.414f +
863 9663.969f);
864 default:
865 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
866 return 0;
867 }
868}
869
870u32 CommandProcessingTimeEstimatorVersion3::Estimate(
871 const PcmFloatDataSourceVersion2Command& command) const {
872 switch (sample_count) {
873 case 160:
874 switch (command.src_quality) {
875 case SrcQuality::Medium:
876 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
877 static_cast<f32>(sample_count)) *
878 (command.pitch * 0.000030518f)) -
879 1.0f) *
880 1672.026f +
881 7681.211f);
882 case SrcQuality::High:
883 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
884 static_cast<f32>(sample_count)) *
885 (command.pitch * 0.000030518f)) -
886 1.0f) *
887 1672.982f +
888 9038.011f);
889 case SrcQuality::Low:
890 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
891 static_cast<f32>(sample_count)) *
892 (command.pitch * 0.000030518f)) -
893 1.0f) *
894 1673.216f +
895 6027.577f);
896 default:
897 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
898 static_cast<u32>(command.src_quality));
899 return 0;
900 }
901
902 case 240:
903 switch (command.src_quality) {
904 case SrcQuality::Medium:
905 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
906 static_cast<f32>(sample_count)) *
907 (command.pitch * 0.000030518f)) -
908 1.0f) *
909 2550.414f +
910 9663.969f);
911 case SrcQuality::High:
912 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
913 static_cast<f32>(sample_count)) *
914 (command.pitch * 0.000030518f)) -
915 1.0f) *
916 2522.303f +
917 11758.571f);
918 case SrcQuality::Low:
919 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
920 static_cast<f32>(sample_count)) *
921 (command.pitch * 0.000030518f)) -
922 1.0f) *
923 2537.061f +
924 7369.309f);
925 default:
926 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
927 static_cast<u32>(command.src_quality));
928 return 0;
929 }
930
931 default:
932 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
933 return 0;
934 }
935}
936
937u32 CommandProcessingTimeEstimatorVersion3::Estimate(
938 const AdpcmDataSourceVersion1Command& command) const {
939 switch (sample_count) {
940 case 160:
941 return static_cast<u32>(
942 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
943 (command.pitch * 0.000030518f)) *
944 1827.665f +
945 7913.808f);
946 case 240:
947 return static_cast<u32>(
948 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
949 (command.pitch * 0.000030518f)) *
950 2756.372f +
951 9736.702f);
952 default:
953 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
954 return 0;
955 }
956}
957
958u32 CommandProcessingTimeEstimatorVersion3::Estimate(
959 const AdpcmDataSourceVersion2Command& command) const {
960 switch (sample_count) {
961 case 160:
962 switch (command.src_quality) {
963 case SrcQuality::Medium:
964 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
965 static_cast<f32>(sample_count)) *
966 (command.pitch * 0.000030518f)) -
967 1.0f) *
968 1827.665f +
969 7913.808f);
970 case SrcQuality::High:
971 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
972 static_cast<f32>(sample_count)) *
973 (command.pitch * 0.000030518f)) -
974 1.0f) *
975 1829.285f +
976 9607.814f);
977 case SrcQuality::Low:
978 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
979 static_cast<f32>(sample_count)) *
980 (command.pitch * 0.000030518f)) -
981 1.0f) *
982 1824.609f +
983 6517.476f);
984 default:
985 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
986 static_cast<u32>(command.src_quality));
987 return 0;
988 }
989
990 case 240:
991 switch (command.src_quality) {
992 case SrcQuality::Medium:
993 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
994 static_cast<f32>(sample_count)) *
995 (command.pitch * 0.000030518f)) -
996 1.0f) *
997 2756.372f +
998 9736.702f);
999 case SrcQuality::High:
1000 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1001 static_cast<f32>(sample_count)) *
1002 (command.pitch * 0.000030518f)) -
1003 1.0f) *
1004 2731.308f +
1005 12154.379f);
1006 case SrcQuality::Low:
1007 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1008 static_cast<f32>(sample_count)) *
1009 (command.pitch * 0.000030518f)) -
1010 1.0f) *
1011 2732.152f +
1012 7929.442f);
1013 default:
1014 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1015 static_cast<u32>(command.src_quality));
1016 return 0;
1017 }
1018
1019 default:
1020 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1021 return 0;
1022 }
1023}
1024
1025u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1026 [[maybe_unused]] const VolumeCommand& command) const {
1027 switch (sample_count) {
1028 case 160:
1029 return static_cast<u32>(1311.1f);
1030 case 240:
1031 return static_cast<u32>(1713.6f);
1032 default:
1033 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1034 return 0;
1035 }
1036}
1037
1038u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1039 [[maybe_unused]] const VolumeRampCommand& command) const {
1040 switch (sample_count) {
1041 case 160:
1042 return static_cast<u32>(1425.3f);
1043 case 240:
1044 return static_cast<u32>(1700.0f);
1045 default:
1046 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1047 return 0;
1048 }
1049}
1050
1051u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1052 [[maybe_unused]] const BiquadFilterCommand& command) const {
1053 switch (sample_count) {
1054 case 160:
1055 return static_cast<u32>(4173.2f);
1056 case 240:
1057 return static_cast<u32>(5585.1f);
1058 default:
1059 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1060 return 0;
1061 }
1062}
1063
1064u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1065 [[maybe_unused]] const MixCommand& command) const {
1066 switch (sample_count) {
1067 case 160:
1068 return static_cast<u32>(1402.8f);
1069 case 240:
1070 return static_cast<u32>(1853.2f);
1071 default:
1072 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1073 return 0;
1074 }
1075}
1076
1077u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1078 [[maybe_unused]] const MixRampCommand& command) const {
1079 switch (sample_count) {
1080 case 160:
1081 return static_cast<u32>(1968.7f);
1082 case 240:
1083 return static_cast<u32>(2459.4f);
1084 default:
1085 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1086 return 0;
1087 }
1088}
1089
1090u32 CommandProcessingTimeEstimatorVersion3::Estimate(const MixRampGroupedCommand& command) const {
1091 u32 count{0};
1092 for (u32 i = 0; i < command.buffer_count; i++) {
1093 if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
1094 count++;
1095 }
1096 }
1097
1098 switch (sample_count) {
1099 case 160:
1100 return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
1101 static_cast<f32>(count));
1102 case 240:
1103 return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
1104 static_cast<f32>(count));
1105 default:
1106 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1107 return 0;
1108 }
1109}
1110
1111u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1112 [[maybe_unused]] const DepopPrepareCommand& command) const {
1113 return 0;
1114}
1115
1116u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1117 [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
1118 switch (sample_count) {
1119 case 160:
1120 return static_cast<u32>(739.64f);
1121 case 240:
1122 return static_cast<u32>(910.97f);
1123 default:
1124 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1125 return 0;
1126 }
1127}
1128
1129u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DelayCommand& command) const {
1130 switch (sample_count) {
1131 case 160:
1132 if (command.enabled) {
1133 switch (command.parameter.channel_count) {
1134 case 1:
1135 return static_cast<u32>(8929.042f);
1136 case 2:
1137 return static_cast<u32>(25500.75f);
1138 case 4:
1139 return static_cast<u32>(47759.617f);
1140 case 6:
1141 return static_cast<u32>(82203.07f);
1142 default:
1143 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1144 command.parameter.channel_count);
1145 return 0;
1146 }
1147 }
1148 switch (command.parameter.channel_count) {
1149 case 1:
1150 return static_cast<u32>(1295.206f);
1151 case 2:
1152 return static_cast<u32>(1213.6f);
1153 case 4:
1154 return static_cast<u32>(942.028f);
1155 case 6:
1156 return static_cast<u32>(1001.553f);
1157 default:
1158 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1159 return 0;
1160 }
1161 case 240:
1162 if (command.enabled) {
1163 switch (command.parameter.channel_count) {
1164 case 1:
1165 return static_cast<u32>(11941.051f);
1166 case 2:
1167 return static_cast<u32>(37197.371f);
1168 case 4:
1169 return static_cast<u32>(69749.836f);
1170 case 6:
1171 return static_cast<u32>(120042.398f);
1172 default:
1173 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1174 command.parameter.channel_count);
1175 return 0;
1176 }
1177 }
1178 switch (command.parameter.channel_count) {
1179 case 1:
1180 return static_cast<u32>(997.668f);
1181 case 2:
1182 return static_cast<u32>(977.634f);
1183 case 4:
1184 return static_cast<u32>(792.309f);
1185 case 6:
1186 return static_cast<u32>(875.427f);
1187 default:
1188 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1189 return 0;
1190 }
1191 default:
1192 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1193 return 0;
1194 }
1195}
1196
1197u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1198 [[maybe_unused]] const UpsampleCommand& command) const {
1199 switch (sample_count) {
1200 case 160:
1201 return static_cast<u32>(312990.0f);
1202 case 240:
1203 return static_cast<u32>(0.0f);
1204 default:
1205 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1206 return 0;
1207 }
1208}
1209
1210u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1211 [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
1212 switch (sample_count) {
1213 case 160:
1214 return static_cast<u32>(9949.7f);
1215 case 240:
1216 return static_cast<u32>(14679.0f);
1217 default:
1218 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1219 return 0;
1220 }
1221}
1222
1223u32 CommandProcessingTimeEstimatorVersion3::Estimate(const AuxCommand& command) const {
1224 switch (sample_count) {
1225 case 160:
1226 if (command.enabled) {
1227 return static_cast<u32>(7182.136f);
1228 }
1229 return static_cast<u32>(472.111f);
1230 case 240:
1231 if (command.enabled) {
1232 return static_cast<u32>(9435.961f);
1233 }
1234 return static_cast<u32>(462.619f);
1235 default:
1236 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1237 return 0;
1238 }
1239}
1240
1241u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DeviceSinkCommand& command) const {
1242 switch (command.input_count) {
1243 case 2:
1244 switch (sample_count) {
1245 case 160:
1246 return static_cast<u32>(8979.956f);
1247 case 240:
1248 return static_cast<u32>(9221.907f);
1249 default:
1250 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1251 return 0;
1252 }
1253 case 6:
1254 switch (sample_count) {
1255 case 160:
1256 return static_cast<u32>(9177.903f);
1257 case 240:
1258 return static_cast<u32>(9725.897f);
1259 default:
1260 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1261 return 0;
1262 }
1263 default:
1264 LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
1265 return 0;
1266 }
1267}
1268
1269u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1270 const CircularBufferSinkCommand& command) const {
1271 switch (sample_count) {
1272 case 160:
1273 return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
1274 case 240:
1275 return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
1276 default:
1277 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1278 return 0;
1279 }
1280}
1281
1282u32 CommandProcessingTimeEstimatorVersion3::Estimate(const ReverbCommand& command) const {
1283 switch (sample_count) {
1284 case 160:
1285 if (command.enabled) {
1286 switch (command.parameter.channel_count) {
1287 case 1:
1288 return static_cast<u32>(81475.055f);
1289 case 2:
1290 return static_cast<u32>(84975.0f);
1291 case 4:
1292 return static_cast<u32>(91625.148f);
1293 case 6:
1294 return static_cast<u32>(95332.266f);
1295 default:
1296 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1297 command.parameter.channel_count);
1298 return 0;
1299 }
1300 }
1301 switch (command.parameter.channel_count) {
1302 case 1:
1303 return static_cast<u32>(536.298f);
1304 case 2:
1305 return static_cast<u32>(588.798f);
1306 case 4:
1307 return static_cast<u32>(643.702f);
1308 case 6:
1309 return static_cast<u32>(705.999f);
1310 default:
1311 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1312 return 0;
1313 }
1314 case 240:
1315 if (command.enabled) {
1316 switch (command.parameter.channel_count) {
1317 case 1:
1318 return static_cast<u32>(120174.469f);
1319 case 2:
1320 return static_cast<u32>(125262.219f);
1321 case 4:
1322 return static_cast<u32>(135751.234f);
1323 case 6:
1324 return static_cast<u32>(141129.234f);
1325 default:
1326 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1327 command.parameter.channel_count);
1328 return 0;
1329 }
1330 }
1331 switch (command.parameter.channel_count) {
1332 case 1:
1333 return static_cast<u32>(617.641f);
1334 case 2:
1335 return static_cast<u32>(659.536f);
1336 case 4:
1337 return static_cast<u32>(711.438f);
1338 case 6:
1339 return static_cast<u32>(778.071f);
1340 default:
1341 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1342 return 0;
1343 }
1344 default:
1345 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1346 return 0;
1347 }
1348}
1349
1350u32 CommandProcessingTimeEstimatorVersion3::Estimate(const I3dl2ReverbCommand& command) const {
1351 switch (sample_count) {
1352 case 160:
1353 if (command.enabled) {
1354 switch (command.parameter.channel_count) {
1355 case 1:
1356 return static_cast<u32>(116754.984f);
1357 case 2:
1358 return static_cast<u32>(125912.055f);
1359 case 4:
1360 return static_cast<u32>(146336.031f);
1361 case 6:
1362 return static_cast<u32>(165812.656f);
1363 default:
1364 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1365 command.parameter.channel_count);
1366 return 0;
1367 }
1368 }
1369 switch (command.parameter.channel_count) {
1370 case 1:
1371 return static_cast<u32>(735.0f);
1372 case 2:
1373 return static_cast<u32>(766.615f);
1374 case 4:
1375 return static_cast<u32>(834.067f);
1376 case 6:
1377 return static_cast<u32>(875.437f);
1378 default:
1379 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1380 return 0;
1381 }
1382 case 240:
1383 if (command.enabled) {
1384 switch (command.parameter.channel_count) {
1385 case 1:
1386 return static_cast<u32>(170292.344f);
1387 case 2:
1388 return static_cast<u32>(183875.625f);
1389 case 4:
1390 return static_cast<u32>(214696.188f);
1391 case 6:
1392 return static_cast<u32>(243846.766f);
1393 default:
1394 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1395 command.parameter.channel_count);
1396 return 0;
1397 }
1398 }
1399 switch (command.parameter.channel_count) {
1400 case 1:
1401 return static_cast<u32>(508.473f);
1402 case 2:
1403 return static_cast<u32>(582.445f);
1404 case 4:
1405 return static_cast<u32>(626.419f);
1406 case 6:
1407 return static_cast<u32>(682.468f);
1408 default:
1409 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1410 return 0;
1411 }
1412 default:
1413 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1414 return 0;
1415 }
1416}
1417
1418u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1419 [[maybe_unused]] const PerformanceCommand& command) const {
1420 switch (sample_count) {
1421 case 160:
1422 return static_cast<u32>(498.17f);
1423 case 240:
1424 return static_cast<u32>(489.42f);
1425 default:
1426 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1427 return 0;
1428 }
1429}
1430
1431u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1432 [[maybe_unused]] const ClearMixBufferCommand& command) const {
1433 switch (sample_count) {
1434 case 160:
1435 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
1436 case 240:
1437 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
1438 default:
1439 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1440 return 0;
1441 }
1442}
1443
1444u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1445 [[maybe_unused]] const CopyMixBufferCommand& command) const {
1446 switch (sample_count) {
1447 case 160:
1448 return static_cast<u32>(842.59f);
1449 case 240:
1450 return static_cast<u32>(986.72f);
1451 default:
1452 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1453 return 0;
1454 }
1455}
1456
1457u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1458 const LightLimiterVersion1Command& command) const {
1459 switch (sample_count) {
1460 case 160:
1461 if (command.enabled) {
1462 switch (command.parameter.channel_count) {
1463 case 1:
1464 return static_cast<u32>(21392.383f);
1465 case 2:
1466 return static_cast<u32>(26829.389f);
1467 case 4:
1468 return static_cast<u32>(32405.152f);
1469 case 6:
1470 return static_cast<u32>(52218.586f);
1471 default:
1472 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1473 command.parameter.channel_count);
1474 return 0;
1475 }
1476 }
1477 switch (command.parameter.channel_count) {
1478 case 1:
1479 return static_cast<u32>(897.004f);
1480 case 2:
1481 return static_cast<u32>(931.549f);
1482 case 4:
1483 return static_cast<u32>(975.387f);
1484 case 6:
1485 return static_cast<u32>(1016.778f);
1486 default:
1487 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1488 return 0;
1489 }
1490 case 240:
1491 if (command.enabled) {
1492 switch (command.parameter.channel_count) {
1493 case 1:
1494 return static_cast<u32>(30555.504f);
1495 case 2:
1496 return static_cast<u32>(39010.785f);
1497 case 4:
1498 return static_cast<u32>(48270.18f);
1499 case 6:
1500 return static_cast<u32>(76711.875f);
1501 default:
1502 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1503 command.parameter.channel_count);
1504 return 0;
1505 }
1506 }
1507 switch (command.parameter.channel_count) {
1508 case 1:
1509 return static_cast<u32>(874.429f);
1510 case 2:
1511 return static_cast<u32>(921.553f);
1512 case 4:
1513 return static_cast<u32>(945.262f);
1514 case 6:
1515 return static_cast<u32>(992.26f);
1516 default:
1517 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1518 return 0;
1519 }
1520 default:
1521 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1522 return 0;
1523 }
1524}
1525
1526u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1527 const LightLimiterVersion2Command& command) const {
1528 switch (sample_count) {
1529 case 160:
1530 if (command.enabled) {
1531 if (command.parameter.statistics_enabled) {
1532 switch (command.parameter.channel_count) {
1533 case 1:
1534 return static_cast<u32>(23308.928f);
1535 case 2:
1536 return static_cast<u32>(29954.062f);
1537 case 4:
1538 return static_cast<u32>(35807.477f);
1539 case 6:
1540 return static_cast<u32>(58339.773f);
1541 default:
1542 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1543 command.parameter.channel_count);
1544 return 0;
1545 }
1546 }
1547 switch (command.parameter.channel_count) {
1548 case 1:
1549 return static_cast<u32>(21392.383f);
1550 case 2:
1551 return static_cast<u32>(26829.389f);
1552 case 4:
1553 return static_cast<u32>(32405.152f);
1554 case 6:
1555 return static_cast<u32>(52218.586f);
1556 default:
1557 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1558 command.parameter.channel_count);
1559 return 0;
1560 }
1561 }
1562 switch (command.parameter.channel_count) {
1563 case 1:
1564 return static_cast<u32>(897.004f);
1565 case 2:
1566 return static_cast<u32>(931.549f);
1567 case 4:
1568 return static_cast<u32>(975.387f);
1569 case 6:
1570 return static_cast<u32>(1016.778f);
1571 default:
1572 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1573 return 0;
1574 }
1575 case 240:
1576 if (command.enabled) {
1577 if (command.parameter.statistics_enabled) {
1578 switch (command.parameter.channel_count) {
1579 case 1:
1580 return static_cast<u32>(33526.121f);
1581 case 2:
1582 return static_cast<u32>(43549.355f);
1583 case 4:
1584 return static_cast<u32>(52190.281f);
1585 case 6:
1586 return static_cast<u32>(85526.516f);
1587 default:
1588 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1589 command.parameter.channel_count);
1590 return 0;
1591 }
1592 }
1593 switch (command.parameter.channel_count) {
1594 case 1:
1595 return static_cast<u32>(30555.504f);
1596 case 2:
1597 return static_cast<u32>(39010.785f);
1598 case 4:
1599 return static_cast<u32>(48270.18f);
1600 case 6:
1601 return static_cast<u32>(76711.875f);
1602 default:
1603 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1604 command.parameter.channel_count);
1605 return 0;
1606 }
1607 }
1608 switch (command.parameter.channel_count) {
1609 case 1:
1610 return static_cast<u32>(874.429f);
1611 case 2:
1612 return static_cast<u32>(921.553f);
1613 case 4:
1614 return static_cast<u32>(945.262f);
1615 case 6:
1616 return static_cast<u32>(992.26f);
1617 default:
1618 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1619 return 0;
1620 }
1621 default:
1622 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1623 return 0;
1624 }
1625}
1626
1627u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1628 [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
1629 return 0;
1630}
1631
1632u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1633 [[maybe_unused]] const CaptureCommand& command) const {
1634 return 0;
1635}
1636
1637u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1638 [[maybe_unused]] const CompressorCommand& command) const {
1639 return 0;
1640}
1641
1642u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1643 const PcmInt16DataSourceVersion1Command& command) const {
1644 switch (sample_count) {
1645 case 160:
1646 return static_cast<u32>(
1647 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1648 (command.pitch * 0.000030518f)) *
1649 427.52f +
1650 6329.442f);
1651 case 240:
1652 return static_cast<u32>(
1653 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1654 (command.pitch * 0.000030518f)) *
1655 710.143f +
1656 7853.286f);
1657 default:
1658 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1659 return 0;
1660 }
1661}
1662
1663u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1664 const PcmInt16DataSourceVersion2Command& command) const {
1665 switch (sample_count) {
1666 case 160:
1667 switch (command.src_quality) {
1668 case SrcQuality::Medium:
1669 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1670 static_cast<f32>(sample_count)) *
1671 (command.pitch * 0.000030518f)) -
1672 1.0f) *
1673 427.52f +
1674 6329.442f);
1675 case SrcQuality::High:
1676 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1677 static_cast<f32>(sample_count)) *
1678 (command.pitch * 0.000030518f)) -
1679 1.0f) *
1680 371.876f +
1681 8049.415f);
1682 case SrcQuality::Low:
1683 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1684 static_cast<f32>(sample_count)) *
1685 (command.pitch * 0.000030518f)) -
1686 1.0f) *
1687 423.43f +
1688 5062.659f);
1689 default:
1690 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1691 static_cast<u32>(command.src_quality));
1692 return 0;
1693 }
1694
1695 case 240:
1696 switch (command.src_quality) {
1697 case SrcQuality::Medium:
1698 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1699 static_cast<f32>(sample_count)) *
1700 (command.pitch * 0.000030518f)) -
1701 1.0f) *
1702 710.143f +
1703 7853.286f);
1704 case SrcQuality::High:
1705 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1706 static_cast<f32>(sample_count)) *
1707 (command.pitch * 0.000030518f)) -
1708 1.0f) *
1709 610.487f +
1710 10138.842f);
1711 case SrcQuality::Low:
1712 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1713 static_cast<f32>(sample_count)) *
1714 (command.pitch * 0.000030518f)) -
1715 1.0f) *
1716 676.722f +
1717 5810.962f);
1718 default:
1719 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1720 static_cast<u32>(command.src_quality));
1721 return 0;
1722 }
1723
1724 default:
1725 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1726 return 0;
1727 }
1728}
1729
1730u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1731 const PcmFloatDataSourceVersion1Command& command) const {
1732 switch (sample_count) {
1733 case 160:
1734 return static_cast<u32>(
1735 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1736 (command.pitch * 0.000030518f)) *
1737 1672.026f +
1738 7681.211f);
1739 case 240:
1740 return static_cast<u32>(
1741 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1742 (command.pitch * 0.000030518f)) *
1743 2550.414f +
1744 9663.969f);
1745 default:
1746 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1747 return 0;
1748 }
1749}
1750
1751u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1752 const PcmFloatDataSourceVersion2Command& command) const {
1753 switch (sample_count) {
1754 case 160:
1755 switch (command.src_quality) {
1756 case SrcQuality::Medium:
1757 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1758 static_cast<f32>(sample_count)) *
1759 (command.pitch * 0.000030518f)) -
1760 1.0f) *
1761 1672.026f +
1762 7681.211f);
1763 case SrcQuality::High:
1764 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1765 static_cast<f32>(sample_count)) *
1766 (command.pitch * 0.000030518f)) -
1767 1.0f) *
1768 1672.982f +
1769 9038.011f);
1770 case SrcQuality::Low:
1771 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1772 static_cast<f32>(sample_count)) *
1773 (command.pitch * 0.000030518f)) -
1774 1.0f) *
1775 1673.216f +
1776 6027.577f);
1777 default:
1778 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1779 static_cast<u32>(command.src_quality));
1780 return 0;
1781 }
1782
1783 case 240:
1784 switch (command.src_quality) {
1785 case SrcQuality::Medium:
1786 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1787 static_cast<f32>(sample_count)) *
1788 (command.pitch * 0.000030518f)) -
1789 1.0f) *
1790 2550.414f +
1791 9663.969f);
1792 case SrcQuality::High:
1793 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1794 static_cast<f32>(sample_count)) *
1795 (command.pitch * 0.000030518f)) -
1796 1.0f) *
1797 2522.303f +
1798 11758.571f);
1799 case SrcQuality::Low:
1800 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1801 static_cast<f32>(sample_count)) *
1802 (command.pitch * 0.000030518f)) -
1803 1.0f) *
1804 2537.061f +
1805 7369.309f);
1806 default:
1807 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1808 static_cast<u32>(command.src_quality));
1809 return 0;
1810 }
1811
1812 default:
1813 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1814 return 0;
1815 }
1816}
1817
1818u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1819 const AdpcmDataSourceVersion1Command& command) const {
1820 switch (sample_count) {
1821 case 160:
1822 return static_cast<u32>(
1823 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1824 (command.pitch * 0.000030518f)) *
1825 1827.665f +
1826 7913.808f);
1827 case 240:
1828 return static_cast<u32>(
1829 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1830 (command.pitch * 0.000030518f)) *
1831 2756.372f +
1832 9736.702f);
1833 default:
1834 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1835 return 0;
1836 }
1837}
1838
1839u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1840 const AdpcmDataSourceVersion2Command& command) const {
1841 switch (sample_count) {
1842 case 160:
1843 switch (command.src_quality) {
1844 case SrcQuality::Medium:
1845 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1846 static_cast<f32>(sample_count)) *
1847 (command.pitch * 0.000030518f)) -
1848 1.0f) *
1849 1827.665f +
1850 7913.808f);
1851 case SrcQuality::High:
1852 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1853 static_cast<f32>(sample_count)) *
1854 (command.pitch * 0.000030518f)) -
1855 1.0f) *
1856 1829.285f +
1857 9607.814f);
1858 case SrcQuality::Low:
1859 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1860 static_cast<f32>(sample_count)) *
1861 (command.pitch * 0.000030518f)) -
1862 1.0f) *
1863 1824.609f +
1864 6517.476f);
1865 default:
1866 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1867 static_cast<u32>(command.src_quality));
1868 return 0;
1869 }
1870
1871 case 240:
1872 switch (command.src_quality) {
1873 case SrcQuality::Medium:
1874 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1875 static_cast<f32>(sample_count)) *
1876 (command.pitch * 0.000030518f)) -
1877 1.0f) *
1878 2756.372f +
1879 9736.702f);
1880 case SrcQuality::High:
1881 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1882 static_cast<f32>(sample_count)) *
1883 (command.pitch * 0.000030518f)) -
1884 1.0f) *
1885 2731.308f +
1886 12154.379f);
1887 case SrcQuality::Low:
1888 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1889 static_cast<f32>(sample_count)) *
1890 (command.pitch * 0.000030518f)) -
1891 1.0f) *
1892 2732.152f +
1893 7929.442f);
1894 default:
1895 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1896 static_cast<u32>(command.src_quality));
1897 return 0;
1898 }
1899
1900 default:
1901 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1902 return 0;
1903 }
1904}
1905
1906u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1907 [[maybe_unused]] const VolumeCommand& command) const {
1908 switch (sample_count) {
1909 case 160:
1910 return static_cast<u32>(1311.1f);
1911 case 240:
1912 return static_cast<u32>(1713.6f);
1913 default:
1914 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1915 return 0;
1916 }
1917}
1918
1919u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1920 [[maybe_unused]] const VolumeRampCommand& command) const {
1921 switch (sample_count) {
1922 case 160:
1923 return static_cast<u32>(1425.3f);
1924 case 240:
1925 return static_cast<u32>(1700.0f);
1926 default:
1927 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1928 return 0;
1929 }
1930}
1931
1932u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1933 [[maybe_unused]] const BiquadFilterCommand& command) const {
1934 switch (sample_count) {
1935 case 160:
1936 return static_cast<u32>(4173.2f);
1937 case 240:
1938 return static_cast<u32>(5585.1f);
1939 default:
1940 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1941 return 0;
1942 }
1943}
1944
1945u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1946 [[maybe_unused]] const MixCommand& command) const {
1947 switch (sample_count) {
1948 case 160:
1949 return static_cast<u32>(1402.8f);
1950 case 240:
1951 return static_cast<u32>(1853.2f);
1952 default:
1953 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1954 return 0;
1955 }
1956}
1957
1958u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1959 [[maybe_unused]] const MixRampCommand& command) const {
1960 switch (sample_count) {
1961 case 160:
1962 return static_cast<u32>(1968.7f);
1963 case 240:
1964 return static_cast<u32>(2459.4f);
1965 default:
1966 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1967 return 0;
1968 }
1969}
1970
1971u32 CommandProcessingTimeEstimatorVersion4::Estimate(const MixRampGroupedCommand& command) const {
1972 u32 count{0};
1973 for (u32 i = 0; i < command.buffer_count; i++) {
1974 if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
1975 count++;
1976 }
1977 }
1978
1979 switch (sample_count) {
1980 case 160:
1981 return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
1982 static_cast<f32>(count));
1983 case 240:
1984 return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
1985 static_cast<f32>(count));
1986 default:
1987 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1988 return 0;
1989 }
1990}
1991
1992u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1993 [[maybe_unused]] const DepopPrepareCommand& command) const {
1994 return 0;
1995}
1996
1997u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1998 [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
1999 switch (sample_count) {
2000 case 160:
2001 return static_cast<u32>(739.64f);
2002 case 240:
2003 return static_cast<u32>(910.97f);
2004 default:
2005 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2006 return 0;
2007 }
2008}
2009
2010u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DelayCommand& command) const {
2011 switch (sample_count) {
2012 case 160:
2013 if (command.enabled) {
2014 switch (command.parameter.channel_count) {
2015 case 1:
2016 return static_cast<u32>(8929.042f);
2017 case 2:
2018 return static_cast<u32>(25500.75f);
2019 case 4:
2020 return static_cast<u32>(47759.617f);
2021 case 6:
2022 return static_cast<u32>(82203.07f);
2023 default:
2024 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2025 command.parameter.channel_count);
2026 return 0;
2027 }
2028 }
2029 switch (command.parameter.channel_count) {
2030 case 1:
2031 return static_cast<u32>(1295.206f);
2032 case 2:
2033 return static_cast<u32>(1213.6f);
2034 case 4:
2035 return static_cast<u32>(942.028f);
2036 case 6:
2037 return static_cast<u32>(1001.553f);
2038 default:
2039 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2040 return 0;
2041 }
2042 case 240:
2043 if (command.enabled) {
2044 switch (command.parameter.channel_count) {
2045 case 1:
2046 return static_cast<u32>(11941.051f);
2047 case 2:
2048 return static_cast<u32>(37197.371f);
2049 case 4:
2050 return static_cast<u32>(69749.836f);
2051 case 6:
2052 return static_cast<u32>(120042.398f);
2053 default:
2054 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2055 command.parameter.channel_count);
2056 return 0;
2057 }
2058 }
2059 switch (command.parameter.channel_count) {
2060 case 1:
2061 return static_cast<u32>(997.668f);
2062 case 2:
2063 return static_cast<u32>(977.634f);
2064 case 4:
2065 return static_cast<u32>(792.309f);
2066 case 6:
2067 return static_cast<u32>(875.427f);
2068 default:
2069 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2070 return 0;
2071 }
2072 default:
2073 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2074 return 0;
2075 }
2076}
2077
2078u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2079 [[maybe_unused]] const UpsampleCommand& command) const {
2080 switch (sample_count) {
2081 case 160:
2082 return static_cast<u32>(312990.0f);
2083 case 240:
2084 return static_cast<u32>(0.0f);
2085 default:
2086 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2087 return 0;
2088 }
2089}
2090
2091u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2092 [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
2093 switch (sample_count) {
2094 case 160:
2095 return static_cast<u32>(9949.7f);
2096 case 240:
2097 return static_cast<u32>(14679.0f);
2098 default:
2099 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2100 return 0;
2101 }
2102}
2103
2104u32 CommandProcessingTimeEstimatorVersion4::Estimate(const AuxCommand& command) const {
2105 switch (sample_count) {
2106 case 160:
2107 if (command.enabled) {
2108 return static_cast<u32>(7182.136f);
2109 }
2110 return static_cast<u32>(472.111f);
2111 case 240:
2112 if (command.enabled) {
2113 return static_cast<u32>(9435.961f);
2114 }
2115 return static_cast<u32>(462.619f);
2116 default:
2117 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2118 return 0;
2119 }
2120}
2121
2122u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DeviceSinkCommand& command) const {
2123 switch (command.input_count) {
2124 case 2:
2125 switch (sample_count) {
2126 case 160:
2127 return static_cast<u32>(8979.956f);
2128 case 240:
2129 return static_cast<u32>(9221.907f);
2130 default:
2131 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2132 return 0;
2133 }
2134 case 6:
2135 switch (sample_count) {
2136 case 160:
2137 return static_cast<u32>(9177.903f);
2138 case 240:
2139 return static_cast<u32>(9725.897f);
2140 default:
2141 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2142 return 0;
2143 }
2144 default:
2145 LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
2146 return 0;
2147 }
2148}
2149
2150u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2151 const CircularBufferSinkCommand& command) const {
2152 switch (sample_count) {
2153 case 160:
2154 return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
2155 case 240:
2156 return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
2157 default:
2158 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2159 return 0;
2160 }
2161}
2162
2163u32 CommandProcessingTimeEstimatorVersion4::Estimate(const ReverbCommand& command) const {
2164 switch (sample_count) {
2165 case 160:
2166 if (command.enabled) {
2167 switch (command.parameter.channel_count) {
2168 case 1:
2169 return static_cast<u32>(81475.055f);
2170 case 2:
2171 return static_cast<u32>(84975.0f);
2172 case 4:
2173 return static_cast<u32>(91625.148f);
2174 case 6:
2175 return static_cast<u32>(95332.266f);
2176 default:
2177 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2178 command.parameter.channel_count);
2179 return 0;
2180 }
2181 }
2182 switch (command.parameter.channel_count) {
2183 case 1:
2184 return static_cast<u32>(536.298f);
2185 case 2:
2186 return static_cast<u32>(588.798f);
2187 case 4:
2188 return static_cast<u32>(643.702f);
2189 case 6:
2190 return static_cast<u32>(705.999f);
2191 default:
2192 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2193 return 0;
2194 }
2195 case 240:
2196 if (command.enabled) {
2197 switch (command.parameter.channel_count) {
2198 case 1:
2199 return static_cast<u32>(120174.469f);
2200 case 2:
2201 return static_cast<u32>(125262.219f);
2202 case 4:
2203 return static_cast<u32>(135751.234f);
2204 case 6:
2205 return static_cast<u32>(141129.234f);
2206 default:
2207 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2208 command.parameter.channel_count);
2209 return 0;
2210 }
2211 }
2212 switch (command.parameter.channel_count) {
2213 case 1:
2214 return static_cast<u32>(617.641f);
2215 case 2:
2216 return static_cast<u32>(659.536f);
2217 case 4:
2218 return static_cast<u32>(711.438f);
2219 case 6:
2220 return static_cast<u32>(778.071f);
2221 default:
2222 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2223 return 0;
2224 }
2225 default:
2226 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2227 return 0;
2228 }
2229}
2230
2231u32 CommandProcessingTimeEstimatorVersion4::Estimate(const I3dl2ReverbCommand& command) const {
2232 switch (sample_count) {
2233 case 160:
2234 if (command.enabled) {
2235 switch (command.parameter.channel_count) {
2236 case 1:
2237 return static_cast<u32>(116754.984f);
2238 case 2:
2239 return static_cast<u32>(125912.055f);
2240 case 4:
2241 return static_cast<u32>(146336.031f);
2242 case 6:
2243 return static_cast<u32>(165812.656f);
2244 default:
2245 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2246 command.parameter.channel_count);
2247 return 0;
2248 }
2249 }
2250 switch (command.parameter.channel_count) {
2251 case 1:
2252 return static_cast<u32>(735.0f);
2253 case 2:
2254 return static_cast<u32>(766.615f);
2255 case 4:
2256 return static_cast<u32>(834.067f);
2257 case 6:
2258 return static_cast<u32>(875.437f);
2259 default:
2260 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2261 return 0;
2262 }
2263 case 240:
2264 if (command.enabled) {
2265 switch (command.parameter.channel_count) {
2266 case 1:
2267 return static_cast<u32>(170292.344f);
2268 case 2:
2269 return static_cast<u32>(183875.625f);
2270 case 4:
2271 return static_cast<u32>(214696.188f);
2272 case 6:
2273 return static_cast<u32>(243846.766f);
2274 default:
2275 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2276 command.parameter.channel_count);
2277 return 0;
2278 }
2279 }
2280 switch (command.parameter.channel_count) {
2281 case 1:
2282 return static_cast<u32>(508.473f);
2283 case 2:
2284 return static_cast<u32>(582.445f);
2285 case 4:
2286 return static_cast<u32>(626.419f);
2287 case 6:
2288 return static_cast<u32>(682.468f);
2289 default:
2290 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2291 return 0;
2292 }
2293 default:
2294 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2295 return 0;
2296 }
2297}
2298
2299u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2300 [[maybe_unused]] const PerformanceCommand& command) const {
2301 switch (sample_count) {
2302 case 160:
2303 return static_cast<u32>(498.17f);
2304 case 240:
2305 return static_cast<u32>(489.42f);
2306 default:
2307 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2308 return 0;
2309 }
2310}
2311
2312u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2313 [[maybe_unused]] const ClearMixBufferCommand& command) const {
2314 switch (sample_count) {
2315 case 160:
2316 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
2317 case 240:
2318 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
2319 default:
2320 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2321 return 0;
2322 }
2323}
2324
2325u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2326 [[maybe_unused]] const CopyMixBufferCommand& command) const {
2327 switch (sample_count) {
2328 case 160:
2329 return static_cast<u32>(842.59f);
2330 case 240:
2331 return static_cast<u32>(986.72f);
2332 default:
2333 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2334 return 0;
2335 }
2336}
2337
2338u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2339 const LightLimiterVersion1Command& command) const {
2340 switch (sample_count) {
2341 case 160:
2342 if (command.enabled) {
2343 switch (command.parameter.channel_count) {
2344 case 1:
2345 return static_cast<u32>(21392.383f);
2346 case 2:
2347 return static_cast<u32>(26829.389f);
2348 case 4:
2349 return static_cast<u32>(32405.152f);
2350 case 6:
2351 return static_cast<u32>(52218.586f);
2352 default:
2353 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2354 command.parameter.channel_count);
2355 return 0;
2356 }
2357 }
2358 switch (command.parameter.channel_count) {
2359 case 1:
2360 return static_cast<u32>(897.004f);
2361 case 2:
2362 return static_cast<u32>(931.549f);
2363 case 4:
2364 return static_cast<u32>(975.387f);
2365 case 6:
2366 return static_cast<u32>(1016.778f);
2367 default:
2368 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2369 return 0;
2370 }
2371 case 240:
2372 if (command.enabled) {
2373 switch (command.parameter.channel_count) {
2374 case 1:
2375 return static_cast<u32>(30555.504f);
2376 case 2:
2377 return static_cast<u32>(39010.785f);
2378 case 4:
2379 return static_cast<u32>(48270.18f);
2380 case 6:
2381 return static_cast<u32>(76711.875f);
2382 default:
2383 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2384 command.parameter.channel_count);
2385 return 0;
2386 }
2387 }
2388 switch (command.parameter.channel_count) {
2389 case 1:
2390 return static_cast<u32>(874.429f);
2391 case 2:
2392 return static_cast<u32>(921.553f);
2393 case 4:
2394 return static_cast<u32>(945.262f);
2395 case 6:
2396 return static_cast<u32>(992.26f);
2397 default:
2398 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2399 return 0;
2400 }
2401 default:
2402 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2403 return 0;
2404 }
2405}
2406
2407u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2408 const LightLimiterVersion2Command& command) const {
2409 switch (sample_count) {
2410 case 160:
2411 if (command.enabled) {
2412 if (command.parameter.statistics_enabled) {
2413 switch (command.parameter.channel_count) {
2414 case 1:
2415 return static_cast<u32>(23308.928f);
2416 case 2:
2417 return static_cast<u32>(29954.062f);
2418 case 4:
2419 return static_cast<u32>(35807.477f);
2420 case 6:
2421 return static_cast<u32>(58339.773f);
2422 default:
2423 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2424 command.parameter.channel_count);
2425 return 0;
2426 }
2427 }
2428 switch (command.parameter.channel_count) {
2429 case 1:
2430 return static_cast<u32>(21392.383f);
2431 case 2:
2432 return static_cast<u32>(26829.389f);
2433 case 4:
2434 return static_cast<u32>(32405.152f);
2435 case 6:
2436 return static_cast<u32>(52218.586f);
2437 default:
2438 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2439 command.parameter.channel_count);
2440 return 0;
2441 }
2442 }
2443 switch (command.parameter.channel_count) {
2444 case 1:
2445 return static_cast<u32>(897.004f);
2446 case 2:
2447 return static_cast<u32>(931.549f);
2448 case 4:
2449 return static_cast<u32>(975.387f);
2450 case 6:
2451 return static_cast<u32>(1016.778f);
2452 default:
2453 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2454 return 0;
2455 }
2456 case 240:
2457 if (command.enabled) {
2458 if (command.parameter.statistics_enabled) {
2459 switch (command.parameter.channel_count) {
2460 case 1:
2461 return static_cast<u32>(33526.121f);
2462 case 2:
2463 return static_cast<u32>(43549.355f);
2464 case 4:
2465 return static_cast<u32>(52190.281f);
2466 case 6:
2467 return static_cast<u32>(85526.516f);
2468 default:
2469 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2470 command.parameter.channel_count);
2471 return 0;
2472 }
2473 }
2474 switch (command.parameter.channel_count) {
2475 case 1:
2476 return static_cast<u32>(30555.504f);
2477 case 2:
2478 return static_cast<u32>(39010.785f);
2479 case 4:
2480 return static_cast<u32>(48270.18f);
2481 case 6:
2482 return static_cast<u32>(76711.875f);
2483 default:
2484 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2485 command.parameter.channel_count);
2486 return 0;
2487 }
2488 }
2489 switch (command.parameter.channel_count) {
2490 case 1:
2491 return static_cast<u32>(874.429f);
2492 case 2:
2493 return static_cast<u32>(921.553f);
2494 case 4:
2495 return static_cast<u32>(945.262f);
2496 case 6:
2497 return static_cast<u32>(992.26f);
2498 default:
2499 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2500 return 0;
2501 }
2502 default:
2503 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2504 return 0;
2505 }
2506}
2507
2508u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2509 [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
2510 switch (sample_count) {
2511 case 160:
2512 return static_cast<u32>(7424.5f);
2513 case 240:
2514 return static_cast<u32>(9730.4f);
2515 default:
2516 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2517 return 0;
2518 }
2519}
2520
2521u32 CommandProcessingTimeEstimatorVersion4::Estimate(const CaptureCommand& command) const {
2522 switch (sample_count) {
2523 case 160:
2524 if (command.enabled) {
2525 return static_cast<u32>(426.982f);
2526 }
2527 return static_cast<u32>(4261.005f);
2528 case 240:
2529 if (command.enabled) {
2530 return static_cast<u32>(435.204f);
2531 }
2532 return static_cast<u32>(5858.265f);
2533 default:
2534 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2535 return 0;
2536 }
2537}
2538
2539u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2540 [[maybe_unused]] const CompressorCommand& command) const {
2541 return 0;
2542}
2543
2544u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2545 const PcmInt16DataSourceVersion1Command& command) const {
2546 switch (sample_count) {
2547 case 160:
2548 return static_cast<u32>(
2549 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2550 (command.pitch * 0.000030518f)) *
2551 427.52f +
2552 6329.442f);
2553 case 240:
2554 return static_cast<u32>(
2555 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2556 (command.pitch * 0.000030518f)) *
2557 710.143f +
2558 7853.286f);
2559 default:
2560 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2561 return 0;
2562 }
2563}
2564
2565u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2566 const PcmInt16DataSourceVersion2Command& command) const {
2567 switch (sample_count) {
2568 case 160:
2569 switch (command.src_quality) {
2570 case SrcQuality::Medium:
2571 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2572 static_cast<f32>(sample_count)) *
2573 (command.pitch * 0.000030518f)) -
2574 1.0f) *
2575 427.52f +
2576 6329.442f);
2577 case SrcQuality::High:
2578 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2579 static_cast<f32>(sample_count)) *
2580 (command.pitch * 0.000030518f)) -
2581 1.0f) *
2582 371.876f +
2583 8049.415f);
2584 case SrcQuality::Low:
2585 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2586 static_cast<f32>(sample_count)) *
2587 (command.pitch * 0.000030518f)) -
2588 1.0f) *
2589 423.43f +
2590 5062.659f);
2591 default:
2592 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2593 static_cast<u32>(command.src_quality));
2594 return 0;
2595 }
2596
2597 case 240:
2598 switch (command.src_quality) {
2599 case SrcQuality::Medium:
2600 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2601 static_cast<f32>(sample_count)) *
2602 (command.pitch * 0.000030518f)) -
2603 1.0f) *
2604 710.143f +
2605 7853.286f);
2606 case SrcQuality::High:
2607 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2608 static_cast<f32>(sample_count)) *
2609 (command.pitch * 0.000030518f)) -
2610 1.0f) *
2611 610.487f +
2612 10138.842f);
2613 case SrcQuality::Low:
2614 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2615 static_cast<f32>(sample_count)) *
2616 (command.pitch * 0.000030518f)) -
2617 1.0f) *
2618 676.722f +
2619 5810.962f);
2620 default:
2621 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2622 static_cast<u32>(command.src_quality));
2623 return 0;
2624 }
2625
2626 default:
2627 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2628 return 0;
2629 }
2630}
2631
2632u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2633 const PcmFloatDataSourceVersion1Command& command) const {
2634 switch (sample_count) {
2635 case 160:
2636 return static_cast<u32>(
2637 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2638 (command.pitch * 0.000030518f)) *
2639 1672.026f +
2640 7681.211f);
2641 case 240:
2642 return static_cast<u32>(
2643 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2644 (command.pitch * 0.000030518f)) *
2645 2550.414f +
2646 9663.969f);
2647 default:
2648 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2649 return 0;
2650 }
2651}
2652
2653u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2654 const PcmFloatDataSourceVersion2Command& command) const {
2655 switch (sample_count) {
2656 case 160:
2657 switch (command.src_quality) {
2658 case SrcQuality::Medium:
2659 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2660 static_cast<f32>(sample_count)) *
2661 (command.pitch * 0.000030518f)) -
2662 1.0f) *
2663 1672.026f +
2664 7681.211f);
2665 case SrcQuality::High:
2666 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2667 static_cast<f32>(sample_count)) *
2668 (command.pitch * 0.000030518f)) -
2669 1.0f) *
2670 1672.982f +
2671 9038.011f);
2672 case SrcQuality::Low:
2673 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2674 static_cast<f32>(sample_count)) *
2675 (command.pitch * 0.000030518f)) -
2676 1.0f) *
2677 1673.216f +
2678 6027.577f);
2679 default:
2680 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2681 static_cast<u32>(command.src_quality));
2682 return 0;
2683 }
2684
2685 case 240:
2686 switch (command.src_quality) {
2687 case SrcQuality::Medium:
2688 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2689 static_cast<f32>(sample_count)) *
2690 (command.pitch * 0.000030518f)) -
2691 1.0f) *
2692 2550.414f +
2693 9663.969f);
2694 case SrcQuality::High:
2695 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2696 static_cast<f32>(sample_count)) *
2697 (command.pitch * 0.000030518f)) -
2698 1.0f) *
2699 2522.303f +
2700 11758.571f);
2701 case SrcQuality::Low:
2702 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2703 static_cast<f32>(sample_count)) *
2704 (command.pitch * 0.000030518f)) -
2705 1.0f) *
2706 2537.061f +
2707 7369.309f);
2708 default:
2709 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2710 static_cast<u32>(command.src_quality));
2711 return 0;
2712 }
2713
2714 default:
2715 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2716 return 0;
2717 }
2718}
2719
2720u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2721 const AdpcmDataSourceVersion1Command& command) const {
2722 switch (sample_count) {
2723 case 160:
2724 return static_cast<u32>(
2725 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2726 (command.pitch * 0.000030518f)) *
2727 1827.665f +
2728 7913.808f);
2729 case 240:
2730 return static_cast<u32>(
2731 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2732 (command.pitch * 0.000030518f)) *
2733 2756.372f +
2734 9736.702f);
2735 default:
2736 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2737 return 0;
2738 }
2739}
2740
2741u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2742 const AdpcmDataSourceVersion2Command& command) const {
2743 switch (sample_count) {
2744 case 160:
2745 switch (command.src_quality) {
2746 case SrcQuality::Medium:
2747 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2748 static_cast<f32>(sample_count)) *
2749 (command.pitch * 0.000030518f)) -
2750 1.0f) *
2751 1827.665f +
2752 7913.808f);
2753 case SrcQuality::High:
2754 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2755 static_cast<f32>(sample_count)) *
2756 (command.pitch * 0.000030518f)) -
2757 1.0f) *
2758 1829.285f +
2759 9607.814f);
2760 case SrcQuality::Low:
2761 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2762 static_cast<f32>(sample_count)) *
2763 (command.pitch * 0.000030518f)) -
2764 1.0f) *
2765 1824.609f +
2766 6517.476f);
2767 default:
2768 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2769 static_cast<u32>(command.src_quality));
2770 return 0;
2771 }
2772
2773 case 240:
2774 switch (command.src_quality) {
2775 case SrcQuality::Medium:
2776 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2777 static_cast<f32>(sample_count)) *
2778 (command.pitch * 0.000030518f)) -
2779 1.0f) *
2780 2756.372f +
2781 9736.702f);
2782 case SrcQuality::High:
2783 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2784 static_cast<f32>(sample_count)) *
2785 (command.pitch * 0.000030518f)) -
2786 1.0f) *
2787 2731.308f +
2788 12154.379f);
2789 case SrcQuality::Low:
2790 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2791 static_cast<f32>(sample_count)) *
2792 (command.pitch * 0.000030518f)) -
2793 1.0f) *
2794 2732.152f +
2795 7929.442f);
2796 default:
2797 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2798 static_cast<u32>(command.src_quality));
2799 return 0;
2800 }
2801
2802 default:
2803 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2804 return 0;
2805 }
2806}
2807
2808u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2809 [[maybe_unused]] const VolumeCommand& command) const {
2810 switch (sample_count) {
2811 case 160:
2812 return static_cast<u32>(1311.1f);
2813 case 240:
2814 return static_cast<u32>(1713.6f);
2815 default:
2816 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2817 return 0;
2818 }
2819}
2820
2821u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2822 [[maybe_unused]] const VolumeRampCommand& command) const {
2823 switch (sample_count) {
2824 case 160:
2825 return static_cast<u32>(1425.3f);
2826 case 240:
2827 return static_cast<u32>(1700.0f);
2828 default:
2829 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2830 return 0;
2831 }
2832}
2833
2834u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2835 [[maybe_unused]] const BiquadFilterCommand& command) const {
2836 switch (sample_count) {
2837 case 160:
2838 return static_cast<u32>(4173.2f);
2839 case 240:
2840 return static_cast<u32>(5585.1f);
2841 default:
2842 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2843 return 0;
2844 }
2845}
2846
2847u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2848 [[maybe_unused]] const MixCommand& command) const {
2849 switch (sample_count) {
2850 case 160:
2851 return static_cast<u32>(1402.8f);
2852 case 240:
2853 return static_cast<u32>(1853.2f);
2854 default:
2855 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2856 return 0;
2857 }
2858}
2859
2860u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2861 [[maybe_unused]] const MixRampCommand& command) const {
2862 switch (sample_count) {
2863 case 160:
2864 return static_cast<u32>(1968.7f);
2865 case 240:
2866 return static_cast<u32>(2459.4f);
2867 default:
2868 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2869 return 0;
2870 }
2871}
2872
2873u32 CommandProcessingTimeEstimatorVersion5::Estimate(const MixRampGroupedCommand& command) const {
2874 u32 count{0};
2875 for (u32 i = 0; i < command.buffer_count; i++) {
2876 if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
2877 count++;
2878 }
2879 }
2880
2881 switch (sample_count) {
2882 case 160:
2883 return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
2884 static_cast<f32>(count));
2885 case 240:
2886 return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
2887 static_cast<f32>(count));
2888 default:
2889 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2890 return 0;
2891 }
2892}
2893
2894u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2895 [[maybe_unused]] const DepopPrepareCommand& command) const {
2896 return 0;
2897}
2898
2899u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2900 [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
2901 switch (sample_count) {
2902 case 160:
2903 return static_cast<u32>(739.64f);
2904 case 240:
2905 return static_cast<u32>(910.97f);
2906 default:
2907 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2908 return 0;
2909 }
2910}
2911
2912u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DelayCommand& command) const {
2913 switch (sample_count) {
2914 case 160:
2915 if (command.enabled) {
2916 switch (command.parameter.channel_count) {
2917 case 1:
2918 return static_cast<u32>(8929.042f);
2919 case 2:
2920 return static_cast<u32>(25500.75f);
2921 case 4:
2922 return static_cast<u32>(47759.617f);
2923 case 6:
2924 return static_cast<u32>(82203.07f);
2925 default:
2926 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2927 command.parameter.channel_count);
2928 return 0;
2929 }
2930 }
2931 switch (command.parameter.channel_count) {
2932 case 1:
2933 return static_cast<u32>(1295.206f);
2934 case 2:
2935 return static_cast<u32>(1213.6f);
2936 case 4:
2937 return static_cast<u32>(942.028f);
2938 case 6:
2939 return static_cast<u32>(1001.553f);
2940 default:
2941 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2942 return 0;
2943 }
2944 case 240:
2945 if (command.enabled) {
2946 switch (command.parameter.channel_count) {
2947 case 1:
2948 return static_cast<u32>(11941.051f);
2949 case 2:
2950 return static_cast<u32>(37197.371f);
2951 case 4:
2952 return static_cast<u32>(69749.836f);
2953 case 6:
2954 return static_cast<u32>(120042.398f);
2955 default:
2956 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2957 command.parameter.channel_count);
2958 return 0;
2959 }
2960 }
2961 switch (command.parameter.channel_count) {
2962 case 1:
2963 return static_cast<u32>(997.668f);
2964 case 2:
2965 return static_cast<u32>(977.634f);
2966 case 4:
2967 return static_cast<u32>(792.309f);
2968 case 6:
2969 return static_cast<u32>(875.427f);
2970 default:
2971 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2972 return 0;
2973 }
2974 default:
2975 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2976 return 0;
2977 }
2978}
2979
2980u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2981 [[maybe_unused]] const UpsampleCommand& command) const {
2982 switch (sample_count) {
2983 case 160:
2984 return static_cast<u32>(312990.0f);
2985 case 240:
2986 return static_cast<u32>(0.0f);
2987 default:
2988 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2989 return 0;
2990 }
2991}
2992
2993u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2994 [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
2995 switch (sample_count) {
2996 case 160:
2997 return static_cast<u32>(9949.7f);
2998 case 240:
2999 return static_cast<u32>(14679.0f);
3000 default:
3001 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3002 return 0;
3003 }
3004}
3005
3006u32 CommandProcessingTimeEstimatorVersion5::Estimate(const AuxCommand& command) const {
3007 switch (sample_count) {
3008 case 160:
3009 if (command.enabled) {
3010 return static_cast<u32>(7182.136f);
3011 }
3012 return static_cast<u32>(472.111f);
3013 case 240:
3014 if (command.enabled) {
3015 return static_cast<u32>(9435.961f);
3016 }
3017 return static_cast<u32>(462.619f);
3018 default:
3019 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3020 return 0;
3021 }
3022}
3023
3024u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DeviceSinkCommand& command) const {
3025 switch (command.input_count) {
3026 case 2:
3027 switch (sample_count) {
3028 case 160:
3029 return static_cast<u32>(8979.956f);
3030 case 240:
3031 return static_cast<u32>(9221.907f);
3032 default:
3033 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3034 return 0;
3035 }
3036 case 6:
3037 switch (sample_count) {
3038 case 160:
3039 return static_cast<u32>(9177.903f);
3040 case 240:
3041 return static_cast<u32>(9725.897f);
3042 default:
3043 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3044 return 0;
3045 }
3046 default:
3047 LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
3048 return 0;
3049 }
3050}
3051
3052u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3053 const CircularBufferSinkCommand& command) const {
3054 switch (sample_count) {
3055 case 160:
3056 return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
3057 case 240:
3058 return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
3059 default:
3060 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3061 return 0;
3062 }
3063}
3064
3065u32 CommandProcessingTimeEstimatorVersion5::Estimate(const ReverbCommand& command) const {
3066 switch (sample_count) {
3067 case 160:
3068 if (command.enabled) {
3069 switch (command.parameter.channel_count) {
3070 case 1:
3071 return static_cast<u32>(81475.055f);
3072 case 2:
3073 return static_cast<u32>(84975.0f);
3074 case 4:
3075 return static_cast<u32>(91625.148f);
3076 case 6:
3077 return static_cast<u32>(95332.266f);
3078 default:
3079 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3080 command.parameter.channel_count);
3081 return 0;
3082 }
3083 }
3084 switch (command.parameter.channel_count) {
3085 case 1:
3086 return static_cast<u32>(536.298f);
3087 case 2:
3088 return static_cast<u32>(588.798f);
3089 case 4:
3090 return static_cast<u32>(643.702f);
3091 case 6:
3092 return static_cast<u32>(705.999f);
3093 default:
3094 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3095 return 0;
3096 }
3097 case 240:
3098 if (command.enabled) {
3099 switch (command.parameter.channel_count) {
3100 case 1:
3101 return static_cast<u32>(120174.469f);
3102 case 2:
3103 return static_cast<u32>(125262.219f);
3104 case 4:
3105 return static_cast<u32>(135751.234f);
3106 case 6:
3107 return static_cast<u32>(141129.234f);
3108 default:
3109 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3110 command.parameter.channel_count);
3111 return 0;
3112 }
3113 }
3114 switch (command.parameter.channel_count) {
3115 case 1:
3116 return static_cast<u32>(617.641f);
3117 case 2:
3118 return static_cast<u32>(659.536f);
3119 case 4:
3120 return static_cast<u32>(711.438f);
3121 case 6:
3122 return static_cast<u32>(778.071f);
3123 default:
3124 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3125 return 0;
3126 }
3127 default:
3128 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3129 return 0;
3130 }
3131}
3132
3133u32 CommandProcessingTimeEstimatorVersion5::Estimate(const I3dl2ReverbCommand& command) const {
3134 switch (sample_count) {
3135 case 160:
3136 if (command.enabled) {
3137 switch (command.parameter.channel_count) {
3138 case 1:
3139 return static_cast<u32>(116754.984f);
3140 case 2:
3141 return static_cast<u32>(125912.055f);
3142 case 4:
3143 return static_cast<u32>(146336.031f);
3144 case 6:
3145 return static_cast<u32>(165812.656f);
3146 default:
3147 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3148 command.parameter.channel_count);
3149 return 0;
3150 }
3151 }
3152 switch (command.parameter.channel_count) {
3153 case 1:
3154 return static_cast<u32>(735.0f);
3155 case 2:
3156 return static_cast<u32>(766.615f);
3157 case 4:
3158 return static_cast<u32>(834.067f);
3159 case 6:
3160 return static_cast<u32>(875.437f);
3161 default:
3162 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3163 return 0;
3164 }
3165 case 240:
3166 if (command.enabled) {
3167 switch (command.parameter.channel_count) {
3168 case 1:
3169 return static_cast<u32>(170292.344f);
3170 case 2:
3171 return static_cast<u32>(183875.625f);
3172 case 4:
3173 return static_cast<u32>(214696.188f);
3174 case 6:
3175 return static_cast<u32>(243846.766f);
3176 default:
3177 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3178 command.parameter.channel_count);
3179 return 0;
3180 }
3181 }
3182 switch (command.parameter.channel_count) {
3183 case 1:
3184 return static_cast<u32>(508.473f);
3185 case 2:
3186 return static_cast<u32>(582.445f);
3187 case 4:
3188 return static_cast<u32>(626.419f);
3189 case 6:
3190 return static_cast<u32>(682.468f);
3191 default:
3192 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3193 return 0;
3194 }
3195 default:
3196 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3197 return 0;
3198 }
3199}
3200
3201u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3202 [[maybe_unused]] const PerformanceCommand& command) const {
3203 switch (sample_count) {
3204 case 160:
3205 return static_cast<u32>(498.17f);
3206 case 240:
3207 return static_cast<u32>(489.42f);
3208 default:
3209 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3210 return 0;
3211 }
3212}
3213
3214u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3215 [[maybe_unused]] const ClearMixBufferCommand& command) const {
3216 switch (sample_count) {
3217 case 160:
3218 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
3219 case 240:
3220 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
3221 default:
3222 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3223 return 0;
3224 }
3225}
3226
3227u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3228 [[maybe_unused]] const CopyMixBufferCommand& command) const {
3229 switch (sample_count) {
3230 case 160:
3231 return static_cast<u32>(842.59f);
3232 case 240:
3233 return static_cast<u32>(986.72f);
3234 default:
3235 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3236 return 0;
3237 }
3238}
3239
3240u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3241 const LightLimiterVersion1Command& command) const {
3242 switch (sample_count) {
3243 case 160:
3244 if (command.enabled) {
3245 switch (command.parameter.channel_count) {
3246 case 1:
3247 return static_cast<u32>(21508.01f);
3248 case 2:
3249 return static_cast<u32>(23120.453f);
3250 case 4:
3251 return static_cast<u32>(26270.053f);
3252 case 6:
3253 return static_cast<u32>(40471.902f);
3254 default:
3255 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3256 command.parameter.channel_count);
3257 return 0;
3258 }
3259 }
3260 switch (command.parameter.channel_count) {
3261 case 1:
3262 return static_cast<u32>(897.004f);
3263 case 2:
3264 return static_cast<u32>(931.549f);
3265 case 4:
3266 return static_cast<u32>(975.387f);
3267 case 6:
3268 return static_cast<u32>(1016.778f);
3269 default:
3270 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3271 return 0;
3272 }
3273 case 240:
3274 if (command.enabled) {
3275 switch (command.parameter.channel_count) {
3276 case 1:
3277 return static_cast<u32>(30565.961f);
3278 case 2:
3279 return static_cast<u32>(32812.91f);
3280 case 4:
3281 return static_cast<u32>(37354.852f);
3282 case 6:
3283 return static_cast<u32>(58486.699f);
3284 default:
3285 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3286 command.parameter.channel_count);
3287 return 0;
3288 }
3289 }
3290 switch (command.parameter.channel_count) {
3291 case 1:
3292 return static_cast<u32>(874.429f);
3293 case 2:
3294 return static_cast<u32>(921.553f);
3295 case 4:
3296 return static_cast<u32>(945.262f);
3297 case 6:
3298 return static_cast<u32>(992.26f);
3299 default:
3300 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3301 return 0;
3302 }
3303 default:
3304 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3305 return 0;
3306 }
3307}
3308
3309u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3310 const LightLimiterVersion2Command& command) const {
3311 switch (sample_count) {
3312 case 160:
3313 if (command.enabled) {
3314 if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) {
3315 if (command.parameter.statistics_enabled) {
3316 switch (command.parameter.channel_count) {
3317 case 1:
3318 return static_cast<u32>(23639.584f);
3319 case 2:
3320 return static_cast<u32>(24666.725f);
3321 case 4:
3322 return static_cast<u32>(28876.459f);
3323 case 6:
3324 return static_cast<u32>(47096.078f);
3325 default:
3326 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3327 command.parameter.channel_count);
3328 return 0;
3329 }
3330 } else {
3331 if (command.parameter.statistics_enabled) {
3332 switch (command.parameter.channel_count) {
3333 case 1:
3334 return static_cast<u32>(21508.01f);
3335 case 2:
3336 return static_cast<u32>(23120.453f);
3337 case 4:
3338 return static_cast<u32>(26270.053f);
3339 case 6:
3340 return static_cast<u32>(40471.902f);
3341 default:
3342 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3343 command.parameter.channel_count);
3344 return 0;
3345 }
3346 }
3347 }
3348 } else if (command.parameter.processing_mode ==
3349 LightLimiterInfo::ProcessingMode::Mode1) {
3350 if (command.parameter.statistics_enabled) {
3351 switch (command.parameter.channel_count) {
3352 case 1:
3353 return static_cast<u32>(23639.584f);
3354 case 2:
3355 return static_cast<u32>(29954.062f);
3356 case 4:
3357 return static_cast<u32>(35807.477f);
3358 case 6:
3359 return static_cast<u32>(58339.773f);
3360 default:
3361 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3362 command.parameter.channel_count);
3363 return 0;
3364 }
3365 } else {
3366 if (command.parameter.statistics_enabled) {
3367 switch (command.parameter.channel_count) {
3368 case 1:
3369 return static_cast<u32>(23639.584f);
3370 case 2:
3371 return static_cast<u32>(29954.062f);
3372 case 4:
3373 return static_cast<u32>(35807.477f);
3374 case 6:
3375 return static_cast<u32>(58339.773f);
3376 default:
3377 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3378 command.parameter.channel_count);
3379 return 0;
3380 }
3381 }
3382 }
3383 } else {
3384 LOG_ERROR(Service_Audio, "Invalid processing mode {}",
3385 command.parameter.processing_mode);
3386 return 0;
3387 }
3388 }
3389 switch (command.parameter.channel_count) {
3390 case 1:
3391 return static_cast<u32>(897.004f);
3392 case 2:
3393 return static_cast<u32>(931.549f);
3394 case 4:
3395 return static_cast<u32>(975.387f);
3396 case 6:
3397 return static_cast<u32>(1016.778f);
3398 default:
3399 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3400 return 0;
3401 }
3402 case 240:
3403 if (command.enabled) {
3404 if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) {
3405 if (command.parameter.statistics_enabled) {
3406 switch (command.parameter.channel_count) {
3407 case 1:
3408 return static_cast<u32>(33875.023f);
3409 case 2:
3410 return static_cast<u32>(35199.938f);
3411 case 4:
3412 return static_cast<u32>(41371.230f);
3413 case 6:
3414 return static_cast<u32>(68370.914f);
3415 default:
3416 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3417 command.parameter.channel_count);
3418 return 0;
3419 }
3420 } else {
3421 switch (command.parameter.channel_count) {
3422 case 1:
3423 return static_cast<u32>(30565.961f);
3424 case 2:
3425 return static_cast<u32>(32812.91f);
3426 case 4:
3427 return static_cast<u32>(37354.852f);
3428 case 6:
3429 return static_cast<u32>(58486.699f);
3430 default:
3431 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3432 command.parameter.channel_count);
3433 return 0;
3434 }
3435 }
3436 } else if (command.parameter.processing_mode ==
3437 LightLimiterInfo::ProcessingMode::Mode1) {
3438 if (command.parameter.statistics_enabled) {
3439 switch (command.parameter.channel_count) {
3440 case 1:
3441 return static_cast<u32>(33942.980f);
3442 case 2:
3443 return static_cast<u32>(28698.893f);
3444 case 4:
3445 return static_cast<u32>(34774.277f);
3446 case 6:
3447 return static_cast<u32>(61897.773f);
3448 default:
3449 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3450 command.parameter.channel_count);
3451 return 0;
3452 }
3453 } else {
3454 switch (command.parameter.channel_count) {
3455 case 1:
3456 return static_cast<u32>(30610.248f);
3457 case 2:
3458 return static_cast<u32>(26322.408f);
3459 case 4:
3460 return static_cast<u32>(30369.000f);
3461 case 6:
3462 return static_cast<u32>(51892.090f);
3463 default:
3464 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3465 command.parameter.channel_count);
3466 return 0;
3467 }
3468 }
3469 } else {
3470 LOG_ERROR(Service_Audio, "Invalid processing mode {}",
3471 command.parameter.processing_mode);
3472 return 0;
3473 }
3474 }
3475 switch (command.parameter.channel_count) {
3476 case 1:
3477 return static_cast<u32>(874.429f);
3478 case 2:
3479 return static_cast<u32>(921.553f);
3480 case 4:
3481 return static_cast<u32>(945.262f);
3482 case 6:
3483 return static_cast<u32>(992.26f);
3484 default:
3485 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3486 return 0;
3487 }
3488 default:
3489 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3490 return 0;
3491 }
3492}
3493
3494u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3495 [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
3496 switch (sample_count) {
3497 case 160:
3498 return static_cast<u32>(7424.5f);
3499 case 240:
3500 return static_cast<u32>(9730.4f);
3501 default:
3502 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3503 return 0;
3504 }
3505}
3506
3507u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CaptureCommand& command) const {
3508 switch (sample_count) {
3509 case 160:
3510 if (command.enabled) {
3511 return static_cast<u32>(426.982f);
3512 }
3513 return static_cast<u32>(4261.005f);
3514 case 240:
3515 if (command.enabled) {
3516 return static_cast<u32>(435.204f);
3517 }
3518 return static_cast<u32>(5858.265f);
3519 default:
3520 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3521 return 0;
3522 }
3523}
3524
3525u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CompressorCommand& command) const {
3526 if (command.enabled) {
3527 switch (command.parameter.channel_count) {
3528 case 1:
3529 switch (sample_count) {
3530 case 160:
3531 return static_cast<u32>(34430.570f);
3532 case 240:
3533 return static_cast<u32>(51095.348f);
3534 default:
3535 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3536 return 0;
3537 }
3538 case 2:
3539 switch (sample_count) {
3540 case 160:
3541 return static_cast<u32>(44253.320f);
3542 case 240:
3543 return static_cast<u32>(65693.094f);
3544 default:
3545 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3546 return 0;
3547 }
3548 case 4:
3549 switch (sample_count) {
3550 case 160:
3551 return static_cast<u32>(63827.457f);
3552 case 240:
3553 return static_cast<u32>(95382.852f);
3554 default:
3555 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3556 return 0;
3557 }
3558 case 6:
3559 switch (sample_count) {
3560 case 160:
3561 return static_cast<u32>(83361.484f);
3562 case 240:
3563 return static_cast<u32>(124509.906f);
3564 default:
3565 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3566 return 0;
3567 }
3568 default:
3569 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3570 return 0;
3571 }
3572 }
3573 switch (command.parameter.channel_count) {
3574 case 1:
3575 switch (sample_count) {
3576 case 160:
3577 return static_cast<u32>(630.115f);
3578 case 240:
3579 return static_cast<u32>(840.136f);
3580 default:
3581 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3582 return 0;
3583 }
3584 case 2:
3585 switch (sample_count) {
3586 case 160:
3587 return static_cast<u32>(638.274f);
3588 case 240:
3589 return static_cast<u32>(826.098f);
3590 default:
3591 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3592 return 0;
3593 }
3594 case 4:
3595 switch (sample_count) {
3596 case 160:
3597 return static_cast<u32>(705.862f);
3598 case 240:
3599 return static_cast<u32>(901.876f);
3600 default:
3601 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3602 return 0;
3603 }
3604 case 6:
3605 switch (sample_count) {
3606 case 160:
3607 return static_cast<u32>(782.019f);
3608 case 240:
3609 return static_cast<u32>(965.286f);
3610 default:
3611 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3612 return 0;
3613 }
3614 default:
3615 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3616 return 0;
3617 }
3618}
3619
3620} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/command/commands.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Estimate the processing time required for all commands.
12 */
13class ICommandProcessingTimeEstimator {
14public:
15 virtual ~ICommandProcessingTimeEstimator() = default;
16
17 virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0;
18 virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0;
19 virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0;
20 virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0;
21 virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0;
22 virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0;
23 virtual u32 Estimate(const VolumeCommand& command) const = 0;
24 virtual u32 Estimate(const VolumeRampCommand& command) const = 0;
25 virtual u32 Estimate(const BiquadFilterCommand& command) const = 0;
26 virtual u32 Estimate(const MixCommand& command) const = 0;
27 virtual u32 Estimate(const MixRampCommand& command) const = 0;
28 virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0;
29 virtual u32 Estimate(const DepopPrepareCommand& command) const = 0;
30 virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0;
31 virtual u32 Estimate(const DelayCommand& command) const = 0;
32 virtual u32 Estimate(const UpsampleCommand& command) const = 0;
33 virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0;
34 virtual u32 Estimate(const AuxCommand& command) const = 0;
35 virtual u32 Estimate(const DeviceSinkCommand& command) const = 0;
36 virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0;
37 virtual u32 Estimate(const ReverbCommand& command) const = 0;
38 virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0;
39 virtual u32 Estimate(const PerformanceCommand& command) const = 0;
40 virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0;
41 virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0;
42 virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0;
43 virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0;
44 virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0;
45 virtual u32 Estimate(const CaptureCommand& command) const = 0;
46 virtual u32 Estimate(const CompressorCommand& command) const = 0;
47};
48
49class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator {
50public:
51 CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_)
52 : sample_count{sample_count_}, buffer_count{buffer_count_} {}
53
54 u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
55 u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
56 u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
57 u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
58 u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
59 u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
60 u32 Estimate(const VolumeCommand& command) const override;
61 u32 Estimate(const VolumeRampCommand& command) const override;
62 u32 Estimate(const BiquadFilterCommand& command) const override;
63 u32 Estimate(const MixCommand& command) const override;
64 u32 Estimate(const MixRampCommand& command) const override;
65 u32 Estimate(const MixRampGroupedCommand& command) const override;
66 u32 Estimate(const DepopPrepareCommand& command) const override;
67 u32 Estimate(const DepopForMixBuffersCommand& command) const override;
68 u32 Estimate(const DelayCommand& command) const override;
69 u32 Estimate(const UpsampleCommand& command) const override;
70 u32 Estimate(const DownMix6chTo2chCommand& command) const override;
71 u32 Estimate(const AuxCommand& command) const override;
72 u32 Estimate(const DeviceSinkCommand& command) const override;
73 u32 Estimate(const CircularBufferSinkCommand& command) const override;
74 u32 Estimate(const ReverbCommand& command) const override;
75 u32 Estimate(const I3dl2ReverbCommand& command) const override;
76 u32 Estimate(const PerformanceCommand& command) const override;
77 u32 Estimate(const ClearMixBufferCommand& command) const override;
78 u32 Estimate(const CopyMixBufferCommand& command) const override;
79 u32 Estimate(const LightLimiterVersion1Command& command) const override;
80 u32 Estimate(const LightLimiterVersion2Command& command) const override;
81 u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
82 u32 Estimate(const CaptureCommand& command) const override;
83 u32 Estimate(const CompressorCommand& command) const override;
84
85private:
86 u32 sample_count{};
87 u32 buffer_count{};
88};
89
90class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator {
91public:
92 CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_)
93 : sample_count{sample_count_}, buffer_count{buffer_count_} {}
94
95 u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
96 u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
97 u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
98 u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
99 u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
100 u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
101 u32 Estimate(const VolumeCommand& command) const override;
102 u32 Estimate(const VolumeRampCommand& command) const override;
103 u32 Estimate(const BiquadFilterCommand& command) const override;
104 u32 Estimate(const MixCommand& command) const override;
105 u32 Estimate(const MixRampCommand& command) const override;
106 u32 Estimate(const MixRampGroupedCommand& command) const override;
107 u32 Estimate(const DepopPrepareCommand& command) const override;
108 u32 Estimate(const DepopForMixBuffersCommand& command) const override;
109 u32 Estimate(const DelayCommand& command) const override;
110 u32 Estimate(const UpsampleCommand& command) const override;
111 u32 Estimate(const DownMix6chTo2chCommand& command) const override;
112 u32 Estimate(const AuxCommand& command) const override;
113 u32 Estimate(const DeviceSinkCommand& command) const override;
114 u32 Estimate(const CircularBufferSinkCommand& command) const override;
115 u32 Estimate(const ReverbCommand& command) const override;
116 u32 Estimate(const I3dl2ReverbCommand& command) const override;
117 u32 Estimate(const PerformanceCommand& command) const override;
118 u32 Estimate(const ClearMixBufferCommand& command) const override;
119 u32 Estimate(const CopyMixBufferCommand& command) const override;
120 u32 Estimate(const LightLimiterVersion1Command& command) const override;
121 u32 Estimate(const LightLimiterVersion2Command& command) const override;
122 u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
123 u32 Estimate(const CaptureCommand& command) const override;
124 u32 Estimate(const CompressorCommand& command) const override;
125
126private:
127 u32 sample_count{};
128 u32 buffer_count{};
129};
130
131class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator {
132public:
133 CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_)
134 : sample_count{sample_count_}, buffer_count{buffer_count_} {}
135
136 u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
137 u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
138 u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
139 u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
140 u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
141 u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
142 u32 Estimate(const VolumeCommand& command) const override;
143 u32 Estimate(const VolumeRampCommand& command) const override;
144 u32 Estimate(const BiquadFilterCommand& command) const override;
145 u32 Estimate(const MixCommand& command) const override;
146 u32 Estimate(const MixRampCommand& command) const override;
147 u32 Estimate(const MixRampGroupedCommand& command) const override;
148 u32 Estimate(const DepopPrepareCommand& command) const override;
149 u32 Estimate(const DepopForMixBuffersCommand& command) const override;
150 u32 Estimate(const DelayCommand& command) const override;
151 u32 Estimate(const UpsampleCommand& command) const override;
152 u32 Estimate(const DownMix6chTo2chCommand& command) const override;
153 u32 Estimate(const AuxCommand& command) const override;
154 u32 Estimate(const DeviceSinkCommand& command) const override;
155 u32 Estimate(const CircularBufferSinkCommand& command) const override;
156 u32 Estimate(const ReverbCommand& command) const override;
157 u32 Estimate(const I3dl2ReverbCommand& command) const override;
158 u32 Estimate(const PerformanceCommand& command) const override;
159 u32 Estimate(const ClearMixBufferCommand& command) const override;
160 u32 Estimate(const CopyMixBufferCommand& command) const override;
161 u32 Estimate(const LightLimiterVersion1Command& command) const override;
162 u32 Estimate(const LightLimiterVersion2Command& command) const override;
163 u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
164 u32 Estimate(const CaptureCommand& command) const override;
165 u32 Estimate(const CompressorCommand& command) const override;
166
167private:
168 u32 sample_count{};
169 u32 buffer_count{};
170};
171
172class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator {
173public:
174 CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_)
175 : sample_count{sample_count_}, buffer_count{buffer_count_} {}
176
177 u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
178 u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
179 u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
180 u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
181 u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
182 u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
183 u32 Estimate(const VolumeCommand& command) const override;
184 u32 Estimate(const VolumeRampCommand& command) const override;
185 u32 Estimate(const BiquadFilterCommand& command) const override;
186 u32 Estimate(const MixCommand& command) const override;
187 u32 Estimate(const MixRampCommand& command) const override;
188 u32 Estimate(const MixRampGroupedCommand& command) const override;
189 u32 Estimate(const DepopPrepareCommand& command) const override;
190 u32 Estimate(const DepopForMixBuffersCommand& command) const override;
191 u32 Estimate(const DelayCommand& command) const override;
192 u32 Estimate(const UpsampleCommand& command) const override;
193 u32 Estimate(const DownMix6chTo2chCommand& command) const override;
194 u32 Estimate(const AuxCommand& command) const override;
195 u32 Estimate(const DeviceSinkCommand& command) const override;
196 u32 Estimate(const CircularBufferSinkCommand& command) const override;
197 u32 Estimate(const ReverbCommand& command) const override;
198 u32 Estimate(const I3dl2ReverbCommand& command) const override;
199 u32 Estimate(const PerformanceCommand& command) const override;
200 u32 Estimate(const ClearMixBufferCommand& command) const override;
201 u32 Estimate(const CopyMixBufferCommand& command) const override;
202 u32 Estimate(const LightLimiterVersion1Command& command) const override;
203 u32 Estimate(const LightLimiterVersion2Command& command) const override;
204 u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
205 u32 Estimate(const CaptureCommand& command) const override;
206 u32 Estimate(const CompressorCommand& command) const override;
207
208private:
209 u32 sample_count{};
210 u32 buffer_count{};
211};
212
213class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator {
214public:
215 CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_)
216 : sample_count{sample_count_}, buffer_count{buffer_count_} {}
217
218 u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
219 u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
220 u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
221 u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
222 u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
223 u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
224 u32 Estimate(const VolumeCommand& command) const override;
225 u32 Estimate(const VolumeRampCommand& command) const override;
226 u32 Estimate(const BiquadFilterCommand& command) const override;
227 u32 Estimate(const MixCommand& command) const override;
228 u32 Estimate(const MixRampCommand& command) const override;
229 u32 Estimate(const MixRampGroupedCommand& command) const override;
230 u32 Estimate(const DepopPrepareCommand& command) const override;
231 u32 Estimate(const DepopForMixBuffersCommand& command) const override;
232 u32 Estimate(const DelayCommand& command) const override;
233 u32 Estimate(const UpsampleCommand& command) const override;
234 u32 Estimate(const DownMix6chTo2chCommand& command) const override;
235 u32 Estimate(const AuxCommand& command) const override;
236 u32 Estimate(const DeviceSinkCommand& command) const override;
237 u32 Estimate(const CircularBufferSinkCommand& command) const override;
238 u32 Estimate(const ReverbCommand& command) const override;
239 u32 Estimate(const I3dl2ReverbCommand& command) const override;
240 u32 Estimate(const PerformanceCommand& command) const override;
241 u32 Estimate(const ClearMixBufferCommand& command) const override;
242 u32 Estimate(const CopyMixBufferCommand& command) const override;
243 u32 Estimate(const LightLimiterVersion1Command& command) const override;
244 u32 Estimate(const LightLimiterVersion2Command& command) const override;
245 u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
246 u32 Estimate(const CaptureCommand& command) const override;
247 u32 Estimate(const CompressorCommand& command) const override;
248
249private:
250 u32 sample_count{};
251 u32 buffer_count{};
252};
253
254} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/command/data_source/adpcm.h"
7#include "audio_core/renderer/command/data_source/pcm_float.h"
8#include "audio_core/renderer/command/data_source/pcm_int16.h"
9#include "audio_core/renderer/command/effect/aux_.h"
10#include "audio_core/renderer/command/effect/biquad_filter.h"
11#include "audio_core/renderer/command/effect/capture.h"
12#include "audio_core/renderer/command/effect/compressor.h"
13#include "audio_core/renderer/command/effect/delay.h"
14#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
15#include "audio_core/renderer/command/effect/light_limiter.h"
16#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
17#include "audio_core/renderer/command/effect/reverb.h"
18#include "audio_core/renderer/command/icommand.h"
19#include "audio_core/renderer/command/mix/clear_mix.h"
20#include "audio_core/renderer/command/mix/copy_mix.h"
21#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
22#include "audio_core/renderer/command/mix/depop_prepare.h"
23#include "audio_core/renderer/command/mix/mix.h"
24#include "audio_core/renderer/command/mix/mix_ramp.h"
25#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
26#include "audio_core/renderer/command/mix/volume.h"
27#include "audio_core/renderer/command/mix/volume_ramp.h"
28#include "audio_core/renderer/command/performance/performance.h"
29#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
30#include "audio_core/renderer/command/resample/upsample.h"
31#include "audio_core/renderer/command/sink/circular_buffer.h"
32#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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <span>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/data_source/adpcm.h"
8#include "audio_core/renderer/command/data_source/decode.h"
9
10namespace AudioCore::AudioRenderer {
11
12void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
13 std::string& string) {
14 string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample "
15 "rate {} target sample rate {} src quality {}\n",
16 output_index, sample_rate, processor.target_sample_rate, src_quality);
17}
18
19void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
20 auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
21 processor.sample_count)};
22
23 DecodeFromWaveBuffersArgs args{
24 .sample_format{SampleFormat::Adpcm},
25 .output{out_buffer},
26 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
27 .wave_buffers{wave_buffers},
28 .channel{0},
29 .channel_count{1},
30 .src_quality{src_quality},
31 .pitch{pitch},
32 .source_sample_rate{sample_rate},
33 .target_sample_rate{processor.target_sample_rate},
34 .sample_count{processor.sample_count},
35 .data_address{data_address},
36 .data_size{data_size},
37 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
38 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
39 };
40
41 DecodeFromWaveBuffers(*processor.memory, args);
42}
43
44bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
45 return true;
46}
47
48void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
49 std::string& string) {
50 string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample "
51 "rate {} target sample rate {} src quality {}\n",
52 output_index, sample_rate, processor.target_sample_rate, src_quality);
53}
54
55void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
56 auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
57 processor.sample_count)};
58
59 DecodeFromWaveBuffersArgs args{
60 .sample_format{SampleFormat::Adpcm},
61 .output{out_buffer},
62 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
63 .wave_buffers{wave_buffers},
64 .channel{0},
65 .channel_count{1},
66 .src_quality{src_quality},
67 .pitch{pitch},
68 .source_sample_rate{sample_rate},
69 .target_sample_rate{processor.target_sample_rate},
70 .sample_count{processor.sample_count},
71 .data_address{data_address},
72 .data_size{data_size},
73 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
74 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
75 };
76
77 DecodeFromWaveBuffers(*processor.memory, args);
78}
79
80bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
81 return true;
82}
83
84} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/common/common.h"
10#include "audio_core/common/wave_buffer.h"
11#include "audio_core/renderer/command/icommand.h"
12#include "common/common_types.h"
13
14namespace AudioCore::AudioRenderer {
15namespace ADSP {
16class CommandListProcessor;
17}
18
19/**
20 * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers
21 * into the output_index mix buffer.
22 */
23struct AdpcmDataSourceVersion1Command : ICommand {
24 /**
25 * Print this command's information to a string.
26 *
27 * @param processor - The CommandListProcessor processing this command.
28 * @param string - The string to print into.
29 */
30 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
31
32 /**
33 * Process this command.
34 *
35 * @param processor - The CommandListProcessor processing this command.
36 */
37 void Process(const ADSP::CommandListProcessor& processor) override;
38
39 /**
40 * Verify this command's data is valid.
41 *
42 * @param processor - The CommandListProcessor processing this command.
43 * @return True if the command is valid, otherwise false.
44 */
45 bool Verify(const ADSP::CommandListProcessor& processor) override;
46
47 /// Quality used for sample rate conversion
48 SrcQuality src_quality;
49 /// Mix buffer index for decoded samples
50 s16 output_index;
51 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
52 u16 flags;
53 /// Wavebuffer sample rate
54 u32 sample_rate;
55 /// Pitch used for sample rate conversion
56 f32 pitch;
57 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
58 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
59 /// Voice state, updated each call and written back to game
60 CpuAddr voice_state;
61 /// Coefficients data address
62 CpuAddr data_address;
63 /// Coefficients data size
64 u64 data_size;
65};
66
67/**
68 * AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers
69 * into the output_index mix buffer.
70 */
71struct AdpcmDataSourceVersion2Command : ICommand {
72 /**
73 * Print this command's information to a string.
74 *
75 * @param processor - The CommandListProcessor processing this command.
76 * @param string - The string to print into.
77 */
78 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
79
80 /**
81 * Process this command.
82 *
83 * @param processor - The CommandListProcessor processing this command.
84 */
85 void Process(const ADSP::CommandListProcessor& processor) override;
86
87 /**
88 * Verify this command's data is valid.
89 *
90 * @param processor - The CommandListProcessor processing this command.
91 * @return True if the command is valid, otherwise false.
92 */
93 bool Verify(const ADSP::CommandListProcessor& processor) override;
94
95 /// Quality used for sample rate conversion
96 SrcQuality src_quality;
97 /// Mix buffer index for decoded samples
98 s16 output_index;
99 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
100 u16 flags;
101 /// Wavebuffer sample rate
102 u32 sample_rate;
103 /// Pitch used for sample rate conversion
104 f32 pitch;
105 /// Target channel to read within the wavebuffer
106 s8 channel_index;
107 /// Number of channels within the wavebuffer
108 s8 channel_count;
109 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
110 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
111 /// Voice state, updated each call and written back to game
112 CpuAddr voice_state;
113 /// Coefficients data address
114 CpuAddr data_address;
115 /// Coefficients data size
116 u64 data_size;
117};
118
119} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <vector>
6
7#include "audio_core/renderer/command/data_source/decode.h"
8#include "audio_core/renderer/command/resample/resample.h"
9#include "common/fixed_point.h"
10#include "common/logging/log.h"
11#include "core/memory.h"
12
13namespace AudioCore::AudioRenderer {
14
15constexpr u32 TempBufferSize = 0x3F00;
16constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
17
18/**
19 * Decode PCM data. Only s16 or f32 is supported.
20 *
21 * @tparam T - Type to decode. Only s16 and f32 are supported.
22 * @param memory - Core memory for reading samples.
23 * @param out_buffer - Output mix buffer to receive the samples.
24 * @param req - Information for how to decode.
25 * @return Number of samples decoded.
26 */
27template <typename T>
28static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
29 const DecodeArg& req) {
30 constexpr s32 min{std::numeric_limits<s16>::min()};
31 constexpr s32 max{std::numeric_limits<s16>::max()};
32
33 if (req.buffer == 0 || req.buffer_size == 0) {
34 return 0;
35 }
36
37 if (req.start_offset >= req.end_offset) {
38 return 0;
39 }
40
41 auto samples_to_decode{
42 std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)};
43 u32 channel_count{static_cast<u32>(req.channel_count)};
44
45 switch (req.channel_count) {
46 default: {
47 const VAddr source{req.buffer +
48 (((req.start_offset + req.offset) * channel_count) * sizeof(T))};
49 const u64 size{channel_count * samples_to_decode};
50 const u64 size_bytes{size * sizeof(T)};
51
52 std::vector<T> samples(size);
53 memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
54
55 if constexpr (std::is_floating_point_v<T>) {
56 for (u32 i = 0; i < samples_to_decode; i++) {
57 auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
58 std::numeric_limits<s16>::max())};
59 out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
60 }
61 } else {
62 for (u32 i = 0; i < samples_to_decode; i++) {
63 out_buffer[i] = samples[i * channel_count + req.target_channel];
64 }
65 }
66 } break;
67
68 case 1:
69 if (req.target_channel != 0) {
70 LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}",
71 req.target_channel);
72 return 0;
73 }
74
75 const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
76 std::vector<T> samples(samples_to_decode);
77 memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
78
79 if constexpr (std::is_floating_point_v<T>) {
80 for (u32 i = 0; i < samples_to_decode; i++) {
81 auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
82 std::numeric_limits<s16>::max())};
83 out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
84 }
85 } else {
86 std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16));
87 }
88 break;
89 }
90
91 return samples_to_decode;
92}
93
94/**
95 * Decode ADPCM data.
96 *
97 * @param memory - Core memory for reading samples.
98 * @param out_buffer - Output mix buffer to receive the samples.
99 * @param req - Information for how to decode.
100 * @return Number of samples decoded.
101 */
102static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
103 const DecodeArg& req) {
104 constexpr u32 SamplesPerFrame{14};
105 constexpr u32 NibblesPerFrame{16};
106
107 if (req.buffer == 0 || req.buffer_size == 0) {
108 return 0;
109 }
110
111 if (req.end_offset < req.start_offset) {
112 return 0;
113 }
114
115 auto end{(req.end_offset % SamplesPerFrame) +
116 NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
117 if (req.end_offset % SamplesPerFrame) {
118 end += 3;
119 } else {
120 end += 1;
121 }
122
123 if (req.buffer_size < end / 2) {
124 return 0;
125 }
126
127 auto samples_to_process{
128 std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
129
130 auto samples_to_read{samples_to_process};
131 auto start_pos{req.start_offset + req.offset};
132 auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
133 auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
134 samples_remaining_in_frame};
135
136 if (samples_remaining_in_frame) {
137 position_in_frame += 2;
138 }
139
140 const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
141 std::vector<u8> wavebuffer(size);
142 memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
143 wavebuffer.size());
144
145 auto context{req.adpcm_context};
146 auto header{context->header};
147 u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
148 u8 scale{static_cast<u8>(header & 0xFU)};
149 s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
150 s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
151
152 auto yn0{context->yn0};
153 auto yn1{context->yn1};
154
155 static constexpr std::array<s32, 16> Steps{
156 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
157 };
158
159 const auto decode_sample = [&](const s32 code) -> s16 {
160 const auto xn = code * (1 << scale);
161 const auto prediction = coeff0 * yn0 + coeff1 * yn1;
162 const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
163 const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
164 yn1 = yn0;
165 yn0 = static_cast<s16>(saturated);
166 return yn0;
167 };
168
169 u32 read_index{0};
170 u32 write_index{0};
171
172 while (samples_to_read > 0) {
173 // Are we at a new frame?
174 if ((position_in_frame % NibblesPerFrame) == 0) {
175 header = wavebuffer[read_index++];
176 coeff_index = (header >> 4) & 0xF;
177 scale = header & 0xF;
178 coeff0 = req.coefficients[coeff_index * 2 + 0];
179 coeff1 = req.coefficients[coeff_index * 2 + 1];
180 position_in_frame += 2;
181
182 // Can we consume all of this frame's samples?
183 if (samples_to_read >= SamplesPerFrame) {
184 // Can grab all samples until the next header
185 for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
186 auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
187 auto code1{Steps[wavebuffer[read_index] & 0xF]};
188 read_index++;
189
190 out_buffer[write_index++] = decode_sample(code0);
191 out_buffer[write_index++] = decode_sample(code1);
192 }
193
194 position_in_frame += SamplesPerFrame;
195 samples_to_read -= SamplesPerFrame;
196 continue;
197 }
198 }
199
200 // Decode a single sample
201 auto code{wavebuffer[read_index]};
202 if (position_in_frame & 1) {
203 code &= 0xF;
204 read_index++;
205 } else {
206 code >>= 4;
207 }
208
209 out_buffer[write_index++] = decode_sample(Steps[code]);
210
211 position_in_frame++;
212 samples_to_read--;
213 }
214
215 context->header = header;
216 context->yn0 = yn0;
217 context->yn1 = yn1;
218
219 return samples_to_process;
220}
221
222/**
223 * Decode implementation.
224 * Decode wavebuffers according to the given args.
225 *
226 * @param memory - Core memory to read data from.
227 * @param args - The wavebuffer data, and information for how to decode it.
228 */
229void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
230 auto& voice_state{*args.voice_state};
231 auto remaining_sample_count{args.sample_count};
232 auto fraction{voice_state.fraction};
233
234 const auto sample_rate_ratio{
235 (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
236 args.pitch};
237 const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
238
239 if (size_required < 0) {
240 return;
241 }
242
243 auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
244 if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
245 return;
246 }
247
248 auto max_remaining_sample_count{
249 ((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio)
250 .to_uint_floor()};
251 max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count);
252
253 auto wavebuffers_consumed{voice_state.wave_buffers_consumed};
254 auto wavebuffer_index{voice_state.wave_buffer_index};
255 auto played_sample_count{voice_state.played_sample_count};
256
257 bool is_buffer_starved{false};
258 u32 offset{voice_state.offset};
259
260 auto output_buffer{args.output};
261 std::vector<s16> temp_buffer(TempBufferSize, 0);
262
263 while (remaining_sample_count > 0) {
264 const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};
265 const auto samples_to_read{
266 (fraction + samples_to_write * sample_rate_ratio).to_uint_floor()};
267
268 u32 temp_buffer_pos{0};
269
270 if (!args.IsVoicePitchAndSrcSkippedSupported) {
271 for (u32 i = 0; i < pitch; i++) {
272 temp_buffer[i] = voice_state.sample_history[i];
273 }
274 temp_buffer_pos = pitch;
275 }
276
277 u32 samples_read{0};
278 while (samples_read < samples_to_read) {
279 if (wavebuffer_index >= MaxWaveBuffers) {
280 LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
281 wavebuffer_index = 0;
282 voice_state.wave_buffer_valid.fill(false);
283 wavebuffers_consumed = MaxWaveBuffers;
284 }
285
286 if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
287 is_buffer_starved = true;
288 break;
289 }
290
291 auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
292
293 if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
294 wavebuffer.context != 0) {
295 memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
296 wavebuffer.context_size);
297 }
298
299 auto start_offset{wavebuffer.start_offset};
300 auto end_offset{wavebuffer.end_offset};
301
302 if (wavebuffer.loop && voice_state.loop_count > 0 &&
303 wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
304 wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
305 start_offset = wavebuffer.loop_start_offset;
306 end_offset = wavebuffer.loop_end_offset;
307 }
308
309 DecodeArg decode_arg{.buffer{wavebuffer.buffer},
310 .buffer_size{wavebuffer.buffer_size},
311 .start_offset{start_offset},
312 .end_offset{end_offset},
313 .channel_count{args.channel_count},
314 .coefficients{},
315 .adpcm_context{nullptr},
316 .target_channel{args.channel},
317 .offset{offset},
318 .samples_to_read{samples_to_read - samples_read}};
319
320 s32 samples_decoded{0};
321
322 switch (args.sample_format) {
323 case SampleFormat::PcmInt16:
324 samples_decoded = DecodePcm<s16>(
325 memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
326 decode_arg);
327 break;
328
329 case SampleFormat::PcmFloat:
330 samples_decoded = DecodePcm<f32>(
331 memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
332 decode_arg);
333 break;
334
335 case SampleFormat::Adpcm: {
336 decode_arg.adpcm_context = &voice_state.adpcm_context;
337 memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
338 samples_decoded = DecodeAdpcm(
339 memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
340 decode_arg);
341 } break;
342
343 default:
344 LOG_ERROR(Service_Audio, "Invalid sample format to decode {}",
345 static_cast<u32>(args.sample_format));
346 samples_decoded = 0;
347 break;
348 }
349
350 played_sample_count += samples_decoded;
351 samples_read += samples_decoded;
352 temp_buffer_pos += samples_decoded;
353 offset += samples_decoded;
354
355 if (samples_decoded == 0 || offset >= end_offset - start_offset) {
356 offset = 0;
357 if (!wavebuffer.loop) {
358 voice_state.wave_buffer_valid[wavebuffer_index] = false;
359 voice_state.loop_count = 0;
360
361 if (wavebuffer.stream_ended) {
362 played_sample_count = 0;
363 }
364
365 wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
366 wavebuffers_consumed++;
367 } else {
368 voice_state.loop_count++;
369 if (wavebuffer.loop_count > 0 &&
370 (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
371 voice_state.wave_buffer_valid[wavebuffer_index] = false;
372 voice_state.loop_count = 0;
373
374 if (wavebuffer.stream_ended) {
375 played_sample_count = 0;
376 }
377
378 wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
379 wavebuffers_consumed++;
380 }
381
382 if (samples_decoded == 0) {
383 is_buffer_starved = true;
384 break;
385 }
386
387 if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
388 played_sample_count = 0;
389 }
390 }
391 }
392 }
393
394 if (args.IsVoicePitchAndSrcSkippedSupported) {
395 if (samples_read > output_buffer.size()) {
396 LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!");
397 }
398 for (u32 i = 0; i < samples_read; i++) {
399 output_buffer[i] = temp_buffer[i];
400 }
401 } else {
402 std::memset(&temp_buffer[temp_buffer_pos], 0,
403 (samples_to_read - samples_read) * sizeof(s16));
404
405 Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write,
406 args.src_quality);
407
408 std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read],
409 pitch * sizeof(s16));
410 }
411
412 remaining_sample_count -= samples_to_write;
413 if (remaining_sample_count != 0 && is_buffer_starved) {
414 LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??");
415 break;
416 }
417
418 output_buffer = output_buffer.subspan(samples_to_write);
419 }
420
421 voice_state.wave_buffers_consumed = wavebuffers_consumed;
422 voice_state.played_sample_count = played_sample_count;
423 voice_state.wave_buffer_index = wavebuffer_index;
424 voice_state.offset = offset;
425 voice_state.fraction = fraction;
426}
427
428} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8
9#include "audio_core/common/common.h"
10#include "audio_core/common/wave_buffer.h"
11#include "audio_core/renderer/voice/voice_state.h"
12#include "common/common_types.h"
13
14namespace Core::Memory {
15class Memory;
16}
17
18namespace AudioCore::AudioRenderer {
19
20struct DecodeFromWaveBuffersArgs {
21 SampleFormat sample_format;
22 std::span<s32> output;
23 VoiceState* voice_state;
24 std::span<WaveBufferVersion2> wave_buffers;
25 s8 channel;
26 s8 channel_count;
27 SrcQuality src_quality;
28 f32 pitch;
29 u32 source_sample_rate;
30 u32 target_sample_rate;
31 u32 sample_count;
32 CpuAddr data_address;
33 u64 data_size;
34 bool IsVoicePlayedSampleCountResetAtLoopPointSupported;
35 bool IsVoicePitchAndSrcSkippedSupported;
36};
37
38struct DecodeArg {
39 CpuAddr buffer;
40 u64 buffer_size;
41 u32 start_offset;
42 u32 end_offset;
43 s8 channel_count;
44 std::array<s16, 16> coefficients;
45 VoiceState::AdpcmContext* adpcm_context;
46 s8 target_channel;
47 u32 offset;
48 u32 samples_to_read;
49};
50
51/**
52 * Decode wavebuffers according to the given args.
53 *
54 * @param memory - Core memory to read data from.
55 * @param args - The wavebuffer data, and information for how to decode it.
56 */
57void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args);
58
59} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/data_source/decode.h"
6#include "audio_core/renderer/command/data_source/pcm_float.h"
7
8namespace AudioCore::AudioRenderer {
9
10void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
11 std::string& string) {
12 string +=
13 fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} "
14 "channel count {} source sample rate {} target sample rate {} src quality {}\n",
15 output_index, channel_index, channel_count, sample_rate,
16 processor.target_sample_rate, src_quality);
17}
18
19void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
20 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
21 processor.sample_count);
22
23 DecodeFromWaveBuffersArgs args{
24 .sample_format{SampleFormat::PcmFloat},
25 .output{out_buffer},
26 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
27 .wave_buffers{wave_buffers},
28 .channel{channel_index},
29 .channel_count{channel_count},
30 .src_quality{src_quality},
31 .pitch{pitch},
32 .source_sample_rate{sample_rate},
33 .target_sample_rate{processor.target_sample_rate},
34 .sample_count{processor.sample_count},
35 .data_address{0},
36 .data_size{0},
37 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
38 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
39 };
40
41 DecodeFromWaveBuffers(*processor.memory, args);
42}
43
44bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
45 return true;
46}
47
48void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
49 std::string& string) {
50 string +=
51 fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} "
52 "channel count {} source sample rate {} target sample rate {} src quality {}\n",
53 output_index, channel_index, channel_count, sample_rate,
54 processor.target_sample_rate, src_quality);
55}
56
57void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
58 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
59 processor.sample_count);
60
61 DecodeFromWaveBuffersArgs args{
62 .sample_format{SampleFormat::PcmFloat},
63 .output{out_buffer},
64 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
65 .wave_buffers{wave_buffers},
66 .channel{channel_index},
67 .channel_count{channel_count},
68 .src_quality{src_quality},
69 .pitch{pitch},
70 .source_sample_rate{sample_rate},
71 .target_sample_rate{processor.target_sample_rate},
72 .sample_count{processor.sample_count},
73 .data_address{0},
74 .data_size{0},
75 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
76 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
77 };
78
79 DecodeFromWaveBuffers(*processor.memory, args);
80}
81
82bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
83 return true;
84}
85
86} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/common/wave_buffer.h"
9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers
19 * into the output_index mix buffer.
20 */
21struct PcmFloatDataSourceVersion1Command : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Quality used for sample rate conversion
46 SrcQuality src_quality;
47 /// Mix buffer index for decoded samples
48 s16 output_index;
49 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
50 u16 flags;
51 /// Wavebuffer sample rate
52 u32 sample_rate;
53 /// Pitch used for sample rate conversion
54 f32 pitch;
55 /// Target channel to read within the wavebuffer
56 s8 channel_index;
57 /// Number of channels within the wavebuffer
58 s8 channel_count;
59 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
60 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
61 /// Voice state, updated each call and written back to game
62 CpuAddr voice_state;
63};
64
65/**
66 * AudioRenderer command to decode PCM float-encoded version 2 wavebuffers
67 * into the output_index mix buffer.
68 */
69struct PcmFloatDataSourceVersion2Command : ICommand {
70 /**
71 * Print this command's information to a string.
72 *
73 * @param processor - The CommandListProcessor processing this command.
74 * @param string - The string to print into.
75 */
76 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
77
78 /**
79 * Process this command.
80 *
81 * @param processor - The CommandListProcessor processing this command.
82 */
83 void Process(const ADSP::CommandListProcessor& processor) override;
84
85 /**
86 * Verify this command's data is valid.
87 *
88 * @param processor - The CommandListProcessor processing this command.
89 * @return True if the command is valid, otherwise false.
90 */
91 bool Verify(const ADSP::CommandListProcessor& processor) override;
92
93 /// Quality used for sample rate conversion
94 SrcQuality src_quality;
95 /// Mix buffer index for decoded samples
96 s16 output_index;
97 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
98 u16 flags;
99 /// Wavebuffer sample rate
100 u32 sample_rate;
101 /// Pitch used for sample rate conversion
102 f32 pitch;
103 /// Target channel to read within the wavebuffer
104 s8 channel_index;
105 /// Number of channels within the wavebuffer
106 s8 channel_count;
107 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
108 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
109 /// Voice state, updated each call and written back to game
110 CpuAddr voice_state;
111};
112
113} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <span>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/data_source/decode.h"
8#include "audio_core/renderer/command/data_source/pcm_int16.h"
9
10namespace AudioCore::AudioRenderer {
11
12void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
13 std::string& string) {
14 string +=
15 fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} "
16 "channel count {} source sample rate {} target sample rate {} src quality {}\n",
17 output_index, channel_index, channel_count, sample_rate,
18 processor.target_sample_rate, src_quality);
19}
20
21void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
22 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
23 processor.sample_count);
24
25 DecodeFromWaveBuffersArgs args{
26 .sample_format{SampleFormat::PcmInt16},
27 .output{out_buffer},
28 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
29 .wave_buffers{wave_buffers},
30 .channel{channel_index},
31 .channel_count{channel_count},
32 .src_quality{src_quality},
33 .pitch{pitch},
34 .source_sample_rate{sample_rate},
35 .target_sample_rate{processor.target_sample_rate},
36 .sample_count{processor.sample_count},
37 .data_address{0},
38 .data_size{0},
39 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
40 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
41 };
42
43 DecodeFromWaveBuffers(*processor.memory, args);
44}
45
46bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
47 return true;
48}
49
50void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
51 std::string& string) {
52 string +=
53 fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} "
54 "channel count {} source sample rate {} target sample rate {} src quality {}\n",
55 output_index, channel_index, channel_count, sample_rate,
56 processor.target_sample_rate, src_quality);
57}
58
59void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
60 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
61 processor.sample_count);
62 DecodeFromWaveBuffersArgs args{
63 .sample_format{SampleFormat::PcmInt16},
64 .output{out_buffer},
65 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
66 .wave_buffers{wave_buffers},
67 .channel{channel_index},
68 .channel_count{channel_count},
69 .src_quality{src_quality},
70 .pitch{pitch},
71 .source_sample_rate{sample_rate},
72 .target_sample_rate{processor.target_sample_rate},
73 .sample_count{processor.sample_count},
74 .data_address{0},
75 .data_size{0},
76 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
77 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
78 };
79
80 DecodeFromWaveBuffers(*processor.memory, args);
81}
82
83bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
84 return true;
85}
86
87} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/common/wave_buffer.h"
9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers
19 * into the output_index mix buffer.
20 */
21struct PcmInt16DataSourceVersion1Command : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Quality used for sample rate conversion
46 SrcQuality src_quality;
47 /// Mix buffer index for decoded samples
48 s16 output_index;
49 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
50 u16 flags;
51 /// Wavebuffer sample rate
52 u32 sample_rate;
53 /// Pitch used for sample rate conversion
54 f32 pitch;
55 /// Target channel to read within the wavebuffer
56 s8 channel_index;
57 /// Number of channels within the wavebuffer
58 s8 channel_count;
59 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
60 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
61 /// Voice state, updated each call and written back to game
62 CpuAddr voice_state;
63};
64
65/**
66 * AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers
67 * into the output_index mix buffer.
68 */
69struct PcmInt16DataSourceVersion2Command : ICommand {
70 /**
71 * Print this command's information to a string.
72 * @param processor - The CommandListProcessor processing this command.
73 * @param string - The string to print into.
74 */
75 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
76
77 /**
78 * Process this command.
79 * @param processor - The CommandListProcessor processing this command.
80 */
81 void Process(const ADSP::CommandListProcessor& processor) override;
82
83 /**
84 * Verify this command's data is valid.
85 * @param processor - The CommandListProcessor processing this command.
86 * @return True if the command is valid, otherwise false.
87 */
88 bool Verify(const ADSP::CommandListProcessor& processor) override;
89
90 /// Quality used for sample rate conversion
91 SrcQuality src_quality;
92 /// Mix buffer index for decoded samples
93 s16 output_index;
94 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
95 u16 flags;
96 /// Wavebuffer sample rate
97 u32 sample_rate;
98 /// Pitch used for sample rate conversion
99 f32 pitch;
100 /// Target channel to read within the wavebuffer
101 s8 channel_index;
102 /// Number of channels within the wavebuffer
103 s8 channel_count;
104 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
105 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
106 /// Voice state, updated each call and written back to game
107 CpuAddr voice_state;
108};
109
110} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/aux_.h"
6#include "audio_core/renderer/effect/aux_.h"
7#include "core/memory.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Reset an AuxBuffer.
12 *
13 * @param memory - Core memory for writing.
14 * @param aux_info - Memory address pointing to the AuxInfo to reset.
15 */
16static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
17 if (aux_info == 0) {
18 LOG_ERROR(Service_Audio, "Aux info is 0!");
19 return;
20 }
21
22 auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))};
23 info->read_offset = 0;
24 info->write_offset = 0;
25 info->total_sample_count = 0;
26}
27
28/**
29 * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
30 * update_count is set, to notify the game that an update happened.
31 *
32 * @param memory - Core memory for writing.
33 * @param send_info_ - Meta information for where to write the mix buffer.
34 * @param sample_count - Unused.
35 * @param send_buffer - Memory address to write the mix buffer to.
36 * @param count_max - Maximum number of samples in the receiving buffer.
37 * @param input - Input mix buffer to write.
38 * @param write_count_ - Number of samples to write.
39 * @param write_offset - Current offset to begin writing the receiving buffer at.
40 * @param update_count - If non-zero, send_info_ will be updated.
41 * @return Number of samples written.
42 */
43static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
44 [[maybe_unused]] u32 sample_count, const CpuAddr send_buffer,
45 const u32 count_max, std::span<const s32> input,
46 const u32 write_count_, const u32 write_offset,
47 const u32 update_count) {
48 if (write_count_ > count_max) {
49 LOG_ERROR(Service_Audio,
50 "write_count must be smaller than count_max! write_count {}, count_max {}",
51 write_count_, count_max);
52 return 0;
53 }
54
55 if (input.empty()) {
56 LOG_ERROR(Service_Audio, "input buffer is empty!");
57 return 0;
58 }
59
60 if (send_buffer == 0) {
61 LOG_ERROR(Service_Audio, "send_buffer is 0!");
62 return 0;
63 }
64
65 if (count_max == 0) {
66 return 0;
67 }
68
69 AuxInfo::AuxInfoDsp send_info{};
70 memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
71
72 u32 target_write_offset{send_info.write_offset + write_offset};
73 if (target_write_offset > count_max || write_count_ == 0) {
74 return 0;
75 }
76
77 u32 write_count{write_count_};
78 u32 write_pos{0};
79 while (write_count > 0) {
80 u32 to_write{std::min(count_max - target_write_offset, write_count)};
81
82 if (to_write > 0) {
83 memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
84 &input[write_pos], to_write * sizeof(s32));
85 }
86
87 target_write_offset = (target_write_offset + to_write) % count_max;
88 write_count -= to_write;
89 write_pos += to_write;
90 }
91
92 if (update_count) {
93 send_info.write_offset = (send_info.write_offset + update_count) % count_max;
94 }
95
96 memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
97
98 return write_count_;
99}
100
101/**
102 * Read the given memory at return_buffer into the output mix buffer, and update return_info_ if
103 * update_count is set, to notify the game that an update happened.
104 *
105 * @param memory - Core memory for writing.
106 * @param return_info_ - Meta information for where to read the mix buffer.
107 * @param return_buffer - Memory address to read the samples from.
108 * @param count_max - Maximum number of samples in the receiving buffer.
109 * @param output - Output mix buffer which will receive the samples.
110 * @param count_ - Number of samples to read.
111 * @param read_offset - Current offset to begin reading the return_buffer at.
112 * @param update_count - If non-zero, send_info_ will be updated.
113 * @return Number of samples read.
114 */
115static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_,
116 const CpuAddr return_buffer, const u32 count_max, std::span<s32> output,
117 const u32 count_, const u32 read_offset, const u32 update_count) {
118 if (count_max == 0) {
119 return 0;
120 }
121
122 if (count_ > count_max) {
123 LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}",
124 count_, count_max);
125 return 0;
126 }
127
128 if (output.empty()) {
129 LOG_ERROR(Service_Audio, "output buffer is empty!");
130 return 0;
131 }
132
133 if (return_buffer == 0) {
134 LOG_ERROR(Service_Audio, "return_buffer is 0!");
135 return 0;
136 }
137
138 AuxInfo::AuxInfoDsp return_info{};
139 memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
140
141 u32 target_read_offset{return_info.read_offset + read_offset};
142 if (target_read_offset > count_max) {
143 return 0;
144 }
145
146 u32 read_count{count_};
147 u32 read_pos{0};
148 while (read_count > 0) {
149 u32 to_read{std::min(count_max - target_read_offset, read_count)};
150
151 if (to_read > 0) {
152 memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32),
153 &output[read_pos], to_read * sizeof(s32));
154 }
155
156 target_read_offset = (target_read_offset + to_read) % count_max;
157 read_count -= to_read;
158 read_pos += to_read;
159 }
160
161 if (update_count) {
162 return_info.read_offset = (return_info.read_offset + update_count) % count_max;
163 }
164
165 memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
166
167 return count_;
168}
169
170void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
171 std::string& string) {
172 string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled,
173 input, output);
174}
175
176void AuxCommand::Process(const ADSP::CommandListProcessor& processor) {
177 auto input_buffer{
178 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
179 auto output_buffer{
180 processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
181
182 if (effect_enabled) {
183 WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer,
184 count_max, input_buffer, processor.sample_count, write_offset,
185 update_count);
186
187 auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max,
188 output_buffer, processor.sample_count, write_offset,
189 update_count)};
190
191 if (read != processor.sample_count) {
192 std::memset(&output_buffer[read], 0, processor.sample_count - read);
193 }
194 } else {
195 ResetAuxBufferDsp(*processor.memory, send_buffer_info);
196 ResetAuxBufferDsp(*processor.memory, return_buffer_info);
197 if (input != output) {
198 std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes());
199 }
200 }
201}
202
203bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) {
204 return true;
205}
206
207} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game
18 * memory, and reading into the output buffer from game memory.
19 */
20struct AuxCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Input mix buffer index
45 s16 input;
46 /// Output mix buffer index
47 s16 output;
48 /// Meta info for writing
49 CpuAddr send_buffer_info;
50 /// Meta info for reading
51 CpuAddr return_buffer_info;
52 /// Game memory write buffer
53 CpuAddr send_buffer;
54 /// Game memory read buffer
55 CpuAddr return_buffer;
56 /// Max samples to read/write
57 u32 count_max;
58 /// Current read/write offset
59 u32 write_offset;
60 /// Number of samples to update per call
61 u32 update_count;
62 /// is this effect enabled?
63 bool effect_enabled;
64};
65
66} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/biquad_filter.h"
6#include "audio_core/renderer/voice/voice_state.h"
7
8namespace AudioCore::AudioRenderer {
9/**
10 * Biquad filter float implementation.
11 *
12 * @param output - Output container for filtered samples.
13 * @param input - Input container for samples to be filtered.
14 * @param b - Feedforward coefficients.
15 * @param a - Feedback coefficients.
16 * @param state - State to track previous samples between calls.
17 * @param sample_count - Number of samples to process.
18 */
19void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
20 std::array<s16, 3>& b_, std::array<s16, 2>& a_,
21 VoiceState::BiquadFilterState& state, const u32 sample_count) {
22 constexpr s64 min{std::numeric_limits<s32>::min()};
23 constexpr s64 max{std::numeric_limits<s32>::max()};
24 std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(),
25 Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(),
26 Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()};
27 std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(),
28 Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()};
29 std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(),
30 state.s3.to_double()};
31
32 for (u32 i = 0; i < sample_count; i++) {
33 f64 in_sample{static_cast<f64>(input[i])};
34 auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]};
35
36 output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max));
37
38 s[1] = s[0];
39 s[0] = in_sample;
40 s[3] = s[2];
41 s[2] = sample;
42 }
43
44 state.s0 = s[0];
45 state.s1 = s[1];
46 state.s2 = s[2];
47 state.s3 = s[3];
48}
49
50/**
51 * Biquad filter s32 implementation.
52 *
53 * @param output - Output container for filtered samples.
54 * @param input - Input container for samples to be filtered.
55 * @param b - Feedforward coefficients.
56 * @param a - Feedback coefficients.
57 * @param state - State to track previous samples between calls.
58 * @param sample_count - Number of samples to process.
59 */
60static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input,
61 std::array<s16, 3>& b_, std::array<s16, 2>& a_,
62 VoiceState::BiquadFilterState& state, const u32 sample_count) {
63 constexpr s64 min{std::numeric_limits<s32>::min()};
64 constexpr s64 max{std::numeric_limits<s32>::max()};
65 std::array<Common::FixedPoint<50, 14>, 3> b{
66 Common::FixedPoint<50, 14>::from_base(b_[0]),
67 Common::FixedPoint<50, 14>::from_base(b_[1]),
68 Common::FixedPoint<50, 14>::from_base(b_[2]),
69 };
70 std::array<Common::FixedPoint<50, 14>, 3> a{
71 Common::FixedPoint<50, 14>::from_base(a_[0]),
72 Common::FixedPoint<50, 14>::from_base(a_[1]),
73 };
74
75 for (u32 i = 0; i < sample_count; i++) {
76 s64 in_sample{input[i]};
77 auto sample{in_sample * b[0] + state.s0};
78 const auto out_sample{std::clamp(sample.to_long(), min, max)};
79
80 output[i] = static_cast<s32>(out_sample);
81
82 state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample;
83 state.s1 = 0 + b[2] * in_sample + a[1] * out_sample;
84 }
85}
86
87void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
88 std::string& string) {
89 string += fmt::format(
90 "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n",
91 input, output, needs_init, use_float_processing);
92}
93
94void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
95 auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)};
96 if (needs_init) {
97 std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState));
98 }
99
100 auto input_buffer{
101 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
102 auto output_buffer{
103 processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
104
105 if (use_float_processing) {
106 ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
107 processor.sample_count);
108 } else {
109 ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
110 processor.sample_count);
111 }
112}
113
114bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
115 return true;
116}
117
118} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "audio_core/renderer/voice/voice_info.h"
10#include "audio_core/renderer/voice/voice_state.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to
20 * the output mix buffer.
21 */
22struct BiquadFilterCommand : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer index
47 s16 input;
48 /// Output mix buffer index
49 s16 output;
50 /// Input parameters for biquad
51 VoiceInfo::BiquadFilterParameter biquad;
52 /// Biquad state, updated each call
53 CpuAddr state;
54 /// If true, reset the state
55 bool needs_init;
56 /// If true, use float processing rather than int
57 bool use_float_processing;
58};
59
60/**
61 * Biquad filter float implementation.
62 *
63 * @param output - Output container for filtered samples.
64 * @param input - Input container for samples to be filtered.
65 * @param b - Feedforward coefficients.
66 * @param a - Feedback coefficients.
67 * @param state - State to track previous samples.
68 * @param sample_count - Number of samples to process.
69 */
70void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
71 std::array<s16, 3>& b, std::array<s16, 2>& a,
72 VoiceState::BiquadFilterState& state, const u32 sample_count);
73
74} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/capture.h"
6#include "audio_core/renderer/effect/aux_.h"
7#include "core/memory.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Reset an AuxBuffer.
12 *
13 * @param memory - Core memory for writing.
14 * @param aux_info - Memory address pointing to the AuxInfo to reset.
15 */
16static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
17 if (aux_info == 0) {
18 LOG_ERROR(Service_Audio, "Aux info is 0!");
19 return;
20 }
21
22 memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0);
23 memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0);
24 memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0);
25}
26
27/**
28 * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
29 * update_count is set, to notify the game that an update happened.
30 *
31 * @param memory - Core memory for writing.
32 * @param send_info_ - Header information for where to write the mix buffer.
33 * @param send_buffer - Memory address to write the mix buffer to.
34 * @param count_max - Maximum number of samples in the receiving buffer.
35 * @param input - Input mix buffer to write.
36 * @param write_count_ - Number of samples to write.
37 * @param write_offset - Current offset to begin writing the receiving buffer at.
38 * @param update_count - If non-zero, send_info_ will be updated.
39 * @return Number of samples written.
40 */
41static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
42 const CpuAddr send_buffer, u32 count_max, std::span<const s32> input,
43 const u32 write_count_, const u32 write_offset,
44 const u32 update_count) {
45 if (write_count_ > count_max) {
46 LOG_ERROR(Service_Audio,
47 "write_count must be smaller than count_max! write_count {}, count_max {}",
48 write_count_, count_max);
49 return 0;
50 }
51
52 if (send_info_ == 0) {
53 LOG_ERROR(Service_Audio, "send_info is 0!");
54 return 0;
55 }
56
57 if (input.empty()) {
58 LOG_ERROR(Service_Audio, "input buffer is empty!");
59 return 0;
60 }
61
62 if (send_buffer == 0) {
63 LOG_ERROR(Service_Audio, "send_buffer is 0!");
64 return 0;
65 }
66
67 if (count_max == 0) {
68 return 0;
69 }
70
71 AuxInfo::AuxBufferInfo send_info{};
72 memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
73
74 u32 target_write_offset{send_info.dsp_info.write_offset + write_offset};
75 if (target_write_offset > count_max || write_count_ == 0) {
76 return 0;
77 }
78
79 u32 write_count{write_count_};
80 u32 write_pos{0};
81 while (write_count > 0) {
82 u32 to_write{std::min(count_max - target_write_offset, write_count)};
83
84 if (to_write > 0) {
85 memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
86 &input[write_pos], to_write * sizeof(s32));
87 }
88
89 target_write_offset = (target_write_offset + to_write) % count_max;
90 write_count -= to_write;
91 write_pos += to_write;
92 }
93
94 if (update_count) {
95 const auto count_diff{send_info.dsp_info.total_sample_count -
96 send_info.cpu_info.total_sample_count};
97 if (count_diff >= count_max) {
98 auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count};
99 if (dsp_lost_count - send_info.cpu_info.lost_sample_count <
100 send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) {
101 dsp_lost_count = send_info.cpu_info.lost_sample_count - 1;
102 }
103 send_info.dsp_info.lost_sample_count = dsp_lost_count;
104 }
105
106 send_info.dsp_info.write_offset =
107 (send_info.dsp_info.write_offset + update_count + count_max) % count_max;
108
109 auto new_sample_count{send_info.dsp_info.total_sample_count + update_count};
110 if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) {
111 new_sample_count = send_info.cpu_info.total_sample_count - 1;
112 }
113 send_info.dsp_info.total_sample_count = new_sample_count;
114 }
115
116 memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
117
118 return write_count_;
119}
120
121void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
122 std::string& string) {
123 string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled,
124 input, output);
125}
126
127void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) {
128 if (effect_enabled) {
129 auto input_buffer{
130 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
131 WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer,
132 processor.sample_count, write_offset, update_count);
133 } else {
134 ResetAuxBufferDsp(*processor.memory, send_buffer_info);
135 }
136}
137
138bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) {
139 return true;
140}
141
142} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory
18 * address.
19 */
20struct CaptureCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Input mix buffer index
45 s16 input;
46 /// Output mix buffer index
47 s16 output;
48 /// Meta info for writing
49 CpuAddr send_buffer_info;
50 /// Game memory write buffer
51 CpuAddr send_buffer;
52 /// Max samples to read/write
53 u32 count_max;
54 /// Current read/write offset
55 u32 write_offset;
56 /// Number of samples to update per call
57 u32 update_count;
58 /// is this effect enabled?
59 bool effect_enabled;
60};
61
62} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <cmath>
5#include <span>
6#include <vector>
7
8#include "audio_core/renderer/adsp/command_list_processor.h"
9#include "audio_core/renderer/command/effect/compressor.h"
10#include "audio_core/renderer/effect/compressor.h"
11
12namespace AudioCore::AudioRenderer {
13
14static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params,
15 CompressorInfo::State& state) {
16 const auto ratio{1.0f / params.compressor_ratio};
17 auto makeup_gain{0.0f};
18 if (params.makeup_gain_enabled) {
19 makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f;
20 }
21 state.makeup_gain = makeup_gain;
22 state.unk_18 = params.unk_28;
23
24 const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f};
25 const auto b{(a - std::trunc(a)) * 0.69315f};
26 const auto c{std::pow(2.0f, b)};
27
28 state.unk_0C = (1.0f - ratio) / 6.0f;
29 state.unk_14 = params.threshold + 1.5f;
30 state.unk_10 = params.threshold - 1.5f;
31 state.unk_20 = c;
32}
33
34static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params,
35 CompressorInfo::State& state) {
36 std::memset(&state, 0, sizeof(CompressorInfo::State));
37
38 state.unk_00 = 0;
39 state.unk_04 = 1.0f;
40 state.unk_08 = 1.0f;
41
42 SetCompressorEffectParameter(params, state);
43}
44
45static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params,
46 CompressorInfo::State& state, bool enabled,
47 std::vector<std::span<const s32>> input_buffers,
48 std::vector<std::span<s32>> output_buffers, u32 sample_count) {
49 if (enabled) {
50 auto state_00{state.unk_00};
51 auto state_04{state.unk_04};
52 auto state_08{state.unk_08};
53 auto state_18{state.unk_18};
54
55 for (u32 i = 0; i < sample_count; i++) {
56 auto a{0.0f};
57 for (s16 channel = 0; channel < params.channel_count; channel++) {
58 const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])};
59 a += (input_sample * input_sample).to_float();
60 }
61
62 state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00);
63
64 auto b{-100.0f};
65 auto c{0.0f};
66 if (state_00 >= 1.0e-10) {
67 b = std::log10(state_00) * 10.0f;
68 c = 1.0f;
69 }
70
71 if (b >= state.unk_10) {
72 const auto d{b >= state.unk_14
73 ? ((1.0f / params.compressor_ratio) - 1.0f) *
74 (b - params.threshold)
75 : (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C};
76 const auto e{d / 20.0f * 3.3219f};
77 const auto f{(e - std::trunc(e)) * 0.69315f};
78 c = std::pow(2.0f, f);
79 }
80
81 state_18 = params.unk_28;
82 auto tmp{c};
83 if ((state_04 - c) <= 0.08f) {
84 state_18 = params.unk_2C;
85 if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) {
86 tmp = state_04;
87 }
88 }
89
90 state_04 = tmp;
91 state_08 += (c - state_08) * state_18;
92
93 for (s16 channel = 0; channel < params.channel_count; channel++) {
94 output_buffers[channel][i] = static_cast<s32>(
95 static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20);
96 }
97 }
98
99 state.unk_00 = state_00;
100 state.unk_04 = state_04;
101 state.unk_08 = state_08;
102 state.unk_18 = state_18;
103 } else {
104 for (s16 channel = 0; channel < params.channel_count; channel++) {
105 if (params.inputs[channel] != params.outputs[channel]) {
106 std::memcpy((char*)output_buffers[channel].data(),
107 (char*)input_buffers[channel].data(),
108 output_buffers[channel].size_bytes());
109 }
110 }
111 }
112}
113
114void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
115 std::string& string) {
116 string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
117 for (s16 i = 0; i < parameter.channel_count; i++) {
118 string += fmt::format("{:02X}, ", inputs[i]);
119 }
120 string += "\n\toutputs: ";
121 for (s16 i = 0; i < parameter.channel_count; i++) {
122 string += fmt::format("{:02X}, ", outputs[i]);
123 }
124 string += "\n";
125}
126
127void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
128 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
129 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
130
131 for (s16 i = 0; i < parameter.channel_count; i++) {
132 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
133 processor.sample_count);
134 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
135 processor.sample_count);
136 }
137
138 auto state_{reinterpret_cast<CompressorInfo::State*>(state)};
139
140 if (effect_enabled) {
141 if (parameter.state == CompressorInfo::ParameterState::Updating) {
142 SetCompressorEffectParameter(parameter, *state_);
143 } else if (parameter.state == CompressorInfo::ParameterState::Initialized) {
144 InitializeCompressorEffect(parameter, *state_);
145 }
146 }
147
148 ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
149 processor.sample_count);
150}
151
152bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) {
153 return true;
154}
155
156} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/effect/compressor.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for limiting volume between a high and low threshold.
20 * Version 1.
21 */
22struct CompressorCommand : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs;
48 /// Output mix buffer offsets for each channel
49 std::array<s16, MaxChannels> outputs;
50 /// Input parameters
51 CompressorInfo::ParameterVersion2 parameter;
52 /// State, updated each call
53 CpuAddr state;
54 /// Game-supplied workbuffer (Unused)
55 CpuAddr workbuffer;
56 /// Is this effect enabled?
57 bool effect_enabled;
58};
59
60} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/delay.h"
6
7namespace AudioCore::AudioRenderer {
8/**
9 * Update the DelayInfo state according to the given parameters.
10 *
11 * @param params - Input parameters to update the state.
12 * @param state - State to be updated.
13 */
14static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params,
15 DelayInfo::State& state) {
16 auto channel_spread{params.channel_spread};
17 state.feedback_gain = params.feedback_gain * 0.97998046875f;
18 state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread);
19 if (params.channel_count == 4 || params.channel_count == 6) {
20 channel_spread >>= 1;
21 }
22 state.delay_feedback_cross_gain = channel_spread * state.feedback_gain;
23 state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f;
24 state.lowpass_gain = 1.0f - state.lowpass_feedback_gain;
25}
26
27/**
28 * Initialize a new DelayInfo state according to the given parameters.
29 *
30 * @param params - Input parameters to update the state.
31 * @param state - State to be updated.
32 * @param workbuffer - Game-supplied memory for the state. (Unused)
33 */
34static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
35 DelayInfo::State& state,
36 [[maybe_unused]] const CpuAddr workbuffer) {
37 state = {};
38
39 for (u32 channel = 0; channel < params.channel_count; channel++) {
40 Common::FixedPoint<32, 32> sample_count_max{0.064f};
41 sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max;
42
43 Common::FixedPoint<18, 14> delay_time{params.delay_time};
44 delay_time *= params.sample_rate / 1000;
45 Common::FixedPoint<32, 32> sample_count{delay_time};
46
47 if (sample_count > sample_count_max) {
48 sample_count = sample_count_max;
49 }
50
51 state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor();
52 state.delay_lines[channel].sample_count = sample_count.to_int_floor();
53 state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0);
54 if (state.delay_lines[channel].buffer.size() == 0) {
55 state.delay_lines[channel].buffer.push_back(0);
56 }
57 state.delay_lines[channel].buffer_pos = 0;
58 state.delay_lines[channel].decay_rate = 1.0f;
59 }
60
61 SetDelayEffectParameter(params, state);
62}
63
64/**
65 * Delay effect impl, according to the parameters and current state, on the input mix buffers,
66 * saving the results to the output mix buffers.
67 *
68 * @tparam NumChannels - Number of channels to process. 1-6.
69 * @param params - Input parameters to use.
70 * @param state - State to use, must be initialized (see InitializeDelayEffect).
71 * @param inputs - Input mix buffers to performan the delay on.
72 * @param outputs - Output mix buffers to receive the delayed samples.
73 * @param sample_count - Number of samples to process.
74 */
75template <size_t NumChannels>
76static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
77 std::vector<std::span<const s32>>& inputs,
78 std::vector<std::span<s32>>& outputs, const u32 sample_count) {
79 for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
80 std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{};
81 for (u32 channel = 0; channel < NumChannels; channel++) {
82 input_samples[channel] = inputs[channel][sample_index] * 64;
83 }
84
85 std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{};
86 for (u32 channel = 0; channel < NumChannels; channel++) {
87 delay_samples[channel] = state.delay_lines[channel].Read();
88 }
89
90 // clang-format off
91 std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{};
92 if constexpr (NumChannels == 1) {
93 matrix = {{
94 {state.feedback_gain},
95 }};
96 } else if constexpr (NumChannels == 2) {
97 matrix = {{
98 {state.delay_feedback_gain, state.delay_feedback_cross_gain},
99 {state.delay_feedback_cross_gain, state.delay_feedback_gain},
100 }};
101 } else if constexpr (NumChannels == 4) {
102 matrix = {{
103 {state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f},
104 {state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain},
105 {state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
106 {0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain},
107 }};
108 } else if constexpr (NumChannels == 6) {
109 matrix = {{
110 {state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f},
111 {0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain},
112 {state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f},
113 {0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f},
114 {state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
115 {0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain},
116 }};
117 }
118 // clang-format on
119
120 std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{};
121 for (u32 channel = 0; channel < NumChannels; channel++) {
122 Common::FixedPoint<50, 14> delay{};
123 for (u32 j = 0; j < NumChannels; j++) {
124 delay += delay_samples[j] * matrix[j][channel];
125 }
126 gained_samples[channel] = input_samples[channel] * params.in_gain + delay;
127 }
128
129 for (u32 channel = 0; channel < NumChannels; channel++) {
130 state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain +
131 state.lowpass_z[channel] * state.lowpass_feedback_gain;
132 state.delay_lines[channel].Write(state.lowpass_z[channel]);
133 }
134
135 for (u32 channel = 0; channel < NumChannels; channel++) {
136 outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain +
137 delay_samples[channel] * params.wet_gain)
138 .to_int_floor() /
139 64;
140 }
141 }
142}
143
144/**
145 * Apply a delay effect if enabled, according to the parameters and current state, on the input mix
146 * buffers, saving the results to the output mix buffers.
147 *
148 * @param params - Input parameters to use.
149 * @param state - State to use, must be initialized (see InitializeDelayEffect).
150 * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
151 * @param inputs - Input mix buffers to performan the delay on.
152 * @param outputs - Output mix buffers to receive the delayed samples.
153 * @param sample_count - Number of samples to process.
154 */
155static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
156 const bool enabled, std::vector<std::span<const s32>>& inputs,
157 std::vector<std::span<s32>>& outputs, const u32 sample_count) {
158
159 if (!IsChannelCountValid(params.channel_count)) {
160 LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count);
161 return;
162 }
163
164 if (enabled) {
165 switch (params.channel_count) {
166 case 1:
167 ApplyDelay<1>(params, state, inputs, outputs, sample_count);
168 break;
169 case 2:
170 ApplyDelay<2>(params, state, inputs, outputs, sample_count);
171 break;
172 case 4:
173 ApplyDelay<4>(params, state, inputs, outputs, sample_count);
174 break;
175 case 6:
176 ApplyDelay<6>(params, state, inputs, outputs, sample_count);
177 break;
178 default:
179 for (u32 channel = 0; channel < params.channel_count; channel++) {
180 if (inputs[channel].data() != outputs[channel].data()) {
181 std::memcpy(outputs[channel].data(), inputs[channel].data(),
182 sample_count * sizeof(s32));
183 }
184 }
185 break;
186 }
187 } else {
188 for (u32 channel = 0; channel < params.channel_count; channel++) {
189 if (inputs[channel].data() != outputs[channel].data()) {
190 std::memcpy(outputs[channel].data(), inputs[channel].data(),
191 sample_count * sizeof(s32));
192 }
193 }
194 }
195}
196
197void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
198 std::string& string) {
199 string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
200 for (u32 i = 0; i < MaxChannels; i++) {
201 string += fmt::format("{:02X}, ", inputs[i]);
202 }
203 string += "\n\toutputs: ";
204 for (u32 i = 0; i < MaxChannels; i++) {
205 string += fmt::format("{:02X}, ", outputs[i]);
206 }
207 string += "\n";
208}
209
210void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
211 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
212 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
213
214 for (s16 i = 0; i < parameter.channel_count; i++) {
215 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
216 processor.sample_count);
217 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
218 processor.sample_count);
219 }
220
221 auto state_{reinterpret_cast<DelayInfo::State*>(state)};
222
223 if (effect_enabled) {
224 if (parameter.state == DelayInfo::ParameterState::Updating) {
225 SetDelayEffectParameter(parameter, *state_);
226 } else if (parameter.state == DelayInfo::ParameterState::Initialized) {
227 InitializeDelayEffect(parameter, *state_, workbuffer);
228 }
229 }
230 ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
231 processor.sample_count);
232}
233
234bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) {
235 return true;
236}
237
238} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/effect/delay.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters
20 * and state, outputs receives the delayed samples.
21 */
22struct DelayCommand : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs;
48 /// Output mix buffer offsets for each channel
49 std::array<s16, MaxChannels> outputs;
50 /// Input parameters
51 DelayInfo::ParameterVersion1 parameter;
52 /// State, updated each call
53 CpuAddr state;
54 /// Game-supplied workbuffer (Unused)
55 CpuAddr workbuffer;
56 /// Is this effect enabled?
57 bool effect_enabled;
58};
59
60} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <numbers>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
8
9namespace AudioCore::AudioRenderer {
10
11constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{
12 5.0f,
13 6.0f,
14 13.0f,
15 14.0f,
16};
17constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MaxDelayLineTimes{
18 45.7042007446f,
19 82.7817001343f,
20 149.938293457f,
21 271.575805664f,
22};
23constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay0MaxDelayLineTimes{17.0f, 13.0f,
24 9.0f, 7.0f};
25constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay1MaxDelayLineTimes{19.0f, 11.0f,
26 10.0f, 6.0f};
27constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyTapTimes{
28 0.0171360000968f,
29 0.0591540001333f,
30 0.161733001471f,
31 0.390186011791f,
32 0.425262004137f,
33 0.455410987139f,
34 0.689737021923f,
35 0.74590998888f,
36 0.833844006062f,
37 0.859502017498f,
38 0.0f,
39 0.0750240013003f,
40 0.168788000941f,
41 0.299901008606f,
42 0.337442994118f,
43 0.371903002262f,
44 0.599011003971f,
45 0.716741025448f,
46 0.817858994007f,
47 0.85166400671f,
48};
49
50constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyGains{
51 0.67096f, 0.61027f, 1.0f, 0.3568f, 0.68361f, 0.65978f, 0.51939f,
52 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.3827f,
53 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
54
55/**
56 * Update the I3dl2ReverbInfo state according to the given parameters.
57 *
58 * @param params - Input parameters to update the state.
59 * @param state - State to be updated.
60 * @param reset - If enabled, the state buffers will be reset. Only set this on initialize.
61 */
62static void UpdateI3dl2ReverbEffectParameter(const I3dl2ReverbInfo::ParameterVersion1& params,
63 I3dl2ReverbInfo::State& state, const bool reset) {
64 const auto pow_10 = [](f32 val) -> f32 {
65 return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
66 };
67 const auto sin = [](f32 degrees) -> f32 {
68 return std::sin(degrees * std::numbers::pi_v<f32> / 180.0f);
69 };
70 const auto cos = [](f32 degrees) -> f32 {
71 return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
72 };
73
74 Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000.0f};
75
76 state.dry_gain = params.dry_gain;
77 Common::FixedPoint<50, 14> early_gain{
78 std::min(params.room_gain + params.reflection_gain, 5000.0f) / 2000.0f};
79 state.early_gain = pow_10(early_gain.to_float());
80 Common::FixedPoint<50, 14> late_gain{std::min(params.room_gain + params.reverb_gain, 5000.0f) /
81 2000.0f};
82 state.late_gain = pow_10(late_gain.to_float());
83
84 Common::FixedPoint<50, 14> hf_gain{pow_10(params.room_HF_gain / 2000.0f)};
85 if (hf_gain >= 1.0f) {
86 state.lowpass_1 = 0.0f;
87 state.lowpass_2 = 1.0f;
88 } else {
89 const auto reference_hf{(params.reference_HF * 256.0f) /
90 static_cast<f32>(params.sample_rate)};
91 const Common::FixedPoint<50, 14> a{1.0f - hf_gain.to_float()};
92 const Common::FixedPoint<50, 14> b{2.0f + (-cos(reference_hf) * (hf_gain * 2.0f))};
93 const Common::FixedPoint<50, 14> c{
94 std::sqrt(std::pow(b.to_float(), 2.0f) + (std::pow(a.to_float(), 2.0f) * -4.0f))};
95
96 state.lowpass_1 = std::min(((b - c) / (a * 2.0f)).to_float(), 0.99723f);
97 state.lowpass_2 = 1.0f - state.lowpass_1;
98 }
99
100 state.early_to_late_taps =
101 (((params.reflection_delay + params.late_reverb_delay_time) * 1000.0f) * delay).to_int();
102 state.last_reverb_echo = params.late_reverb_diffusion * 0.6f * 0.01f;
103
104 for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
105 auto curr_delay{
106 ((MinDelayLineTimes[i] + (params.late_reverb_density / 100.0f) *
107 (MaxDelayLineTimes[i] - MinDelayLineTimes[i])) *
108 delay)
109 .to_int()};
110 state.fdn_delay_lines[i].SetDelay(curr_delay);
111
112 const auto a{
113 (static_cast<f32>(state.fdn_delay_lines[i].delay + state.decay_delay_lines0[i].delay +
114 state.decay_delay_lines1[i].delay) *
115 -60.0f) /
116 (params.late_reverb_decay_time * static_cast<f32>(params.sample_rate))};
117 const auto b{a / params.late_reverb_HF_decay_ratio};
118 const auto c{
119 cos(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate)) /
120 sin(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate))};
121 const auto d{pow_10((b - a) / 40.0f)};
122 const auto e{pow_10((b + a) / 40.0f) * 0.7071f};
123
124 state.lowpass_coeff[i][0] = ((c * d + 1.0f) * e) / (c + d);
125 state.lowpass_coeff[i][1] = ((1.0f - (c * d)) * e) / (c + d);
126 state.lowpass_coeff[i][2] = (c - d) / (c + d);
127
128 state.decay_delay_lines0[i].wet_gain = state.last_reverb_echo;
129 state.decay_delay_lines1[i].wet_gain = state.last_reverb_echo * -0.9f;
130 }
131
132 if (reset) {
133 state.shelf_filter.fill(0.0f);
134 state.lowpass_0 = 0.0f;
135 for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
136 std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
137 std::ranges::fill(state.decay_delay_lines0[i].buffer, 0);
138 std::ranges::fill(state.decay_delay_lines1[i].buffer, 0);
139 }
140 std::ranges::fill(state.center_delay_line.buffer, 0);
141 std::ranges::fill(state.early_delay_line.buffer, 0);
142 }
143
144 const auto reflection_time{(params.late_reverb_delay_time * 0.9998f + 0.02f) * 1000.0f};
145 const auto reflection_delay{params.reflection_delay * 1000.0f};
146 for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayTaps; i++) {
147 auto length{((reflection_delay + reflection_time * EarlyTapTimes[i]) * delay).to_int()};
148 if (length >= state.early_delay_line.max_delay) {
149 length = state.early_delay_line.max_delay;
150 }
151 state.early_tap_steps[i] = length;
152 }
153}
154
155/**
156 * Initialize a new I3dl2ReverbInfo state according to the given parameters.
157 *
158 * @param params - Input parameters to update the state.
159 * @param state - State to be updated.
160 * @param workbuffer - Game-supplied memory for the state. (Unused)
161 */
162static void InitializeI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
163 I3dl2ReverbInfo::State& state, const CpuAddr workbuffer) {
164 state = {};
165 Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000};
166
167 for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
168 auto fdn_delay_time{(MaxDelayLineTimes[i] * delay).to_uint_floor()};
169 state.fdn_delay_lines[i].Initialize(fdn_delay_time);
170
171 auto decay0_delay_time{(Decay0MaxDelayLineTimes[i] * delay).to_uint_floor()};
172 state.decay_delay_lines0[i].Initialize(decay0_delay_time);
173
174 auto decay1_delay_time{(Decay1MaxDelayLineTimes[i] * delay).to_uint_floor()};
175 state.decay_delay_lines1[i].Initialize(decay1_delay_time);
176 }
177
178 const auto center_delay_time{(5 * delay).to_uint_floor()};
179 state.center_delay_line.Initialize(center_delay_time);
180
181 const auto early_delay_time{(400 * delay).to_uint_floor()};
182 state.early_delay_line.Initialize(early_delay_time);
183
184 UpdateI3dl2ReverbEffectParameter(params, state, true);
185}
186
187/**
188 * Pass-through the effect, copying input to output directly, with no reverb applied.
189 *
190 * @param inputs - Array of input mix buffers to copy.
191 * @param outputs - Array of output mix buffers to receive copy.
192 * @param channel_count - Number of channels in inputs and outputs.
193 * @param sample_count - Number of samples within each channel (unused).
194 */
195static void ApplyI3dl2ReverbEffectBypass(std::span<std::span<const s32>> inputs,
196 std::span<std::span<s32>> outputs, const u32 channel_count,
197 [[maybe_unused]] const u32 sample_count) {
198 for (u32 i = 0; i < channel_count; i++) {
199 if (inputs[i].data() != outputs[i].data()) {
200 std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
201 }
202 }
203}
204
205/**
206 * Tick the delay lines, reading and returning their current output, and writing a new decaying
207 * sample (mix).
208 *
209 * @param decay0 - The first decay line.
210 * @param decay1 - The second decay line.
211 * @param fdn - Feedback delay network.
212 * @param mix - The new calculated sample to be written and decayed.
213 * @return The next delayed and decayed sample.
214 */
215static Common::FixedPoint<50, 14> Axfx2AllPassTick(I3dl2ReverbInfo::I3dl2DelayLine& decay0,
216 I3dl2ReverbInfo::I3dl2DelayLine& decay1,
217 I3dl2ReverbInfo::I3dl2DelayLine& fdn,
218 const Common::FixedPoint<50, 14> mix) {
219 auto val{decay0.Read()};
220 auto mixed{mix - (val * decay0.wet_gain)};
221 auto out{decay0.Tick(mixed) + (mixed * decay0.wet_gain)};
222
223 val = decay1.Read();
224 mixed = out - (val * decay1.wet_gain);
225 out = decay1.Tick(mixed) + (mixed * decay1.wet_gain);
226
227 fdn.Tick(out);
228 return out;
229}
230
231/**
232 * Impl. Apply a I3DL2 reverb according to the current state, on the input mix buffers,
233 * saving the results to the output mix buffers.
234 *
235 * @tparam NumChannels - Number of channels to process. 1-6.
236 Inputs/outputs should have this many buffers.
237 * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
238 * @param inputs - Input mix buffers to perform the reverb on.
239 * @param outputs - Output mix buffers to receive the reverbed samples.
240 * @param sample_count - Number of samples to process.
241 */
242template <size_t NumChannels>
243static void ApplyI3dl2ReverbEffect(I3dl2ReverbInfo::State& state,
244 std::span<std::span<const s32>> inputs,
245 std::span<std::span<s32>> outputs, const u32 sample_count) {
246 constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
247 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
248 };
249 constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
250 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
251 };
252 constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
253 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3,
254 };
255 constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
256 2, 0, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5,
257 };
258
259 std::span<const u8> tap_indexes{};
260 if constexpr (NumChannels == 1) {
261 tap_indexes = OutTapIndexes1Ch;
262 } else if constexpr (NumChannels == 2) {
263 tap_indexes = OutTapIndexes2Ch;
264 } else if constexpr (NumChannels == 4) {
265 tap_indexes = OutTapIndexes4Ch;
266 } else if constexpr (NumChannels == 6) {
267 tap_indexes = OutTapIndexes6Ch;
268 }
269
270 for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
271 Common::FixedPoint<50, 14> early_to_late_tap{
272 state.early_delay_line.TapOut(state.early_to_late_taps)};
273 std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
274
275 for (u32 early_tap = 0; early_tap < I3dl2ReverbInfo::MaxDelayTaps; early_tap++) {
276 output_samples[tap_indexes[early_tap]] +=
277 state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
278 EarlyGains[early_tap];
279 if constexpr (NumChannels == 6) {
280 output_samples[static_cast<u32>(Channels::LFE)] +=
281 state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
282 EarlyGains[early_tap];
283 }
284 }
285
286 Common::FixedPoint<50, 14> current_sample{};
287 for (u32 channel = 0; channel < NumChannels; channel++) {
288 current_sample += inputs[channel][sample_index];
289 }
290
291 state.lowpass_0 =
292 (current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1).to_float();
293 state.early_delay_line.Tick(state.lowpass_0);
294
295 for (u32 channel = 0; channel < NumChannels; channel++) {
296 output_samples[channel] *= state.early_gain;
297 }
298
299 std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> filtered_samples{};
300 for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
301 filtered_samples[delay_line] =
302 state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][0] +
303 state.shelf_filter[delay_line];
304 state.shelf_filter[delay_line] =
305 (filtered_samples[delay_line] * state.lowpass_coeff[delay_line][2] +
306 state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][1])
307 .to_float();
308 }
309
310 const std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> mix_matrix{
311 filtered_samples[1] + filtered_samples[2] + early_to_late_tap * state.late_gain,
312 -filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
313 filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
314 filtered_samples[1] - filtered_samples[2] + early_to_late_tap * state.late_gain,
315 };
316
317 std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> allpass_samples{};
318 for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
319 allpass_samples[delay_line] = Axfx2AllPassTick(
320 state.decay_delay_lines0[delay_line], state.decay_delay_lines1[delay_line],
321 state.fdn_delay_lines[delay_line], mix_matrix[delay_line]);
322 }
323
324 if constexpr (NumChannels == 6) {
325 const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
326 allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
327 allpass_samples[3], allpass_samples[2], allpass_samples[3],
328 };
329
330 for (u32 channel = 0; channel < NumChannels; channel++) {
331 Common::FixedPoint<50, 14> allpass{};
332
333 if (channel == static_cast<u32>(Channels::Center)) {
334 allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
335 } else {
336 allpass = allpass_outputs[channel];
337 }
338
339 auto out_sample{output_samples[channel] + allpass +
340 state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
341
342 outputs[channel][sample_index] =
343 static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
344 }
345 } else {
346 for (u32 channel = 0; channel < NumChannels; channel++) {
347 auto out_sample{output_samples[channel] + allpass_samples[channel] +
348 state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
349 outputs[channel][sample_index] =
350 static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
351 }
352 }
353 }
354}
355
356/**
357 * Apply a I3DL2 reverb if enabled, according to the current state, on the input mix buffers,
358 * saving the results to the output mix buffers.
359 *
360 * @param params - Input parameters to use.
361 * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
362 * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
363 * @param inputs - Input mix buffers to performan the delay on.
364 * @param outputs - Output mix buffers to receive the delayed samples.
365 * @param sample_count - Number of samples to process.
366 */
367static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
368 I3dl2ReverbInfo::State& state, const bool enabled,
369 std::span<std::span<const s32>> inputs,
370 std::span<std::span<s32>> outputs, const u32 sample_count) {
371 if (enabled) {
372 switch (params.channel_count) {
373 case 0:
374 return;
375 case 1:
376 ApplyI3dl2ReverbEffect<1>(state, inputs, outputs, sample_count);
377 break;
378 case 2:
379 ApplyI3dl2ReverbEffect<2>(state, inputs, outputs, sample_count);
380 break;
381 case 4:
382 ApplyI3dl2ReverbEffect<4>(state, inputs, outputs, sample_count);
383 break;
384 case 6:
385 ApplyI3dl2ReverbEffect<6>(state, inputs, outputs, sample_count);
386 break;
387 default:
388 ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
389 break;
390 }
391 } else {
392 ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
393 }
394}
395
396void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
397 std::string& string) {
398 string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
399 for (u32 i = 0; i < parameter.channel_count; i++) {
400 string += fmt::format("{:02X}, ", inputs[i]);
401 }
402 string += "\n\toutputs: ";
403 for (u32 i = 0; i < parameter.channel_count; i++) {
404 string += fmt::format("{:02X}, ", outputs[i]);
405 }
406 string += "\n";
407}
408
409void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
410 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
411 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
412
413 for (u32 i = 0; i < parameter.channel_count; i++) {
414 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
415 processor.sample_count);
416 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
417 processor.sample_count);
418 }
419
420 auto state_{reinterpret_cast<I3dl2ReverbInfo::State*>(state)};
421
422 if (effect_enabled) {
423 if (parameter.state == I3dl2ReverbInfo::ParameterState::Updating) {
424 UpdateI3dl2ReverbEffectParameter(parameter, *state_, false);
425 } else if (parameter.state == I3dl2ReverbInfo::ParameterState::Initialized) {
426 InitializeI3dl2ReverbEffect(parameter, *state_, workbuffer);
427 }
428 }
429 ApplyI3dl2ReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
430 processor.sample_count);
431}
432
433bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
434 return true;
435}
436
437} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/effect/i3dl2.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to
20 * the I3DL2 spec, outputs receives the results.
21 */
22struct I3dl2ReverbCommand : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs;
48 /// Output mix buffer offsets for each channel
49 std::array<s16, MaxChannels> outputs;
50 /// Input parameters
51 I3dl2ReverbInfo::ParameterVersion1 parameter;
52 /// State, updated each call
53 CpuAddr state;
54 /// Game-supplied workbuffer (Unused)
55 CpuAddr workbuffer;
56 /// Is this effect enabled?
57 bool effect_enabled;
58};
59
60} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/light_limiter.h"
6
7namespace AudioCore::AudioRenderer {
8/**
9 * Update the LightLimiterInfo state according to the given parameters.
10 * A no-op.
11 *
12 * @param params - Input parameters to update the state.
13 * @param state - State to be updated.
14 */
15static void UpdateLightLimiterEffectParameter(const LightLimiterInfo::ParameterVersion2& params,
16 LightLimiterInfo::State& state) {}
17
18/**
19 * Initialize a new LightLimiterInfo state according to the given parameters.
20 *
21 * @param params - Input parameters to update the state.
22 * @param state - State to be updated.
23 * @param workbuffer - Game-supplied memory for the state. (Unused)
24 */
25static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
26 LightLimiterInfo::State& state, const CpuAddr workbuffer) {
27 state = {};
28 state.samples_average.fill(0.0f);
29 state.compression_gain.fill(1.0f);
30 state.look_ahead_sample_offsets.fill(0);
31 for (u32 i = 0; i < params.channel_count; i++) {
32 state.look_ahead_sample_buffers[i].resize(params.look_ahead_samples_max, 0.0f);
33 }
34}
35
36/**
37 * Apply a light limiter effect if enabled, according to the current state, on the input mix
38 * buffers, saving the results to the output mix buffers.
39 *
40 * @param params - Input parameters to use.
41 * @param state - State to use, must be initialized (see InitializeLightLimiterEffect).
42 * @param enabled - If enabled, limiter will be applied, otherwise input is copied to output.
43 * @param inputs - Input mix buffers to perform the limiter on.
44 * @param outputs - Output mix buffers to receive the limited samples.
45 * @param sample_count - Number of samples to process.
46 * @params statistics - Optional output statistics, only used with version 2.
47 */
48static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
49 LightLimiterInfo::State& state, const bool enabled,
50 std::vector<std::span<const s32>>& inputs,
51 std::vector<std::span<s32>>& outputs, const u32 sample_count,
52 LightLimiterInfo::StatisticsInternal* statistics) {
53 constexpr s64 min{std::numeric_limits<s32>::min()};
54 constexpr s64 max{std::numeric_limits<s32>::max()};
55
56 const auto recip_estimate = [](f64 a) -> f64 {
57 s32 q, s;
58 f64 r;
59 q = (s32)(a * 512.0); /* a in units of 1/512 rounded down */
60 r = 1.0 / (((f64)q + 0.5) / 512.0); /* reciprocal r */
61 s = (s32)(256.0 * r + 0.5); /* r in units of 1/256 rounded to nearest */
62 return ((f64)s / 256.0);
63 };
64
65 if (enabled) {
66 if (statistics && params.statistics_reset_required) {
67 for (u32 i = 0; i < params.channel_count; i++) {
68 statistics->channel_compression_gain_min[i] = 1.0f;
69 statistics->channel_max_sample[i] = 0;
70 }
71 }
72
73 for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
74 for (u32 channel = 0; channel < params.channel_count; channel++) {
75 auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) /
76 Common::FixedPoint<49, 15>::one) *
77 params.input_gain};
78 auto abs_sample{sample};
79 if (sample < 0.0f) {
80 abs_sample = -sample;
81 }
82 auto coeff{abs_sample > state.samples_average[channel] ? params.attack_coeff
83 : params.release_coeff};
84 state.samples_average[channel] +=
85 ((abs_sample - state.samples_average[channel]) * coeff).to_float();
86
87 // Reciprocal estimate
88 auto new_average_sample{Common::FixedPoint<49, 15>(
89 recip_estimate(state.samples_average[channel].to_double()))};
90 if (params.processing_mode != LightLimiterInfo::ProcessingMode::Mode1) {
91 // Two Newton-Raphson steps
92 auto temp{2.0 - (state.samples_average[channel] * new_average_sample)};
93 new_average_sample = 2.0 - (state.samples_average[channel] * temp);
94 }
95
96 auto above_threshold{state.samples_average[channel] > params.threshold};
97 auto attenuation{above_threshold ? params.threshold * new_average_sample : 1.0f};
98 coeff = attenuation < state.compression_gain[channel] ? params.attack_coeff
99 : params.release_coeff;
100 state.compression_gain[channel] +=
101 (attenuation - state.compression_gain[channel]) * coeff;
102
103 auto lookahead_sample{
104 state.look_ahead_sample_buffers[channel]
105 [state.look_ahead_sample_offsets[channel]]};
106
107 state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] =
108 sample;
109 state.look_ahead_sample_offsets[channel] =
110 (state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min;
111
112 outputs[channel][sample_index] = static_cast<s32>(
113 std::clamp((lookahead_sample * state.compression_gain[channel] *
114 params.output_gain * Common::FixedPoint<49, 15>::one)
115 .to_long(),
116 min, max));
117
118 if (statistics) {
119 statistics->channel_max_sample[channel] =
120 std::max(statistics->channel_max_sample[channel], abs_sample.to_float());
121 statistics->channel_compression_gain_min[channel] =
122 std::min(statistics->channel_compression_gain_min[channel],
123 state.compression_gain[channel].to_float());
124 }
125 }
126 }
127 } else {
128 for (u32 i = 0; i < params.channel_count; i++) {
129 if (params.inputs[i] != params.outputs[i]) {
130 std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
131 }
132 }
133 }
134}
135
136void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
137 std::string& string) {
138 string += fmt::format("LightLimiterVersion1Command\n\tinputs: ");
139 for (u32 i = 0; i < MaxChannels; i++) {
140 string += fmt::format("{:02X}, ", inputs[i]);
141 }
142 string += "\n\toutputs: ";
143 for (u32 i = 0; i < MaxChannels; i++) {
144 string += fmt::format("{:02X}, ", outputs[i]);
145 }
146 string += "\n";
147}
148
149void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
150 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
151 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
152
153 for (u32 i = 0; i < parameter.channel_count; i++) {
154 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
155 processor.sample_count);
156 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
157 processor.sample_count);
158 }
159
160 auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
161
162 if (effect_enabled) {
163 if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
164 UpdateLightLimiterEffectParameter(parameter, *state_);
165 } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
166 InitializeLightLimiterEffect(parameter, *state_, workbuffer);
167 }
168 }
169
170 LightLimiterInfo::StatisticsInternal* statistics{nullptr};
171 ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
172 processor.sample_count, statistics);
173}
174
175bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
176 return true;
177}
178
179void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
180 std::string& string) {
181 string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n");
182 for (u32 i = 0; i < MaxChannels; i++) {
183 string += fmt::format("{:02X}, ", inputs[i]);
184 }
185 string += "\n\toutputs: ";
186 for (u32 i = 0; i < MaxChannels; i++) {
187 string += fmt::format("{:02X}, ", outputs[i]);
188 }
189 string += "\n";
190}
191
192void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
193 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
194 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
195
196 for (u32 i = 0; i < parameter.channel_count; i++) {
197 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
198 processor.sample_count);
199 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
200 processor.sample_count);
201 }
202
203 auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
204
205 if (effect_enabled) {
206 if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
207 UpdateLightLimiterEffectParameter(parameter, *state_);
208 } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
209 InitializeLightLimiterEffect(parameter, *state_, workbuffer);
210 }
211 }
212
213 auto statistics{reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(result_state)};
214 ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
215 processor.sample_count, statistics);
216}
217
218bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
219 return true;
220}
221
222} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/effect/light_limiter.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for limiting volume between a high and low threshold.
20 * Version 1.
21 */
22struct LightLimiterVersion1Command : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs;
48 /// Output mix buffer offsets for each channel
49 std::array<s16, MaxChannels> outputs;
50 /// Input parameters
51 LightLimiterInfo::ParameterVersion2 parameter;
52 /// State, updated each call
53 CpuAddr state;
54 /// Game-supplied workbuffer (Unused)
55 CpuAddr workbuffer;
56 /// Is this effect enabled?
57 bool effect_enabled;
58};
59
60/**
61 * AudioRenderer command for limiting volume between a high and low threshold.
62 * Version 2 with output statistics.
63 */
64struct LightLimiterVersion2Command : ICommand {
65 /**
66 * Print this command's information to a string.
67 *
68 * @param processor - The CommandListProcessor processing this command.
69 * @param string - The string to print into.
70 */
71 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
72
73 /**
74 * Process this command.
75 *
76 * @param processor - The CommandListProcessor processing this command.
77 */
78 void Process(const ADSP::CommandListProcessor& processor) override;
79
80 /**
81 * Verify this command's data is valid.
82 *
83 * @param processor - The CommandListProcessor processing this command.
84 */
85 bool Verify(const ADSP::CommandListProcessor& processor) override;
86
87 /// Input mix buffer offsets for each channel
88 std::array<s16, MaxChannels> inputs;
89 /// Output mix buffer offsets for each channel
90 std::array<s16, MaxChannels> outputs;
91 /// Input parameters
92 LightLimiterInfo::ParameterVersion2 parameter;
93 /// State, updated each call
94 CpuAddr state;
95 /// Game-supplied workbuffer (Unused)
96 CpuAddr workbuffer;
97 /// Optional statistics, sent back to the sysmodule
98 CpuAddr result_state;
99 /// Is this effect enabled?
100 bool effect_enabled;
101};
102
103} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/biquad_filter.h"
6#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
7
8namespace AudioCore::AudioRenderer {
9
10void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
11 std::string& string) {
12 string += fmt::format(
13 "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n",
14 input, output, needs_init[0], needs_init[1]);
15}
16
17void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
18 if (filter_tap_count > MaxBiquadFilters) {
19 LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count);
20 filter_tap_count = MaxBiquadFilters;
21 }
22
23 auto input_buffer{
24 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
25 auto output_buffer{
26 processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
27
28 // TODO: Fix this, currently just applies the filter to the input twice,
29 // and doesn't chain the biquads together at all.
30 for (u32 i = 0; i < filter_tap_count; i++) {
31 auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(states[i])};
32 if (needs_init[i]) {
33 std::memset(state, 0, sizeof(VoiceState::BiquadFilterState));
34 }
35
36 ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state,
37 processor.sample_count);
38 }
39}
40
41bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
42 return true;
43}
44
45} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/voice/voice_info.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for applying multiple biquads at once.
20 */
21struct MultiTapBiquadFilterCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Input mix buffer index
46 s16 input;
47 /// Output mix buffer index
48 s16 output;
49 /// Biquad parameters
50 std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads;
51 /// Biquad states, updated each call
52 std::array<CpuAddr, MaxBiquadFilters> states;
53 /// If each biquad needs initialisation
54 std::array<bool, MaxBiquadFilters> needs_init;
55 /// Number of active biquads
56 u8 filter_tap_count;
57};
58
59} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <numbers>
5#include <ranges>
6
7#include "audio_core/renderer/adsp/command_list_processor.h"
8#include "audio_core/renderer/command/effect/reverb.h"
9
10namespace AudioCore::AudioRenderer {
11
12constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = {
13 53.9532470703125f,
14 79.19256591796875f,
15 116.23876953125f,
16 170.61529541015625f,
17};
18
19constexpr std::array<f32, ReverbInfo::MaxDelayLines> DecayMaxDelayLineTimes = {
20 7.0f,
21 9.0f,
22 13.0f,
23 17.0f,
24};
25
26constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps + 1>, ReverbInfo::NumEarlyModes>
27 EarlyDelayTimes = {
28 {{0.000000f, 3.500000f, 2.799988f, 3.899963f, 2.699951f, 13.399963f, 7.899963f, 8.399963f,
29 9.899963f, 12.000000f, 12.500000f},
30 {0.000000f, 11.799988f, 5.500000f, 11.199951f, 10.399963f, 38.099976f, 22.199951f,
31 29.599976f, 21.199951f, 24.799988f, 40.000000f},
32 {0.000000f, 41.500000f, 20.500000f, 41.299988f, 0.000000f, 29.500000f, 33.799988f,
33 45.199951f, 46.799988f, 0.000000f, 50.000000f},
34 {33.099976f, 43.299988f, 22.799988f, 37.899963f, 14.899963f, 35.299988f, 17.899963f,
35 34.199951f, 0.000000f, 43.299988f, 50.000000f},
36 {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
37 0.000000f, 0.000000f, 0.000000f}},
38};
39
40constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps>, ReverbInfo::NumEarlyModes>
41 EarlyDelayGains = {{
42 {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f,
43 0.679993f, 0.679993f},
44 {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.679993f, 0.679993f,
45 0.679993f, 0.679993f},
46 {0.500000f, 0.699951f, 0.699951f, 0.679993f, 0.500000f, 0.679993f, 0.679993f, 0.699951f,
47 0.679993f, 0.000000f},
48 {0.929993f, 0.919983f, 0.869995f, 0.859985f, 0.939941f, 0.809998f, 0.799988f, 0.769958f,
49 0.759949f, 0.649963f},
50 {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
51 0.000000f, 0.000000f},
52 }};
53
54constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
55 FdnDelayTimes = {{
56 {53.953247f, 79.192566f, 116.238770f, 130.615295f},
57 {53.953247f, 79.192566f, 116.238770f, 170.615295f},
58 {5.000000f, 10.000000f, 5.000000f, 10.000000f},
59 {47.029968f, 71.000000f, 103.000000f, 170.000000f},
60 {53.953247f, 79.192566f, 116.238770f, 170.615295f},
61 }};
62
63constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
64 DecayDelayTimes = {{
65 {7.000000f, 9.000000f, 13.000000f, 17.000000f},
66 {7.000000f, 9.000000f, 13.000000f, 17.000000f},
67 {1.000000f, 1.000000f, 1.000000f, 1.000000f},
68 {7.000000f, 7.000000f, 13.000000f, 9.000000f},
69 {7.000000f, 9.000000f, 13.000000f, 17.000000f},
70 }};
71
72/**
73 * Update the ReverbInfo state according to the given parameters.
74 *
75 * @param params - Input parameters to update the state.
76 * @param state - State to be updated.
77 */
78static void UpdateReverbEffectParameter(const ReverbInfo::ParameterVersion2& params,
79 ReverbInfo::State& state) {
80 const auto pow_10 = [](f32 val) -> f32 {
81 return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
82 };
83 const auto cos = [](f32 degrees) -> f32 {
84 return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
85 };
86
87 static bool unk_initialized{false};
88 static Common::FixedPoint<50, 14> unk_value{};
89
90 const auto sample_rate{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
91 const auto pre_delay_time{Common::FixedPoint<50, 14>::from_base(params.pre_delay)};
92
93 for (u32 i = 0; i < ReverbInfo::MaxDelayTaps; i++) {
94 auto early_delay{
95 ((pre_delay_time + EarlyDelayTimes[params.early_mode][i]) * sample_rate).to_int()};
96 early_delay = std::min(early_delay, state.pre_delay_line.sample_count_max);
97 state.early_delay_times[i] = early_delay + 1;
98 state.early_gains[i] = Common::FixedPoint<50, 14>::from_base(params.early_gain) *
99 EarlyDelayGains[params.early_mode][i];
100 }
101
102 if (params.channel_count == 2) {
103 state.early_gains[4] * 0.5f;
104 state.early_gains[5] * 0.5f;
105 }
106
107 auto pre_time{
108 ((pre_delay_time + EarlyDelayTimes[params.early_mode][10]) * sample_rate).to_int()};
109 state.pre_delay_time = std::min(pre_time, state.pre_delay_line.sample_count_max);
110
111 if (!unk_initialized) {
112 unk_value = cos((1280.0f / sample_rate).to_float());
113 unk_initialized = true;
114 }
115
116 for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
117 const auto fdn_delay{(FdnDelayTimes[params.late_mode][i] * sample_rate).to_int()};
118 state.fdn_delay_lines[i].sample_count =
119 std::min(fdn_delay, state.fdn_delay_lines[i].sample_count_max);
120 state.fdn_delay_lines[i].buffer_end =
121 &state.fdn_delay_lines[i].buffer[state.fdn_delay_lines[i].sample_count - 1];
122
123 const auto decay_delay{(DecayDelayTimes[params.late_mode][i] * sample_rate).to_int()};
124 state.decay_delay_lines[i].sample_count =
125 std::min(decay_delay, state.decay_delay_lines[i].sample_count_max);
126 state.decay_delay_lines[i].buffer_end =
127 &state.decay_delay_lines[i].buffer[state.decay_delay_lines[i].sample_count - 1];
128
129 state.decay_delay_lines[i].decay =
130 0.5999755859375f * (1.0f - Common::FixedPoint<50, 14>::from_base(params.colouration));
131
132 auto a{(Common::FixedPoint<50, 14>(state.fdn_delay_lines[i].sample_count_max) +
133 state.decay_delay_lines[i].sample_count_max) *
134 -3};
135 auto b{a / (Common::FixedPoint<50, 14>::from_base(params.decay_time) * sample_rate)};
136 Common::FixedPoint<50, 14> c{0.0f};
137 Common::FixedPoint<50, 14> d{0.0f};
138 auto hf_decay_ratio{Common::FixedPoint<50, 14>::from_base(params.high_freq_decay_ratio)};
139
140 if (hf_decay_ratio > 0.99493408203125f) {
141 c = 0.0f;
142 d = 1.0f;
143 } else {
144 const auto e{
145 pow_10(((((1.0f / hf_decay_ratio) - 1.0f) * 2) / 100 * (b / 10)).to_float())};
146 const auto f{1.0f - e};
147 const auto g{2.0f - (unk_value * e * 2)};
148 const auto h{std::sqrt(std::pow(g.to_float(), 2.0f) - (std::pow(f, 2.0f) * 4))};
149
150 c = (g - h) / (f * 2.0f);
151 d = 1.0f - c;
152 }
153
154 state.hf_decay_prev_gain[i] = c;
155 state.hf_decay_gain[i] = pow_10((b / 1000).to_float()) * d * 0.70709228515625f;
156 state.prev_feedback_output[i] = 0;
157 }
158}
159
160/**
161 * Initialize a new ReverbInfo state according to the given parameters.
162 *
163 * @param params - Input parameters to update the state.
164 * @param state - State to be updated.
165 * @param workbuffer - Game-supplied memory for the state. (Unused)
166 * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb begins.
167 */
168static void InitializeReverbEffect(const ReverbInfo::ParameterVersion2& params,
169 ReverbInfo::State& state, const CpuAddr workbuffer,
170 const bool long_size_pre_delay_supported) {
171 state = {};
172
173 auto delay{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
174
175 for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
176 auto fdn_delay_time{(FdnMaxDelayLineTimes[i] * delay).to_uint_floor()};
177 state.fdn_delay_lines[i].Initialize(fdn_delay_time, 1.0f);
178
179 auto decay_delay_time{(DecayMaxDelayLineTimes[i] * delay).to_uint_floor()};
180 state.decay_delay_lines[i].Initialize(decay_delay_time, 0.0f);
181 }
182
183 const auto pre_delay{long_size_pre_delay_supported ? 350.0f : 150.0f};
184 const auto pre_delay_line{(pre_delay * delay).to_uint_floor()};
185 state.pre_delay_line.Initialize(pre_delay_line, 1.0f);
186
187 const auto center_delay_time{(5 * delay).to_uint_floor()};
188 state.center_delay_line.Initialize(center_delay_time, 1.0f);
189
190 UpdateReverbEffectParameter(params, state);
191
192 for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
193 std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
194 std::ranges::fill(state.decay_delay_lines[i].buffer, 0);
195 }
196 std::ranges::fill(state.center_delay_line.buffer, 0);
197 std::ranges::fill(state.pre_delay_line.buffer, 0);
198}
199
200/**
201 * Pass-through the effect, copying input to output directly, with no reverb applied.
202 *
203 * @param inputs - Array of input mix buffers to copy.
204 * @param outputs - Array of output mix buffers to receive copy.
205 * @param channel_count - Number of channels in inputs and outputs.
206 * @param sample_count - Number of samples within each channel.
207 */
208static void ApplyReverbEffectBypass(std::span<std::span<const s32>> inputs,
209 std::span<std::span<s32>> outputs, const u32 channel_count,
210 const u32 sample_count) {
211 for (u32 i = 0; i < channel_count; i++) {
212 if (inputs[i].data() != outputs[i].data()) {
213 std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
214 }
215 }
216}
217
218/**
219 * Tick the delay lines, reading and returning their current output, and writing a new decaying
220 * sample (mix).
221 *
222 * @param decay - The decay line.
223 * @param fdn - Feedback delay network.
224 * @param mix - The new calculated sample to be written and decayed.
225 * @return The next delayed and decayed sample.
226 */
227static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine& decay,
228 ReverbInfo::ReverbDelayLine& fdn,
229 const Common::FixedPoint<50, 14> mix) {
230 const auto val{decay.Read()};
231 const auto mixed{mix - (val * decay.decay)};
232 const auto out{decay.Tick(mixed) + (mixed * decay.decay)};
233
234 fdn.Tick(out);
235 return out;
236}
237
238/**
239 * Impl. Apply a Reverb according to the current state, on the input mix buffers,
240 * saving the results to the output mix buffers.
241 *
242 * @tparam NumChannels - Number of channels to process. 1-6.
243 Inputs/outputs should have this many buffers.
244 * @param params - Input parameters to update the state.
245 * @param state - State to use, must be initialized (see InitializeReverbEffect).
246 * @param inputs - Input mix buffers to perform the reverb on.
247 * @param outputs - Output mix buffers to receive the reverbed samples.
248 * @param sample_count - Number of samples to process.
249 */
250template <size_t NumChannels>
251static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
252 std::vector<std::span<const s32>>& inputs,
253 std::vector<std::span<s32>>& outputs, const u32 sample_count) {
254 constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
255 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
256 };
257 constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
258 0, 0, 1, 1, 0, 1, 0, 0, 1, 1,
259 };
260 constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
261 0, 0, 1, 1, 0, 1, 2, 2, 3, 3,
262 };
263 constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
264 0, 0, 1, 1, 2, 2, 4, 4, 5, 5,
265 };
266
267 std::span<const u8> tap_indexes{};
268 if constexpr (NumChannels == 1) {
269 tap_indexes = OutTapIndexes1Ch;
270 } else if constexpr (NumChannels == 2) {
271 tap_indexes = OutTapIndexes2Ch;
272 } else if constexpr (NumChannels == 4) {
273 tap_indexes = OutTapIndexes4Ch;
274 } else if constexpr (NumChannels == 6) {
275 tap_indexes = OutTapIndexes6Ch;
276 }
277
278 for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
279 std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
280
281 for (u32 early_tap = 0; early_tap < ReverbInfo::MaxDelayTaps; early_tap++) {
282 const auto sample{state.pre_delay_line.TapOut(state.early_delay_times[early_tap]) *
283 state.early_gains[early_tap]};
284 output_samples[tap_indexes[early_tap]] += sample;
285 if constexpr (NumChannels == 6) {
286 output_samples[static_cast<u32>(Channels::LFE)] += sample;
287 }
288 }
289
290 if constexpr (NumChannels == 6) {
291 output_samples[static_cast<u32>(Channels::LFE)] *= 0.2f;
292 }
293
294 Common::FixedPoint<50, 14> input_sample{};
295 for (u32 channel = 0; channel < NumChannels; channel++) {
296 input_sample += inputs[channel][sample_index];
297 }
298
299 input_sample *= 64;
300 input_sample *= Common::FixedPoint<50, 14>::from_base(params.base_gain);
301 state.pre_delay_line.Write(input_sample);
302
303 for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
304 state.prev_feedback_output[i] =
305 state.prev_feedback_output[i] * state.hf_decay_prev_gain[i] +
306 state.fdn_delay_lines[i].Read() * state.hf_decay_gain[i];
307 }
308
309 Common::FixedPoint<50, 14> pre_delay_sample{
310 state.pre_delay_line.Read() * Common::FixedPoint<50, 14>::from_base(params.late_gain)};
311
312 std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> mix_matrix{
313 state.prev_feedback_output[2] + state.prev_feedback_output[1] + pre_delay_sample,
314 -state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
315 state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
316 state.prev_feedback_output[1] - state.prev_feedback_output[2] + pre_delay_sample,
317 };
318
319 std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> allpass_samples{};
320 for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
321 allpass_samples[i] = Axfx2AllPassTick(state.decay_delay_lines[i],
322 state.fdn_delay_lines[i], mix_matrix[i]);
323 }
324
325 const auto dry_gain{Common::FixedPoint<50, 14>::from_base(params.dry_gain)};
326 const auto wet_gain{Common::FixedPoint<50, 14>::from_base(params.wet_gain)};
327
328 if constexpr (NumChannels == 6) {
329 const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
330 allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
331 allpass_samples[3], allpass_samples[2], allpass_samples[3],
332 };
333
334 for (u32 channel = 0; channel < NumChannels; channel++) {
335 auto in_sample{inputs[channel][sample_index] * dry_gain};
336
337 Common::FixedPoint<50, 14> allpass{};
338 if (channel == static_cast<u32>(Channels::Center)) {
339 allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
340 } else {
341 allpass = allpass_outputs[channel];
342 }
343
344 auto out_sample{((output_samples[channel] + allpass) * wet_gain) / 64};
345 outputs[channel][sample_index] = (in_sample + out_sample).to_int();
346 }
347 } else {
348 for (u32 channel = 0; channel < NumChannels; channel++) {
349 auto in_sample{inputs[channel][sample_index] * dry_gain};
350 auto out_sample{((output_samples[channel] + allpass_samples[channel]) * wet_gain) /
351 64};
352 outputs[channel][sample_index] = (in_sample + out_sample).to_int();
353 }
354 }
355 }
356}
357
358/**
359 * Apply a Reverb if enabled, according to the current state, on the input mix buffers,
360 * saving the results to the output mix buffers.
361 *
362 * @param params - Input parameters to use.
363 * @param state - State to use, must be initialized (see InitializeReverbEffect).
364 * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
365 * @param inputs - Input mix buffers to performan the reverb on.
366 * @param outputs - Output mix buffers to receive the reverbed samples.
367 * @param sample_count - Number of samples to process.
368 */
369static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
370 const bool enabled, std::vector<std::span<const s32>>& inputs,
371 std::vector<std::span<s32>>& outputs, const u32 sample_count) {
372 if (enabled) {
373 switch (params.channel_count) {
374 case 0:
375 return;
376 case 1:
377 ApplyReverbEffect<1>(params, state, inputs, outputs, sample_count);
378 break;
379 case 2:
380 ApplyReverbEffect<2>(params, state, inputs, outputs, sample_count);
381 break;
382 case 4:
383 ApplyReverbEffect<4>(params, state, inputs, outputs, sample_count);
384 break;
385 case 6:
386 ApplyReverbEffect<6>(params, state, inputs, outputs, sample_count);
387 break;
388 default:
389 ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
390 break;
391 }
392 } else {
393 ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
394 }
395}
396
397void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
398 std::string& string) {
399 string += fmt::format(
400 "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled,
401 long_size_pre_delay_supported);
402 for (u32 i = 0; i < MaxChannels; i++) {
403 string += fmt::format("{:02X}, ", inputs[i]);
404 }
405 string += "\n\toutputs: ";
406 for (u32 i = 0; i < MaxChannels; i++) {
407 string += fmt::format("{:02X}, ", outputs[i]);
408 }
409 string += "\n";
410}
411
412void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
413 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
414 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
415
416 for (u32 i = 0; i < parameter.channel_count; i++) {
417 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
418 processor.sample_count);
419 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
420 processor.sample_count);
421 }
422
423 auto state_{reinterpret_cast<ReverbInfo::State*>(state)};
424
425 if (effect_enabled) {
426 if (parameter.state == ReverbInfo::ParameterState::Updating) {
427 UpdateReverbEffectParameter(parameter, *state_);
428 } else if (parameter.state == ReverbInfo::ParameterState::Initialized) {
429 InitializeReverbEffect(parameter, *state_, workbuffer, long_size_pre_delay_supported);
430 }
431 }
432 ApplyReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
433 processor.sample_count);
434}
435
436bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
437 return true;
438}
439
440} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/effect/reverb.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives
20 * the results.
21 */
22struct ReverbCommand : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs;
48 /// Output mix buffer offsets for each channel
49 std::array<s16, MaxChannels> outputs;
50 /// Input parameters
51 ReverbInfo::ParameterVersion2 parameter;
52 /// State, updated each call
53 CpuAddr state;
54 /// Game-supplied workbuffer (Unused)
55 CpuAddr workbuffer;
56 /// Is this effect enabled?
57 bool effect_enabled;
58 /// Is a longer pre-delay time supported?
59 bool long_size_pre_delay_supported;
60};
61
62} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/common/common.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10namespace ADSP {
11class CommandListProcessor;
12}
13
14enum class CommandId : u8 {
15 /* 0x00 */ Invalid,
16 /* 0x01 */ DataSourcePcmInt16Version1,
17 /* 0x02 */ DataSourcePcmInt16Version2,
18 /* 0x03 */ DataSourcePcmFloatVersion1,
19 /* 0x04 */ DataSourcePcmFloatVersion2,
20 /* 0x05 */ DataSourceAdpcmVersion1,
21 /* 0x06 */ DataSourceAdpcmVersion2,
22 /* 0x07 */ Volume,
23 /* 0x08 */ VolumeRamp,
24 /* 0x09 */ BiquadFilter,
25 /* 0x0A */ Mix,
26 /* 0x0B */ MixRamp,
27 /* 0x0C */ MixRampGrouped,
28 /* 0x0D */ DepopPrepare,
29 /* 0x0E */ DepopForMixBuffers,
30 /* 0x0F */ Delay,
31 /* 0x10 */ Upsample,
32 /* 0x11 */ DownMix6chTo2ch,
33 /* 0x12 */ Aux,
34 /* 0x13 */ DeviceSink,
35 /* 0x14 */ CircularBufferSink,
36 /* 0x15 */ Reverb,
37 /* 0x16 */ I3dl2Reverb,
38 /* 0x17 */ Performance,
39 /* 0x18 */ ClearMixBuffer,
40 /* 0x19 */ CopyMixBuffer,
41 /* 0x1A */ LightLimiterVersion1,
42 /* 0x1B */ LightLimiterVersion2,
43 /* 0x1C */ MultiTapBiquadFilter,
44 /* 0x1D */ Capture,
45 /* 0x1E */ Compressor,
46};
47
48constexpr u32 CommandMagic{0xCAFEBABE};
49
50/**
51 * A command, generated by the host, and processed by the ADSP's AudioRenderer.
52 */
53struct ICommand {
54 virtual ~ICommand() = default;
55
56 /**
57 * Print this command's information to a string.
58 *
59 * @param processor - The CommandListProcessor processing this command.
60 * @param string - The string to print into.
61 */
62 virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0;
63
64 /**
65 * Process this command.
66 *
67 * @param processor - The CommandListProcessor processing this command.
68 */
69 virtual void Process(const ADSP::CommandListProcessor& processor) = 0;
70
71 /**
72 * Verify this command's data is valid.
73 *
74 * @param processor - The CommandListProcessor processing this command.
75 * @return True if the command is valid, otherwise false.
76 */
77 virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0;
78
79 /// Command magic 0xCAFEBABE
80 u32 magic{};
81 /// Command enabled
82 bool enabled{};
83 /// Type of this command (see CommandId)
84 CommandId type{};
85 /// Size of this command
86 s16 size{};
87 /// Estimated processing time for this command
88 u32 estimated_process_time{};
89 /// Node id of the voice or mix this command was generated from
90 u32 node_id{};
91};
92
93} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <string>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/mix/clear_mix.h"
8
9namespace AudioCore::AudioRenderer {
10
11void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
12 std::string& string) {
13 string += fmt::format("ClearMixBufferCommand\n");
14}
15
16void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
17 memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes());
18}
19
20bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
21 return true;
22}
23
24} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for a clearing the mix buffers.
18 * Used at the start of each command list.
19 */
20struct ClearMixBufferCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43};
44
45} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/copy_mix.h"
6
7namespace AudioCore::AudioRenderer {
8
9void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
10 std::string& string) {
11 string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index,
12 output_index);
13}
14
15void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
16 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
17 processor.sample_count)};
18 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
19 processor.sample_count)};
20 std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32));
21}
22
23bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
24 return true;
25}
26
27} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for a copying a mix buffer from input to output.
18 */
19struct CopyMixBufferCommand : ICommand {
20 /**
21 * Print this command's information to a string.
22 *
23 * @param processor - The CommandListProcessor processing this command.
24 * @param string - The string to print into.
25 */
26 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
27
28 /**
29 * Process this command.
30 *
31 * @param processor - The CommandListProcessor processing this command.
32 */
33 void Process(const ADSP::CommandListProcessor& processor) override;
34
35 /**
36 * Verify this command's data is valid.
37 *
38 * @param processor - The CommandListProcessor processing this command.
39 * @return True if the command is valid, otherwise false.
40 */
41 bool Verify(const ADSP::CommandListProcessor& processor) override;
42
43 /// Input mix buffer index
44 s16 input_index;
45 /// Output mix buffer index
46 s16 output_index;
47};
48
49} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/common/common.h"
5#include "audio_core/renderer/adsp/command_list_processor.h"
6#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
7
8namespace AudioCore::AudioRenderer {
9/**
10 * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time
11 * according to decay.
12 *
13 * @param output - Output buffer to be depopped.
14 * @param depop_sample - Depopped sample to apply to output samples.
15 * @param decay_ - Amount to decay the depopped sample for every output sample.
16 * @param sample_count - Samples to process.
17 * @return Final decayed depop sample.
18 */
19static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample,
20 Common::FixedPoint<49, 15>& decay_, const u32 sample_count) {
21 auto sample{std::abs(depop_sample)};
22 auto decay{decay_.to_raw()};
23
24 if (depop_sample <= 0) {
25 for (u32 i = 0; i < sample_count; i++) {
26 sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
27 output[i] -= sample;
28 }
29 return -sample;
30 } else {
31 for (u32 i = 0; i < sample_count; i++) {
32 sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
33 output[i] += sample;
34 }
35 return sample;
36 }
37}
38
39void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
40 std::string& string) {
41 string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input,
42 count, decay.to_float());
43}
44
45void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) {
46 auto end_index{std::min(processor.buffer_count, input + count)};
47 std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index};
48
49 for (u32 index = input; index < end_index; index++) {
50 const auto depop_sample{depop_buff[index]};
51 if (depop_sample != 0) {
52 auto input_buffer{processor.mix_buffers.subspan(index * processor.sample_count,
53 processor.sample_count)};
54 depop_buff[index] =
55 ApplyDepopMix(input_buffer, depop_sample, decay, processor.sample_count);
56 }
57 }
58}
59
60bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) {
61 return true;
62}
63
64} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10#include "common/fixed_point.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command for depopping a mix buffer.
19 * Adds a cumulation of previous samples to the current mix buffer with a decay.
20 */
21struct DepopForMixBuffersCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Starting input mix buffer index
46 u32 input;
47 /// Number of mix buffers to depop
48 u32 count;
49 /// Amount to decay the depop sample for each new sample
50 Common::FixedPoint<49, 15> decay;
51 /// Address of the depop buffer, holding the last sample for every mix buffer
52 CpuAddr depop_buffer;
53};
54
55} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/depop_prepare.h"
6#include "audio_core/renderer/voice/voice_state.h"
7#include "common/fixed_point.h"
8
9namespace AudioCore::AudioRenderer {
10
11void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
12 std::string& string) {
13 string += fmt::format("DepopPrepareCommand\n\tinputs: ");
14 for (u32 i = 0; i < buffer_count; i++) {
15 string += fmt::format("{:02X}, ", inputs[i]);
16 }
17 string += "\n";
18}
19
20void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) {
21 auto samples{reinterpret_cast<s32*>(previous_samples)};
22 auto buffer{std::span(reinterpret_cast<s32*>(depop_buffer), buffer_count)};
23
24 for (u32 i = 0; i < buffer_count; i++) {
25 if (samples[i]) {
26 buffer[inputs[i]] += samples[i];
27 samples[i] = 0;
28 }
29 }
30}
31
32bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) {
33 return true;
34}
35
36} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for preparing depop.
18 * Adds the previusly output last samples to the depop buffer.
19 */
20struct DepopPrepareCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Depop buffer offset for each mix buffer
45 std::array<s16, MaxMixBuffers> inputs;
46 /// Pointer to the previous mix buffer samples
47 CpuAddr previous_samples;
48 /// Number of mix buffers to use
49 u32 buffer_count;
50 /// Pointer to the current depop values
51 CpuAddr depop_buffer;
52};
53
54} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <limits>
6#include <span>
7
8#include "audio_core/renderer/adsp/command_list_processor.h"
9#include "audio_core/renderer/command/mix/mix.h"
10#include "common/fixed_point.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Mix input mix buffer into output mix buffer, with volume applied to the input.
15 *
16 * @tparam Q - Number of bits for fixed point operations.
17 * @param output - Output mix buffer.
18 * @param input - Input mix buffer.
19 * @param volume - Volume applied to the input.
20 * @param sample_count - Number of samples to process.
21 */
22template <size_t Q>
23static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f32 volume_,
24 const u32 sample_count) {
25 const Common::FixedPoint<64 - Q, Q> volume{volume_};
26 for (u32 i = 0; i < sample_count; i++) {
27 output[i] = (output[i] + input[i] * volume).to_int();
28 }
29}
30
31void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
32 std::string& string) {
33 string += fmt::format("MixCommand");
34 string += fmt::format("\n\tinput {:02X}", input_index);
35 string += fmt::format("\n\toutput {:02X}", output_index);
36 string += fmt::format("\n\tvolume {:.8f}", volume);
37 string += "\n";
38}
39
40void MixCommand::Process(const ADSP::CommandListProcessor& processor) {
41 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
42 processor.sample_count)};
43 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
44 processor.sample_count)};
45
46 // If volume is 0, nothing will be added to the output, so just skip.
47 if (volume == 0.0f) {
48 return;
49 }
50
51 switch (precision) {
52 case 15:
53 ApplyMix<15>(output, input, volume, processor.sample_count);
54 break;
55
56 case 23:
57 ApplyMix<23>(output, input, volume, processor.sample_count);
58 break;
59
60 default:
61 LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
62 break;
63 }
64}
65
66bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) {
67 return true;
68}
69
70} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
18 * applied to the input.
19 */
20struct MixCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Fixed point precision
45 u8 precision;
46 /// Input mix buffer index
47 s16 input_index;
48 /// Output mix buffer index
49 s16 output_index;
50 /// Mix volume applied to the input
51 f32 volume;
52};
53
54} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/mix_ramp.h"
6#include "common/fixed_point.h"
7#include "common/logging/log.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Mix input mix buffer into output mix buffer, with volume applied to the input.
12 *
13 * @tparam Q - Number of bits for fixed point operations.
14 * @param output - Output mix buffer.
15 * @param input - Input mix buffer.
16 * @param volume - Volume applied to the input.
17 * @param ramp - Ramp applied to volume every sample.
18 * @param sample_count - Number of samples to process.
19 * @return The final gained input sample, used for depopping.
20 */
21template <size_t Q>
22s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
23 const f32 ramp_, const u32 sample_count) {
24 Common::FixedPoint<64 - Q, Q> volume{volume_};
25 Common::FixedPoint<64 - Q, Q> sample{0};
26
27 if (ramp_ == 0.0f) {
28 for (u32 i = 0; i < sample_count; i++) {
29 sample = input[i] * volume;
30 output[i] = (output[i] + sample).to_int();
31 }
32 } else {
33 Common::FixedPoint<64 - Q, Q> ramp{ramp_};
34 for (u32 i = 0; i < sample_count; i++) {
35 sample = input[i] * volume;
36 output[i] = (output[i] + sample).to_int();
37 volume += ramp;
38 }
39 }
40 return sample.to_int();
41}
42
43template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32,
44 const u32);
45template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32,
46 const u32);
47
48void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
49 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
50 string += fmt::format("MixRampCommand");
51 string += fmt::format("\n\tinput {:02X}", input_index);
52 string += fmt::format("\n\toutput {:02X}", output_index);
53 string += fmt::format("\n\tvolume {:.8f}", volume);
54 string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
55 string += fmt::format("\n\tramp {:.8f}", ramp);
56 string += "\n";
57}
58
59void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) {
60 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
61 processor.sample_count)};
62 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
63 processor.sample_count)};
64 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
65 auto prev_sample_ptr{reinterpret_cast<s32*>(previous_sample)};
66
67 // If previous volume and ramp are both 0, nothing will be added to the output, so just skip.
68 if (prev_volume == 0.0f && ramp == 0.0f) {
69 *prev_sample_ptr = 0;
70 return;
71 }
72
73 switch (precision) {
74 case 15:
75 *prev_sample_ptr =
76 ApplyMixRamp<15>(output, input, prev_volume, ramp, processor.sample_count);
77 break;
78
79 case 23:
80 *prev_sample_ptr =
81 ApplyMixRamp<23>(output, input, prev_volume, ramp, processor.sample_count);
82 break;
83
84 default:
85 LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
86 break;
87 }
88}
89
90bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
91 return true;
92}
93
94} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
19 * applied to the input, and volume ramping to smooth out the transition.
20 */
21struct MixRampCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Fixed point precision
46 u8 precision;
47 /// Input mix buffer index
48 s16 input_index;
49 /// Output mix buffer index
50 s16 output_index;
51 /// Previous mix volume
52 f32 prev_volume;
53 /// Current mix volume
54 f32 volume;
55 /// Pointer to the previous sample buffer, used for depopping
56 CpuAddr previous_sample;
57};
58
59/**
60 * Mix input mix buffer into output mix buffer, with volume applied to the input.
61 * @tparam Q - Number of bits for fixed point operations.
62 * @param output - Output mix buffer.
63 * @param input - Input mix buffer.
64 * @param volume - Volume applied to the input.
65 * @param ramp - Ramp applied to volume every sample.
66 * @param sample_count - Number of samples to process.
67 * @return The final gained input sample, used for depopping.
68 */
69template <size_t Q>
70s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
71 const f32 ramp_, const u32 sample_count);
72
73} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/mix_ramp.h"
6#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
7
8namespace AudioCore::AudioRenderer {
9
10void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
11 string += "MixRampGroupedCommand";
12 for (u32 i = 0; i < buffer_count; i++) {
13 string += fmt::format("\n\t{}", i);
14 const auto ramp{(volumes[i] - prev_volumes[i]) / static_cast<f32>(processor.sample_count)};
15 string += fmt::format("\n\t\tinput {:02X}", inputs[i]);
16 string += fmt::format("\n\t\toutput {:02X}", outputs[i]);
17 string += fmt::format("\n\t\tvolume {:.8f}", volumes[i]);
18 string += fmt::format("\n\t\tprev_volume {:.8f}", prev_volumes[i]);
19 string += fmt::format("\n\t\tramp {:.8f}", ramp);
20 string += "\n";
21 }
22}
23
24void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) {
25 std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers};
26
27 for (u32 i = 0; i < buffer_count; i++) {
28 auto last_sample{0};
29 if (prev_volumes[i] != 0.0f || volumes[i] != 0.0f) {
30 const auto output{processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
31 processor.sample_count)};
32 const auto input{processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
33 processor.sample_count)};
34 const auto ramp{(volumes[i] - prev_volumes[i]) /
35 static_cast<f32>(processor.sample_count)};
36
37 if (prev_volumes[i] == 0.0f && ramp == 0.0f) {
38 prev_samples[i] = 0;
39 continue;
40 }
41
42 switch (precision) {
43 case 15:
44 last_sample =
45 ApplyMixRamp<15>(output, input, prev_volumes[i], ramp, processor.sample_count);
46 break;
47 case 23:
48 last_sample =
49 ApplyMixRamp<23>(output, input, prev_volumes[i], ramp, processor.sample_count);
50 break;
51 default:
52 LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
53 break;
54 }
55 }
56
57 prev_samples[i] = last_sample;
58 }
59}
60
61bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) {
62 return true;
63}
64
65} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with
19 * a volume applied to the input, and volume ramping to smooth out the transition.
20 */
21struct MixRampGroupedCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Fixed point precision
46 u8 precision;
47 /// Number of mix buffers to mix
48 u32 buffer_count;
49 /// Input mix buffer indexes for each mix buffer
50 std::array<s16, MaxMixBuffers> inputs;
51 /// Output mix buffer indexes for each mix buffer
52 std::array<s16, MaxMixBuffers> outputs;
53 /// Previous mix vloumes for each mix buffer
54 std::array<f32, MaxMixBuffers> prev_volumes;
55 /// Current mix vloumes for each mix buffer
56 std::array<f32, MaxMixBuffers> volumes;
57 /// Pointer to the previous sample buffer, used for depop
58 CpuAddr previous_samples;
59};
60
61} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/volume.h"
6#include "common/fixed_point.h"
7#include "common/logging/log.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Apply volume to the input mix buffer, saving to the output buffer.
12 *
13 * @tparam Q - Number of bits for fixed point operations.
14 * @param output - Output mix buffer.
15 * @param input - Input mix buffer.
16 * @param volume - Volume applied to the input.
17 * @param sample_count - Number of samples to process.
18 */
19template <size_t Q>
20static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input, const f32 volume,
21 const u32 sample_count) {
22 if (volume == 1.0f) {
23 std::memcpy(output.data(), input.data(), input.size_bytes());
24 } else {
25 const Common::FixedPoint<64 - Q, Q> gain{volume};
26 for (u32 i = 0; i < sample_count; i++) {
27 output[i] = (input[i] * gain).to_int();
28 }
29 }
30}
31
32void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
33 std::string& string) {
34 string += fmt::format("VolumeCommand");
35 string += fmt::format("\n\tinput {:02X}", input_index);
36 string += fmt::format("\n\toutput {:02X}", output_index);
37 string += fmt::format("\n\tvolume {:.8f}", volume);
38 string += "\n";
39}
40
41void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) {
42 // If input and output buffers are the same, and the volume is 1.0f, this won't do
43 // anything, so just skip.
44 if (input_index == output_index && volume == 1.0f) {
45 return;
46 }
47
48 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
49 processor.sample_count)};
50 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
51 processor.sample_count)};
52
53 switch (precision) {
54 case 15:
55 ApplyUniformGain<15>(output, input, volume, processor.sample_count);
56 break;
57
58 case 23:
59 ApplyUniformGain<23>(output, input, volume, processor.sample_count);
60 break;
61
62 default:
63 LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
64 break;
65 }
66}
67
68bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) {
69 return true;
70}
71
72} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for applying volume to a mix buffer.
18 */
19struct VolumeCommand : ICommand {
20 /**
21 * Print this command's information to a string.
22 *
23 * @param processor - The CommandListProcessor processing this command.
24 * @param string - The string to print into.
25 */
26 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
27
28 /**
29 * Process this command.
30 *
31 * @param processor - The CommandListProcessor processing this command.
32 */
33 void Process(const ADSP::CommandListProcessor& processor) override;
34
35 /**
36 * Verify this command's data is valid.
37 *
38 * @param processor - The CommandListProcessor processing this command.
39 * @return True if the command is valid, otherwise false.
40 */
41 bool Verify(const ADSP::CommandListProcessor& processor) override;
42
43 /// Fixed point precision
44 u8 precision;
45 /// Input mix buffer index
46 s16 input_index;
47 /// Output mix buffer index
48 s16 output_index;
49 /// Mix volume applied to the input
50 f32 volume;
51};
52
53} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/volume_ramp.h"
6#include "common/fixed_point.h"
7
8namespace AudioCore::AudioRenderer {
9/**
10 * Apply volume with ramping to the input mix buffer, saving to the output buffer.
11 *
12 * @tparam Q - Number of bits for fixed point operations.
13 * @param output - Output mix buffers.
14 * @param input - Input mix buffers.
15 * @param volume - Volume applied to the input.
16 * @param ramp - Ramp applied to volume every sample.
17 * @param sample_count - Number of samples to process.
18 */
19template <size_t Q>
20static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32> input,
21 const f32 volume, const f32 ramp_, const u32 sample_count) {
22 if (volume == 0.0f && ramp_ == 0.0f) {
23 std::memset(output.data(), 0, output.size_bytes());
24 } else if (volume == 1.0f && ramp_ == 0.0f) {
25 std::memcpy(output.data(), input.data(), output.size_bytes());
26 } else if (ramp_ == 0.0f) {
27 const Common::FixedPoint<64 - Q, Q> gain{volume};
28 for (u32 i = 0; i < sample_count; i++) {
29 output[i] = (input[i] * gain).to_int();
30 }
31 } else {
32 Common::FixedPoint<64 - Q, Q> gain{volume};
33 const Common::FixedPoint<64 - Q, Q> ramp{ramp_};
34 for (u32 i = 0; i < sample_count; i++) {
35 output[i] = (input[i] * gain).to_int();
36 gain += ramp;
37 }
38 }
39}
40
41void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
42 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
43 string += fmt::format("VolumeRampCommand");
44 string += fmt::format("\n\tinput {:02X}", input_index);
45 string += fmt::format("\n\toutput {:02X}", output_index);
46 string += fmt::format("\n\tvolume {:.8f}", volume);
47 string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
48 string += fmt::format("\n\tramp {:.8f}", ramp);
49 string += "\n";
50}
51
52void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) {
53 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
54 processor.sample_count)};
55 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
56 processor.sample_count)};
57 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
58
59 // If input and output buffers are the same, and the volume is 1.0f, and there's no ramping,
60 // this won't do anything, so just skip.
61 if (input_index == output_index && prev_volume == 1.0f && ramp == 0.0f) {
62 return;
63 }
64
65 switch (precision) {
66 case 15:
67 ApplyLinearEnvelopeGain<15>(output, input, prev_volume, ramp, processor.sample_count);
68 break;
69
70 case 23:
71 ApplyLinearEnvelopeGain<23>(output, input, prev_volume, ramp, processor.sample_count);
72 break;
73
74 default:
75 LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
76 break;
77 }
78}
79
80bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
81 return true;
82}
83
84} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth
18 * out the transition.
19 */
20struct VolumeRampCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Fixed point precision
45 u8 precision;
46 /// Input mix buffer index
47 s16 input_index;
48 /// Output mix buffer index
49 s16 output_index;
50 /// Previous mix volume applied to the input
51 f32 prev_volume;
52 /// Current mix volume applied to the input
53 f32 volume;
54};
55
56} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/performance/performance.h"
6#include "core/core.h"
7#include "core/core_timing.h"
8#include "core/core_timing_util.h"
9
10namespace AudioCore::AudioRenderer {
11
12void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
13 std::string& string) {
14 string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state));
15}
16
17void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
18 auto base{entry_address.translated_address};
19 if (state == PerformanceState::Start) {
20 auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
21 *start_time_ptr = static_cast<u32>(
22 Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
23 processor.start_time - processor.current_processing_time)
24 .count());
25 } else if (state == PerformanceState::Stop) {
26 auto processed_time_ptr{
27 reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
28 auto entry_count_ptr{
29 reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
30
31 *processed_time_ptr = static_cast<u32>(
32 Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
33 processor.start_time - processor.current_processing_time)
34 .count());
35 (*entry_count_ptr)++;
36 }
37}
38
39bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) {
40 return true;
41}
42
43} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "audio_core/renderer/performance/performance_entry_addresses.h"
10#include "audio_core/renderer/performance/performance_manager.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule.
20 */
21struct PerformanceCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// State of the performance
46 PerformanceState state;
47 /// Pointers to be written
48 PerformanceEntryAddresses entry_address;
49};
50
51} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
6
7namespace AudioCore::AudioRenderer {
8
9void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
10 std::string& string) {
11 string += fmt::format("DownMix6chTo2chCommand\n\tinputs: ");
12 for (u32 i = 0; i < MaxChannels; i++) {
13 string += fmt::format("{:02X}, ", inputs[i]);
14 }
15 string += "\n\toutputs: ";
16 for (u32 i = 0; i < MaxChannels; i++) {
17 string += fmt::format("{:02X}, ", outputs[i]);
18 }
19 string += "\n";
20}
21
22void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) {
23 auto in_front_left{
24 processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)};
25 auto in_front_right{
26 processor.mix_buffers.subspan(inputs[1] * processor.sample_count, processor.sample_count)};
27 auto in_center{
28 processor.mix_buffers.subspan(inputs[2] * processor.sample_count, processor.sample_count)};
29 auto in_lfe{
30 processor.mix_buffers.subspan(inputs[3] * processor.sample_count, processor.sample_count)};
31 auto in_back_left{
32 processor.mix_buffers.subspan(inputs[4] * processor.sample_count, processor.sample_count)};
33 auto in_back_right{
34 processor.mix_buffers.subspan(inputs[5] * processor.sample_count, processor.sample_count)};
35
36 auto out_front_left{
37 processor.mix_buffers.subspan(outputs[0] * processor.sample_count, processor.sample_count)};
38 auto out_front_right{
39 processor.mix_buffers.subspan(outputs[1] * processor.sample_count, processor.sample_count)};
40 auto out_center{
41 processor.mix_buffers.subspan(outputs[2] * processor.sample_count, processor.sample_count)};
42 auto out_lfe{
43 processor.mix_buffers.subspan(outputs[3] * processor.sample_count, processor.sample_count)};
44 auto out_back_left{
45 processor.mix_buffers.subspan(outputs[4] * processor.sample_count, processor.sample_count)};
46 auto out_back_right{
47 processor.mix_buffers.subspan(outputs[5] * processor.sample_count, processor.sample_count)};
48
49 for (u32 i = 0; i < processor.sample_count; i++) {
50 const auto left_sample{(in_front_left[i] * down_mix_coeff[0] +
51 in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
52 in_back_left[i] * down_mix_coeff[3])
53 .to_int()};
54
55 const auto right_sample{(in_front_right[i] * down_mix_coeff[0] +
56 in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
57 in_back_right[i] * down_mix_coeff[3])
58 .to_int()};
59
60 out_front_left[i] = left_sample;
61 out_front_right[i] = right_sample;
62 }
63
64 std::memset(out_center.data(), 0, out_center.size_bytes());
65 std::memset(out_lfe.data(), 0, out_lfe.size_bytes());
66 std::memset(out_back_left.data(), 0, out_back_left.size_bytes());
67 std::memset(out_back_right.data(), 0, out_back_right.size_bytes());
68}
69
70bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) {
71 return true;
72}
73
74} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10#include "common/fixed_point.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command for downmixing 6 channels to 2.
19 * Channel layout (SMPTE):
20 * 0 - front left
21 * 1 - front right
22 * 2 - center
23 * 3 - lfe
24 * 4 - back left
25 * 5 - back right
26 */
27struct DownMix6chTo2chCommand : ICommand {
28 /**
29 * Print this command's information to a string.
30 *
31 * @param processor - The CommandListProcessor processing this command.
32 * @param string - The string to print into.
33 */
34 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
35
36 /**
37 * Process this command.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 */
41 void Process(const ADSP::CommandListProcessor& processor) override;
42
43 /**
44 * Verify this command's data is valid.
45 *
46 * @param processor - The CommandListProcessor processing this command.
47 * @return True if the command is valid, otherwise false.
48 */
49 bool Verify(const ADSP::CommandListProcessor& processor) override;
50
51 /// Input mix buffer offsets for each channel
52 std::array<s16, MaxChannels> inputs;
53 /// Output mix buffer offsets for each channel
54 std::array<s16, MaxChannels> outputs;
55 /// Coefficients used for downmixing
56 std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff;
57};
58
59} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/command/resample/resample.h"
5
6namespace AudioCore::AudioRenderer {
7
8static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input,
9 const Common::FixedPoint<49, 15>& sample_rate_ratio,
10 Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
11 if (sample_rate_ratio == 1.0f) {
12 for (u32 i = 0; i < samples_to_write; i++) {
13 output[i] = input[i];
14 }
15 } else {
16 u32 read_index{0};
17 for (u32 i = 0; i < samples_to_write; i++) {
18 output[i] = input[read_index + (fraction >= 0.5f)];
19 fraction += sample_rate_ratio;
20 read_index += static_cast<u32>(fraction.to_int_floor());
21 fraction.clear_int();
22 }
23 }
24}
25
26static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> input,
27 const Common::FixedPoint<49, 15>& sample_rate_ratio,
28 Common::FixedPoint<49, 15>& fraction,
29 const u32 samples_to_write) {
30 static constexpr std::array<f32, 512> lut0 = {
31 0.20141602f, 0.59283447f, 0.20513916f, 0.00009155f, 0.19772339f, 0.59277344f, 0.20889282f,
32 0.00027466f, 0.19406128f, 0.59262085f, 0.21264648f, 0.00045776f, 0.19039917f, 0.59240723f,
33 0.21646118f, 0.00067139f, 0.18679810f, 0.59213257f, 0.22030640f, 0.00085449f, 0.18322754f,
34 0.59176636f, 0.22415161f, 0.00103760f, 0.17968750f, 0.59133911f, 0.22802734f, 0.00125122f,
35 0.17617798f, 0.59085083f, 0.23193359f, 0.00146484f, 0.17269897f, 0.59027100f, 0.23583984f,
36 0.00167847f, 0.16925049f, 0.58963013f, 0.23977661f, 0.00189209f, 0.16583252f, 0.58892822f,
37 0.24374390f, 0.00210571f, 0.16244507f, 0.58816528f, 0.24774170f, 0.00234985f, 0.15908813f,
38 0.58731079f, 0.25173950f, 0.00256348f, 0.15576172f, 0.58639526f, 0.25576782f, 0.00280762f,
39 0.15249634f, 0.58541870f, 0.25979614f, 0.00308228f, 0.14923096f, 0.58435059f, 0.26385498f,
40 0.00332642f, 0.14602661f, 0.58325195f, 0.26794434f, 0.00360107f, 0.14285278f, 0.58206177f,
41 0.27203369f, 0.00387573f, 0.13973999f, 0.58078003f, 0.27612305f, 0.00418091f, 0.13662720f,
42 0.57946777f, 0.28024292f, 0.00448608f, 0.13357544f, 0.57806396f, 0.28436279f, 0.00479126f,
43 0.13052368f, 0.57662964f, 0.28851318f, 0.00512695f, 0.12753296f, 0.57510376f, 0.29266357f,
44 0.00546265f, 0.12460327f, 0.57351685f, 0.29681396f, 0.00579834f, 0.12167358f, 0.57183838f,
45 0.30099487f, 0.00616455f, 0.11880493f, 0.57012939f, 0.30517578f, 0.00656128f, 0.11596680f,
46 0.56835938f, 0.30935669f, 0.00695801f, 0.11318970f, 0.56649780f, 0.31353760f, 0.00735474f,
47 0.11041260f, 0.56457520f, 0.31771851f, 0.00778198f, 0.10769653f, 0.56262207f, 0.32192993f,
48 0.00823975f, 0.10501099f, 0.56057739f, 0.32614136f, 0.00869751f, 0.10238647f, 0.55847168f,
49 0.33032227f, 0.00915527f, 0.09976196f, 0.55633545f, 0.33453369f, 0.00967407f, 0.09722900f,
50 0.55410767f, 0.33874512f, 0.01019287f, 0.09469604f, 0.55181885f, 0.34295654f, 0.01071167f,
51 0.09222412f, 0.54949951f, 0.34713745f, 0.01126099f, 0.08978271f, 0.54708862f, 0.35134888f,
52 0.01184082f, 0.08737183f, 0.54464722f, 0.35552979f, 0.01245117f, 0.08499146f, 0.54214478f,
53 0.35974121f, 0.01306152f, 0.08267212f, 0.53958130f, 0.36392212f, 0.01370239f, 0.08041382f,
54 0.53695679f, 0.36810303f, 0.01437378f, 0.07815552f, 0.53427124f, 0.37225342f, 0.01507568f,
55 0.07595825f, 0.53155518f, 0.37640381f, 0.01577759f, 0.07379150f, 0.52877808f, 0.38055420f,
56 0.01651001f, 0.07165527f, 0.52593994f, 0.38470459f, 0.01727295f, 0.06958008f, 0.52307129f,
57 0.38882446f, 0.01806641f, 0.06753540f, 0.52014160f, 0.39294434f, 0.01889038f, 0.06552124f,
58 0.51715088f, 0.39703369f, 0.01974487f, 0.06356812f, 0.51409912f, 0.40112305f, 0.02059937f,
59 0.06164551f, 0.51101685f, 0.40518188f, 0.02148438f, 0.05975342f, 0.50790405f, 0.40921021f,
60 0.02243042f, 0.05789185f, 0.50473022f, 0.41323853f, 0.02337646f, 0.05609131f, 0.50152588f,
61 0.41726685f, 0.02435303f, 0.05432129f, 0.49826050f, 0.42123413f, 0.02539062f, 0.05258179f,
62 0.49493408f, 0.42520142f, 0.02642822f, 0.05087280f, 0.49160767f, 0.42913818f, 0.02749634f,
63 0.04922485f, 0.48822021f, 0.43307495f, 0.02859497f, 0.04760742f, 0.48477173f, 0.43695068f,
64 0.02975464f, 0.04602051f, 0.48132324f, 0.44082642f, 0.03091431f, 0.04446411f, 0.47781372f,
65 0.44467163f, 0.03210449f, 0.04293823f, 0.47424316f, 0.44845581f, 0.03335571f, 0.04147339f,
66 0.47067261f, 0.45223999f, 0.03460693f, 0.04003906f, 0.46704102f, 0.45599365f, 0.03591919f,
67 0.03863525f, 0.46340942f, 0.45971680f, 0.03726196f, 0.03726196f, 0.45971680f, 0.46340942f,
68 0.03863525f, 0.03591919f, 0.45599365f, 0.46704102f, 0.04003906f, 0.03460693f, 0.45223999f,
69 0.47067261f, 0.04147339f, 0.03335571f, 0.44845581f, 0.47424316f, 0.04293823f, 0.03210449f,
70 0.44467163f, 0.47781372f, 0.04446411f, 0.03091431f, 0.44082642f, 0.48132324f, 0.04602051f,
71 0.02975464f, 0.43695068f, 0.48477173f, 0.04760742f, 0.02859497f, 0.43307495f, 0.48822021f,
72 0.04922485f, 0.02749634f, 0.42913818f, 0.49160767f, 0.05087280f, 0.02642822f, 0.42520142f,
73 0.49493408f, 0.05258179f, 0.02539062f, 0.42123413f, 0.49826050f, 0.05432129f, 0.02435303f,
74 0.41726685f, 0.50152588f, 0.05609131f, 0.02337646f, 0.41323853f, 0.50473022f, 0.05789185f,
75 0.02243042f, 0.40921021f, 0.50790405f, 0.05975342f, 0.02148438f, 0.40518188f, 0.51101685f,
76 0.06164551f, 0.02059937f, 0.40112305f, 0.51409912f, 0.06356812f, 0.01974487f, 0.39703369f,
77 0.51715088f, 0.06552124f, 0.01889038f, 0.39294434f, 0.52014160f, 0.06753540f, 0.01806641f,
78 0.38882446f, 0.52307129f, 0.06958008f, 0.01727295f, 0.38470459f, 0.52593994f, 0.07165527f,
79 0.01651001f, 0.38055420f, 0.52877808f, 0.07379150f, 0.01577759f, 0.37640381f, 0.53155518f,
80 0.07595825f, 0.01507568f, 0.37225342f, 0.53427124f, 0.07815552f, 0.01437378f, 0.36810303f,
81 0.53695679f, 0.08041382f, 0.01370239f, 0.36392212f, 0.53958130f, 0.08267212f, 0.01306152f,
82 0.35974121f, 0.54214478f, 0.08499146f, 0.01245117f, 0.35552979f, 0.54464722f, 0.08737183f,
83 0.01184082f, 0.35134888f, 0.54708862f, 0.08978271f, 0.01126099f, 0.34713745f, 0.54949951f,
84 0.09222412f, 0.01071167f, 0.34295654f, 0.55181885f, 0.09469604f, 0.01019287f, 0.33874512f,
85 0.55410767f, 0.09722900f, 0.00967407f, 0.33453369f, 0.55633545f, 0.09976196f, 0.00915527f,
86 0.33032227f, 0.55847168f, 0.10238647f, 0.00869751f, 0.32614136f, 0.56057739f, 0.10501099f,
87 0.00823975f, 0.32192993f, 0.56262207f, 0.10769653f, 0.00778198f, 0.31771851f, 0.56457520f,
88 0.11041260f, 0.00735474f, 0.31353760f, 0.56649780f, 0.11318970f, 0.00695801f, 0.30935669f,
89 0.56835938f, 0.11596680f, 0.00656128f, 0.30517578f, 0.57012939f, 0.11880493f, 0.00616455f,
90 0.30099487f, 0.57183838f, 0.12167358f, 0.00579834f, 0.29681396f, 0.57351685f, 0.12460327f,
91 0.00546265f, 0.29266357f, 0.57510376f, 0.12753296f, 0.00512695f, 0.28851318f, 0.57662964f,
92 0.13052368f, 0.00479126f, 0.28436279f, 0.57806396f, 0.13357544f, 0.00448608f, 0.28024292f,
93 0.57946777f, 0.13662720f, 0.00418091f, 0.27612305f, 0.58078003f, 0.13973999f, 0.00387573f,
94 0.27203369f, 0.58206177f, 0.14285278f, 0.00360107f, 0.26794434f, 0.58325195f, 0.14602661f,
95 0.00332642f, 0.26385498f, 0.58435059f, 0.14923096f, 0.00308228f, 0.25979614f, 0.58541870f,
96 0.15249634f, 0.00280762f, 0.25576782f, 0.58639526f, 0.15576172f, 0.00256348f, 0.25173950f,
97 0.58731079f, 0.15908813f, 0.00234985f, 0.24774170f, 0.58816528f, 0.16244507f, 0.00210571f,
98 0.24374390f, 0.58892822f, 0.16583252f, 0.00189209f, 0.23977661f, 0.58963013f, 0.16925049f,
99 0.00167847f, 0.23583984f, 0.59027100f, 0.17269897f, 0.00146484f, 0.23193359f, 0.59085083f,
100 0.17617798f, 0.00125122f, 0.22802734f, 0.59133911f, 0.17968750f, 0.00103760f, 0.22415161f,
101 0.59176636f, 0.18322754f, 0.00085449f, 0.22030640f, 0.59213257f, 0.18679810f, 0.00067139f,
102 0.21646118f, 0.59240723f, 0.19039917f, 0.00045776f, 0.21264648f, 0.59262085f, 0.19406128f,
103 0.00027466f, 0.20889282f, 0.59277344f, 0.19772339f, 0.00009155f, 0.20513916f, 0.59283447f,
104 0.20141602f,
105 };
106
107 static constexpr std::array<f32, 512> lut1 = {
108 0.00207520f, 0.99606323f, 0.00210571f, -0.00015259f, -0.00610352f, 0.99578857f,
109 0.00646973f, -0.00045776f, -0.01000977f, 0.99526978f, 0.01095581f, -0.00079346f,
110 -0.01373291f, 0.99444580f, 0.01562500f, -0.00109863f, -0.01733398f, 0.99337769f,
111 0.02041626f, -0.00143433f, -0.02075195f, 0.99203491f, 0.02539062f, -0.00177002f,
112 -0.02404785f, 0.99041748f, 0.03051758f, -0.00210571f, -0.02719116f, 0.98855591f,
113 0.03582764f, -0.00244141f, -0.03021240f, 0.98641968f, 0.04125977f, -0.00280762f,
114 -0.03308105f, 0.98400879f, 0.04687500f, -0.00314331f, -0.03579712f, 0.98135376f,
115 0.05261230f, -0.00350952f, -0.03839111f, 0.97842407f, 0.05856323f, -0.00390625f,
116 -0.04083252f, 0.97521973f, 0.06463623f, -0.00427246f, -0.04315186f, 0.97180176f,
117 0.07086182f, -0.00466919f, -0.04534912f, 0.96810913f, 0.07727051f, -0.00509644f,
118 -0.04742432f, 0.96414185f, 0.08383179f, -0.00549316f, -0.04934692f, 0.95996094f,
119 0.09054565f, -0.00592041f, -0.05114746f, 0.95550537f, 0.09741211f, -0.00637817f,
120 -0.05285645f, 0.95083618f, 0.10443115f, -0.00683594f, -0.05441284f, 0.94589233f,
121 0.11160278f, -0.00732422f, -0.05584717f, 0.94073486f, 0.11892700f, -0.00781250f,
122 -0.05718994f, 0.93533325f, 0.12643433f, -0.00830078f, -0.05841064f, 0.92968750f,
123 0.13406372f, -0.00881958f, -0.05953979f, 0.92382812f, 0.14184570f, -0.00936890f,
124 -0.06054688f, 0.91772461f, 0.14978027f, -0.00991821f, -0.06146240f, 0.91143799f,
125 0.15783691f, -0.01046753f, -0.06225586f, 0.90490723f, 0.16607666f, -0.01104736f,
126 -0.06295776f, 0.89816284f, 0.17443848f, -0.01165771f, -0.06356812f, 0.89120483f,
127 0.18292236f, -0.01229858f, -0.06408691f, 0.88403320f, 0.19155884f, -0.01293945f,
128 -0.06451416f, 0.87667847f, 0.20034790f, -0.01358032f, -0.06484985f, 0.86914062f,
129 0.20925903f, -0.01428223f, -0.06509399f, 0.86138916f, 0.21829224f, -0.01495361f,
130 -0.06527710f, 0.85345459f, 0.22744751f, -0.01568604f, -0.06536865f, 0.84533691f,
131 0.23675537f, -0.01641846f, -0.06536865f, 0.83703613f, 0.24615479f, -0.01718140f,
132 -0.06533813f, 0.82858276f, 0.25567627f, -0.01794434f, -0.06518555f, 0.81991577f,
133 0.26531982f, -0.01873779f, -0.06500244f, 0.81112671f, 0.27505493f, -0.01956177f,
134 -0.06472778f, 0.80215454f, 0.28491211f, -0.02038574f, -0.06442261f, 0.79306030f,
135 0.29489136f, -0.02124023f, -0.06402588f, 0.78378296f, 0.30496216f, -0.02209473f,
136 -0.06359863f, 0.77438354f, 0.31512451f, -0.02297974f, -0.06307983f, 0.76486206f,
137 0.32537842f, -0.02389526f, -0.06253052f, 0.75518799f, 0.33569336f, -0.02481079f,
138 -0.06195068f, 0.74539185f, 0.34613037f, -0.02575684f, -0.06130981f, 0.73547363f,
139 0.35662842f, -0.02670288f, -0.06060791f, 0.72543335f, 0.36721802f, -0.02767944f,
140 -0.05987549f, 0.71527100f, 0.37786865f, -0.02865601f, -0.05911255f, 0.70504761f,
141 0.38858032f, -0.02966309f, -0.05831909f, 0.69470215f, 0.39935303f, -0.03067017f,
142 -0.05746460f, 0.68426514f, 0.41018677f, -0.03170776f, -0.05661011f, 0.67373657f,
143 0.42108154f, -0.03271484f, -0.05569458f, 0.66311646f, 0.43200684f, -0.03378296f,
144 -0.05477905f, 0.65246582f, 0.44299316f, -0.03482056f, -0.05383301f, 0.64169312f,
145 0.45401001f, -0.03588867f, -0.05285645f, 0.63088989f, 0.46505737f, -0.03695679f,
146 -0.05187988f, 0.62002563f, 0.47613525f, -0.03802490f, -0.05087280f, 0.60910034f,
147 0.48721313f, -0.03912354f, -0.04983521f, 0.59814453f, 0.49832153f, -0.04019165f,
148 -0.04879761f, 0.58712769f, 0.50946045f, -0.04129028f, -0.04772949f, 0.57611084f,
149 0.52056885f, -0.04235840f, -0.04669189f, 0.56503296f, 0.53170776f, -0.04345703f,
150 -0.04562378f, 0.55392456f, 0.54281616f, -0.04452515f, -0.04452515f, 0.54281616f,
151 0.55392456f, -0.04562378f, -0.04345703f, 0.53170776f, 0.56503296f, -0.04669189f,
152 -0.04235840f, 0.52056885f, 0.57611084f, -0.04772949f, -0.04129028f, 0.50946045f,
153 0.58712769f, -0.04879761f, -0.04019165f, 0.49832153f, 0.59814453f, -0.04983521f,
154 -0.03912354f, 0.48721313f, 0.60910034f, -0.05087280f, -0.03802490f, 0.47613525f,
155 0.62002563f, -0.05187988f, -0.03695679f, 0.46505737f, 0.63088989f, -0.05285645f,
156 -0.03588867f, 0.45401001f, 0.64169312f, -0.05383301f, -0.03482056f, 0.44299316f,
157 0.65246582f, -0.05477905f, -0.03378296f, 0.43200684f, 0.66311646f, -0.05569458f,
158 -0.03271484f, 0.42108154f, 0.67373657f, -0.05661011f, -0.03170776f, 0.41018677f,
159 0.68426514f, -0.05746460f, -0.03067017f, 0.39935303f, 0.69470215f, -0.05831909f,
160 -0.02966309f, 0.38858032f, 0.70504761f, -0.05911255f, -0.02865601f, 0.37786865f,
161 0.71527100f, -0.05987549f, -0.02767944f, 0.36721802f, 0.72543335f, -0.06060791f,
162 -0.02670288f, 0.35662842f, 0.73547363f, -0.06130981f, -0.02575684f, 0.34613037f,
163 0.74539185f, -0.06195068f, -0.02481079f, 0.33569336f, 0.75518799f, -0.06253052f,
164 -0.02389526f, 0.32537842f, 0.76486206f, -0.06307983f, -0.02297974f, 0.31512451f,
165 0.77438354f, -0.06359863f, -0.02209473f, 0.30496216f, 0.78378296f, -0.06402588f,
166 -0.02124023f, 0.29489136f, 0.79306030f, -0.06442261f, -0.02038574f, 0.28491211f,
167 0.80215454f, -0.06472778f, -0.01956177f, 0.27505493f, 0.81112671f, -0.06500244f,
168 -0.01873779f, 0.26531982f, 0.81991577f, -0.06518555f, -0.01794434f, 0.25567627f,
169 0.82858276f, -0.06533813f, -0.01718140f, 0.24615479f, 0.83703613f, -0.06536865f,
170 -0.01641846f, 0.23675537f, 0.84533691f, -0.06536865f, -0.01568604f, 0.22744751f,
171 0.85345459f, -0.06527710f, -0.01495361f, 0.21829224f, 0.86138916f, -0.06509399f,
172 -0.01428223f, 0.20925903f, 0.86914062f, -0.06484985f, -0.01358032f, 0.20034790f,
173 0.87667847f, -0.06451416f, -0.01293945f, 0.19155884f, 0.88403320f, -0.06408691f,
174 -0.01229858f, 0.18292236f, 0.89120483f, -0.06356812f, -0.01165771f, 0.17443848f,
175 0.89816284f, -0.06295776f, -0.01104736f, 0.16607666f, 0.90490723f, -0.06225586f,
176 -0.01046753f, 0.15783691f, 0.91143799f, -0.06146240f, -0.00991821f, 0.14978027f,
177 0.91772461f, -0.06054688f, -0.00936890f, 0.14184570f, 0.92382812f, -0.05953979f,
178 -0.00881958f, 0.13406372f, 0.92968750f, -0.05841064f, -0.00830078f, 0.12643433f,
179 0.93533325f, -0.05718994f, -0.00781250f, 0.11892700f, 0.94073486f, -0.05584717f,
180 -0.00732422f, 0.11160278f, 0.94589233f, -0.05441284f, -0.00683594f, 0.10443115f,
181 0.95083618f, -0.05285645f, -0.00637817f, 0.09741211f, 0.95550537f, -0.05114746f,
182 -0.00592041f, 0.09054565f, 0.95996094f, -0.04934692f, -0.00549316f, 0.08383179f,
183 0.96414185f, -0.04742432f, -0.00509644f, 0.07727051f, 0.96810913f, -0.04534912f,
184 -0.00466919f, 0.07086182f, 0.97180176f, -0.04315186f, -0.00427246f, 0.06463623f,
185 0.97521973f, -0.04083252f, -0.00390625f, 0.05856323f, 0.97842407f, -0.03839111f,
186 -0.00350952f, 0.05261230f, 0.98135376f, -0.03579712f, -0.00314331f, 0.04687500f,
187 0.98400879f, -0.03308105f, -0.00280762f, 0.04125977f, 0.98641968f, -0.03021240f,
188 -0.00244141f, 0.03582764f, 0.98855591f, -0.02719116f, -0.00210571f, 0.03051758f,
189 0.99041748f, -0.02404785f, -0.00177002f, 0.02539062f, 0.99203491f, -0.02075195f,
190 -0.00143433f, 0.02041626f, 0.99337769f, -0.01733398f, -0.00109863f, 0.01562500f,
191 0.99444580f, -0.01373291f, -0.00079346f, 0.01095581f, 0.99526978f, -0.01000977f,
192 -0.00045776f, 0.00646973f, 0.99578857f, -0.00610352f, -0.00015259f, 0.00210571f,
193 0.99606323f, -0.00207520f,
194 };
195
196 static constexpr std::array<f32, 512> lut2 = {
197 0.09750366f, 0.80221558f, 0.10159302f, -0.00097656f, 0.09350586f, 0.80203247f,
198 0.10580444f, -0.00103760f, 0.08959961f, 0.80169678f, 0.11010742f, -0.00115967f,
199 0.08578491f, 0.80117798f, 0.11447144f, -0.00128174f, 0.08203125f, 0.80047607f,
200 0.11892700f, -0.00140381f, 0.07836914f, 0.79962158f, 0.12347412f, -0.00152588f,
201 0.07479858f, 0.79861450f, 0.12814331f, -0.00164795f, 0.07135010f, 0.79742432f,
202 0.13287354f, -0.00177002f, 0.06796265f, 0.79605103f, 0.13769531f, -0.00192261f,
203 0.06469727f, 0.79452515f, 0.14260864f, -0.00204468f, 0.06149292f, 0.79284668f,
204 0.14761353f, -0.00219727f, 0.05834961f, 0.79098511f, 0.15270996f, -0.00231934f,
205 0.05532837f, 0.78894043f, 0.15789795f, -0.00247192f, 0.05236816f, 0.78674316f,
206 0.16317749f, -0.00265503f, 0.04949951f, 0.78442383f, 0.16851807f, -0.00280762f,
207 0.04672241f, 0.78189087f, 0.17398071f, -0.00299072f, 0.04400635f, 0.77920532f,
208 0.17950439f, -0.00314331f, 0.04141235f, 0.77636719f, 0.18511963f, -0.00332642f,
209 0.03887939f, 0.77337646f, 0.19082642f, -0.00350952f, 0.03640747f, 0.77023315f,
210 0.19659424f, -0.00369263f, 0.03402710f, 0.76693726f, 0.20248413f, -0.00387573f,
211 0.03173828f, 0.76348877f, 0.20843506f, -0.00405884f, 0.02951050f, 0.75985718f,
212 0.21444702f, -0.00427246f, 0.02737427f, 0.75610352f, 0.22055054f, -0.00445557f,
213 0.02529907f, 0.75219727f, 0.22674561f, -0.00466919f, 0.02331543f, 0.74816895f,
214 0.23300171f, -0.00485229f, 0.02139282f, 0.74398804f, 0.23931885f, -0.00506592f,
215 0.01956177f, 0.73965454f, 0.24572754f, -0.00531006f, 0.01779175f, 0.73519897f,
216 0.25219727f, -0.00552368f, 0.01605225f, 0.73059082f, 0.25872803f, -0.00570679f,
217 0.01440430f, 0.72586060f, 0.26535034f, -0.00592041f, 0.01281738f, 0.72100830f,
218 0.27203369f, -0.00616455f, 0.01132202f, 0.71600342f, 0.27877808f, -0.00637817f,
219 0.00988770f, 0.71090698f, 0.28558350f, -0.00656128f, 0.00851440f, 0.70565796f,
220 0.29244995f, -0.00677490f, 0.00720215f, 0.70031738f, 0.29934692f, -0.00701904f,
221 0.00592041f, 0.69485474f, 0.30633545f, -0.00723267f, 0.00469971f, 0.68927002f,
222 0.31338501f, -0.00741577f, 0.00357056f, 0.68356323f, 0.32046509f, -0.00762939f,
223 0.00247192f, 0.67773438f, 0.32760620f, -0.00787354f, 0.00143433f, 0.67184448f,
224 0.33477783f, -0.00808716f, 0.00045776f, 0.66583252f, 0.34197998f, -0.00827026f,
225 -0.00048828f, 0.65972900f, 0.34924316f, -0.00845337f, -0.00134277f, 0.65353394f,
226 0.35656738f, -0.00863647f, -0.00216675f, 0.64721680f, 0.36389160f, -0.00885010f,
227 -0.00296021f, 0.64083862f, 0.37127686f, -0.00903320f, -0.00369263f, 0.63433838f,
228 0.37869263f, -0.00921631f, -0.00436401f, 0.62777710f, 0.38613892f, -0.00933838f,
229 -0.00497437f, 0.62115479f, 0.39361572f, -0.00949097f, -0.00558472f, 0.61444092f,
230 0.40109253f, -0.00964355f, -0.00613403f, 0.60763550f, 0.40859985f, -0.00979614f,
231 -0.00665283f, 0.60076904f, 0.41610718f, -0.00991821f, -0.00714111f, 0.59384155f,
232 0.42364502f, -0.01000977f, -0.00756836f, 0.58685303f, 0.43121338f, -0.01013184f,
233 -0.00796509f, 0.57977295f, 0.43875122f, -0.01022339f, -0.00833130f, 0.57266235f,
234 0.44631958f, -0.01028442f, -0.00866699f, 0.56552124f, 0.45388794f, -0.01034546f,
235 -0.00897217f, 0.55831909f, 0.46145630f, -0.01040649f, -0.00921631f, 0.55105591f,
236 0.46902466f, -0.01040649f, -0.00946045f, 0.54373169f, 0.47659302f, -0.01040649f,
237 -0.00967407f, 0.53640747f, 0.48413086f, -0.01037598f, -0.00985718f, 0.52902222f,
238 0.49166870f, -0.01037598f, -0.01000977f, 0.52160645f, 0.49917603f, -0.01031494f,
239 -0.01013184f, 0.51416016f, 0.50668335f, -0.01025391f, -0.01025391f, 0.50668335f,
240 0.51416016f, -0.01013184f, -0.01031494f, 0.49917603f, 0.52160645f, -0.01000977f,
241 -0.01037598f, 0.49166870f, 0.52902222f, -0.00985718f, -0.01037598f, 0.48413086f,
242 0.53640747f, -0.00967407f, -0.01040649f, 0.47659302f, 0.54373169f, -0.00946045f,
243 -0.01040649f, 0.46902466f, 0.55105591f, -0.00921631f, -0.01040649f, 0.46145630f,
244 0.55831909f, -0.00897217f, -0.01034546f, 0.45388794f, 0.56552124f, -0.00866699f,
245 -0.01028442f, 0.44631958f, 0.57266235f, -0.00833130f, -0.01022339f, 0.43875122f,
246 0.57977295f, -0.00796509f, -0.01013184f, 0.43121338f, 0.58685303f, -0.00756836f,
247 -0.01000977f, 0.42364502f, 0.59384155f, -0.00714111f, -0.00991821f, 0.41610718f,
248 0.60076904f, -0.00665283f, -0.00979614f, 0.40859985f, 0.60763550f, -0.00613403f,
249 -0.00964355f, 0.40109253f, 0.61444092f, -0.00558472f, -0.00949097f, 0.39361572f,
250 0.62115479f, -0.00497437f, -0.00933838f, 0.38613892f, 0.62777710f, -0.00436401f,
251 -0.00921631f, 0.37869263f, 0.63433838f, -0.00369263f, -0.00903320f, 0.37127686f,
252 0.64083862f, -0.00296021f, -0.00885010f, 0.36389160f, 0.64721680f, -0.00216675f,
253 -0.00863647f, 0.35656738f, 0.65353394f, -0.00134277f, -0.00845337f, 0.34924316f,
254 0.65972900f, -0.00048828f, -0.00827026f, 0.34197998f, 0.66583252f, 0.00045776f,
255 -0.00808716f, 0.33477783f, 0.67184448f, 0.00143433f, -0.00787354f, 0.32760620f,
256 0.67773438f, 0.00247192f, -0.00762939f, 0.32046509f, 0.68356323f, 0.00357056f,
257 -0.00741577f, 0.31338501f, 0.68927002f, 0.00469971f, -0.00723267f, 0.30633545f,
258 0.69485474f, 0.00592041f, -0.00701904f, 0.29934692f, 0.70031738f, 0.00720215f,
259 -0.00677490f, 0.29244995f, 0.70565796f, 0.00851440f, -0.00656128f, 0.28558350f,
260 0.71090698f, 0.00988770f, -0.00637817f, 0.27877808f, 0.71600342f, 0.01132202f,
261 -0.00616455f, 0.27203369f, 0.72100830f, 0.01281738f, -0.00592041f, 0.26535034f,
262 0.72586060f, 0.01440430f, -0.00570679f, 0.25872803f, 0.73059082f, 0.01605225f,
263 -0.00552368f, 0.25219727f, 0.73519897f, 0.01779175f, -0.00531006f, 0.24572754f,
264 0.73965454f, 0.01956177f, -0.00506592f, 0.23931885f, 0.74398804f, 0.02139282f,
265 -0.00485229f, 0.23300171f, 0.74816895f, 0.02331543f, -0.00466919f, 0.22674561f,
266 0.75219727f, 0.02529907f, -0.00445557f, 0.22055054f, 0.75610352f, 0.02737427f,
267 -0.00427246f, 0.21444702f, 0.75985718f, 0.02951050f, -0.00405884f, 0.20843506f,
268 0.76348877f, 0.03173828f, -0.00387573f, 0.20248413f, 0.76693726f, 0.03402710f,
269 -0.00369263f, 0.19659424f, 0.77023315f, 0.03640747f, -0.00350952f, 0.19082642f,
270 0.77337646f, 0.03887939f, -0.00332642f, 0.18511963f, 0.77636719f, 0.04141235f,
271 -0.00314331f, 0.17950439f, 0.77920532f, 0.04400635f, -0.00299072f, 0.17398071f,
272 0.78189087f, 0.04672241f, -0.00280762f, 0.16851807f, 0.78442383f, 0.04949951f,
273 -0.00265503f, 0.16317749f, 0.78674316f, 0.05236816f, -0.00247192f, 0.15789795f,
274 0.78894043f, 0.05532837f, -0.00231934f, 0.15270996f, 0.79098511f, 0.05834961f,
275 -0.00219727f, 0.14761353f, 0.79284668f, 0.06149292f, -0.00204468f, 0.14260864f,
276 0.79452515f, 0.06469727f, -0.00192261f, 0.13769531f, 0.79605103f, 0.06796265f,
277 -0.00177002f, 0.13287354f, 0.79742432f, 0.07135010f, -0.00164795f, 0.12814331f,
278 0.79861450f, 0.07479858f, -0.00152588f, 0.12347412f, 0.79962158f, 0.07836914f,
279 -0.00140381f, 0.11892700f, 0.80047607f, 0.08203125f, -0.00128174f, 0.11447144f,
280 0.80117798f, 0.08578491f, -0.00115967f, 0.11010742f, 0.80169678f, 0.08959961f,
281 -0.00103760f, 0.10580444f, 0.80203247f, 0.09350586f, -0.00097656f, 0.10159302f,
282 0.80221558f, 0.09750366f,
283 };
284
285 const auto get_lut = [&]() -> std::span<const f32> {
286 if (sample_rate_ratio <= 1.0f) {
287 return std::span<const f32>(lut2.data(), lut2.size());
288 } else if (sample_rate_ratio < 1.3f) {
289 return std::span<const f32>(lut1.data(), lut1.size());
290 } else {
291 return std::span<const f32>(lut0.data(), lut0.size());
292 }
293 };
294
295 auto lut{get_lut()};
296 u32 read_index{0};
297 for (u32 i = 0; i < samples_to_write; i++) {
298 const auto lut_index{(fraction.get_frac() >> 8) * 4};
299 const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
300 const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
301 const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
302 const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]};
303 output[i] = (sample0 + sample1 + sample2 + sample3).to_int_floor();
304 fraction += sample_rate_ratio;
305 read_index += static_cast<u32>(fraction.to_int_floor());
306 fraction.clear_int();
307 }
308}
309
310static void ResampleHighQuality(std::span<s32> output, std::span<const s16> input,
311 const Common::FixedPoint<49, 15>& sample_rate_ratio,
312 Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
313 static constexpr std::array<f32, 1024> lut0 = {
314 -0.01776123f, -0.00070190f, 0.26672363f, 0.50006104f, 0.26956177f, 0.00024414f,
315 -0.01800537f, 0.00000000f, -0.01748657f, -0.00164795f, 0.26388550f, 0.50003052f,
316 0.27236938f, 0.00122070f, -0.01824951f, -0.00003052f, -0.01724243f, -0.00256348f,
317 0.26107788f, 0.49996948f, 0.27520752f, 0.00219727f, -0.01849365f, -0.00003052f,
318 -0.01699829f, -0.00344849f, 0.25823975f, 0.49984741f, 0.27801514f, 0.00320435f,
319 -0.01873779f, -0.00006104f, -0.01675415f, -0.00433350f, 0.25543213f, 0.49972534f,
320 0.28085327f, 0.00424194f, -0.01898193f, -0.00006104f, -0.01651001f, -0.00518799f,
321 0.25259399f, 0.49954224f, 0.28366089f, 0.00527954f, -0.01922607f, -0.00009155f,
322 -0.01626587f, -0.00604248f, 0.24978638f, 0.49932861f, 0.28646851f, 0.00634766f,
323 -0.01947021f, -0.00012207f, -0.01602173f, -0.00686646f, 0.24697876f, 0.49908447f,
324 0.28930664f, 0.00744629f, -0.01971436f, -0.00015259f, -0.01574707f, -0.00765991f,
325 0.24414062f, 0.49877930f, 0.29211426f, 0.00854492f, -0.01995850f, -0.00015259f,
326 -0.01550293f, -0.00845337f, 0.24133301f, 0.49847412f, 0.29492188f, 0.00967407f,
327 -0.02020264f, -0.00018311f, -0.01525879f, -0.00921631f, 0.23852539f, 0.49810791f,
328 0.29772949f, 0.01083374f, -0.02044678f, -0.00021362f, -0.01501465f, -0.00997925f,
329 0.23571777f, 0.49774170f, 0.30050659f, 0.01199341f, -0.02069092f, -0.00024414f,
330 -0.01477051f, -0.01071167f, 0.23291016f, 0.49731445f, 0.30331421f, 0.01318359f,
331 -0.02093506f, -0.00027466f, -0.01452637f, -0.01141357f, 0.23010254f, 0.49685669f,
332 0.30609131f, 0.01437378f, -0.02117920f, -0.00030518f, -0.01428223f, -0.01211548f,
333 0.22732544f, 0.49636841f, 0.30886841f, 0.01559448f, -0.02142334f, -0.00033569f,
334 -0.01403809f, -0.01278687f, 0.22451782f, 0.49581909f, 0.31164551f, 0.01684570f,
335 -0.02163696f, -0.00039673f, -0.01379395f, -0.01345825f, 0.22174072f, 0.49526978f,
336 0.31442261f, 0.01809692f, -0.02188110f, -0.00042725f, -0.01358032f, -0.01409912f,
337 0.21896362f, 0.49465942f, 0.31719971f, 0.01937866f, -0.02209473f, -0.00045776f,
338 -0.01333618f, -0.01473999f, 0.21618652f, 0.49404907f, 0.31994629f, 0.02069092f,
339 -0.02233887f, -0.00048828f, -0.01309204f, -0.01535034f, 0.21343994f, 0.49337769f,
340 0.32269287f, 0.02203369f, -0.02255249f, -0.00054932f, -0.01284790f, -0.01596069f,
341 0.21066284f, 0.49267578f, 0.32543945f, 0.02337646f, -0.02279663f, -0.00057983f,
342 -0.01263428f, -0.01654053f, 0.20791626f, 0.49194336f, 0.32818604f, 0.02471924f,
343 -0.02301025f, -0.00064087f, -0.01239014f, -0.01708984f, 0.20516968f, 0.49118042f,
344 0.33090210f, 0.02612305f, -0.02322388f, -0.00067139f, -0.01214600f, -0.01763916f,
345 0.20242310f, 0.49035645f, 0.33361816f, 0.02752686f, -0.02343750f, -0.00073242f,
346 -0.01193237f, -0.01818848f, 0.19970703f, 0.48953247f, 0.33633423f, 0.02896118f,
347 -0.02365112f, -0.00079346f, -0.01168823f, -0.01867676f, 0.19696045f, 0.48864746f,
348 0.33901978f, 0.03039551f, -0.02386475f, -0.00082397f, -0.01147461f, -0.01919556f,
349 0.19427490f, 0.48776245f, 0.34170532f, 0.03186035f, -0.02407837f, -0.00088501f,
350 -0.01123047f, -0.01968384f, 0.19155884f, 0.48681641f, 0.34439087f, 0.03335571f,
351 -0.02429199f, -0.00094604f, -0.01101685f, -0.02014160f, 0.18887329f, 0.48583984f,
352 0.34704590f, 0.03485107f, -0.02447510f, -0.00100708f, -0.01080322f, -0.02059937f,
353 0.18615723f, 0.48483276f, 0.34970093f, 0.03637695f, -0.02468872f, -0.00106812f,
354 -0.01058960f, -0.02102661f, 0.18350220f, 0.48379517f, 0.35235596f, 0.03793335f,
355 -0.02487183f, -0.00112915f, -0.01034546f, -0.02145386f, 0.18081665f, 0.48272705f,
356 0.35498047f, 0.03948975f, -0.02505493f, -0.00119019f, -0.01013184f, -0.02188110f,
357 0.17816162f, 0.48162842f, 0.35760498f, 0.04107666f, -0.02523804f, -0.00125122f,
358 -0.00991821f, -0.02227783f, 0.17550659f, 0.48049927f, 0.36019897f, 0.04269409f,
359 -0.02542114f, -0.00131226f, -0.00970459f, -0.02264404f, 0.17288208f, 0.47933960f,
360 0.36279297f, 0.04431152f, -0.02560425f, -0.00140381f, -0.00952148f, -0.02301025f,
361 0.17025757f, 0.47814941f, 0.36538696f, 0.04595947f, -0.02578735f, -0.00146484f,
362 -0.00930786f, -0.02337646f, 0.16763306f, 0.47689819f, 0.36795044f, 0.04763794f,
363 -0.02593994f, -0.00152588f, -0.00909424f, -0.02371216f, 0.16503906f, 0.47564697f,
364 0.37048340f, 0.04931641f, -0.02609253f, -0.00161743f, -0.00888062f, -0.02401733f,
365 0.16244507f, 0.47436523f, 0.37304688f, 0.05102539f, -0.02627563f, -0.00170898f,
366 -0.00869751f, -0.02435303f, 0.15988159f, 0.47302246f, 0.37554932f, 0.05276489f,
367 -0.02642822f, -0.00177002f, -0.00848389f, -0.02462769f, 0.15731812f, 0.47167969f,
368 0.37805176f, 0.05450439f, -0.02658081f, -0.00186157f, -0.00830078f, -0.02493286f,
369 0.15475464f, 0.47027588f, 0.38055420f, 0.05627441f, -0.02670288f, -0.00195312f,
370 -0.00808716f, -0.02520752f, 0.15222168f, 0.46887207f, 0.38302612f, 0.05804443f,
371 -0.02685547f, -0.00204468f, -0.00790405f, -0.02545166f, 0.14968872f, 0.46743774f,
372 0.38546753f, 0.05987549f, -0.02697754f, -0.00213623f, -0.00772095f, -0.02569580f,
373 0.14718628f, 0.46594238f, 0.38790894f, 0.06170654f, -0.02709961f, -0.00222778f,
374 -0.00753784f, -0.02593994f, 0.14468384f, 0.46444702f, 0.39031982f, 0.06353760f,
375 -0.02722168f, -0.00231934f, -0.00735474f, -0.02615356f, 0.14218140f, 0.46289062f,
376 0.39273071f, 0.06539917f, -0.02734375f, -0.00241089f, -0.00717163f, -0.02636719f,
377 0.13970947f, 0.46133423f, 0.39511108f, 0.06729126f, -0.02743530f, -0.00250244f,
378 -0.00698853f, -0.02655029f, 0.13726807f, 0.45974731f, 0.39749146f, 0.06918335f,
379 -0.02755737f, -0.00259399f, -0.00680542f, -0.02673340f, 0.13479614f, 0.45812988f,
380 0.39984131f, 0.07113647f, -0.02764893f, -0.00271606f, -0.00662231f, -0.02691650f,
381 0.13238525f, 0.45648193f, 0.40216064f, 0.07305908f, -0.02774048f, -0.00280762f,
382 -0.00643921f, -0.02706909f, 0.12997437f, 0.45480347f, 0.40447998f, 0.07504272f,
383 -0.02780151f, -0.00292969f, -0.00628662f, -0.02722168f, 0.12756348f, 0.45309448f,
384 0.40676880f, 0.07699585f, -0.02789307f, -0.00305176f, -0.00610352f, -0.02734375f,
385 0.12518311f, 0.45135498f, 0.40902710f, 0.07901001f, -0.02795410f, -0.00314331f,
386 -0.00595093f, -0.02746582f, 0.12280273f, 0.44958496f, 0.41128540f, 0.08102417f,
387 -0.02801514f, -0.00326538f, -0.00579834f, -0.02758789f, 0.12045288f, 0.44778442f,
388 0.41351318f, 0.08306885f, -0.02804565f, -0.00338745f, -0.00561523f, -0.02770996f,
389 0.11813354f, 0.44598389f, 0.41571045f, 0.08511353f, -0.02810669f, -0.00350952f,
390 -0.00546265f, -0.02780151f, 0.11581421f, 0.44412231f, 0.41787720f, 0.08718872f,
391 -0.02813721f, -0.00363159f, -0.00531006f, -0.02786255f, 0.11349487f, 0.44226074f,
392 0.42004395f, 0.08929443f, -0.02816772f, -0.00375366f, -0.00515747f, -0.02795410f,
393 0.11120605f, 0.44036865f, 0.42218018f, 0.09140015f, -0.02816772f, -0.00387573f,
394 -0.00500488f, -0.02801514f, 0.10894775f, 0.43844604f, 0.42431641f, 0.09353638f,
395 -0.02819824f, -0.00402832f, -0.00485229f, -0.02807617f, 0.10668945f, 0.43649292f,
396 0.42639160f, 0.09570312f, -0.02819824f, -0.00415039f, -0.00469971f, -0.02810669f,
397 0.10446167f, 0.43453979f, 0.42846680f, 0.09786987f, -0.02819824f, -0.00427246f,
398 -0.00457764f, -0.02813721f, 0.10223389f, 0.43252563f, 0.43051147f, 0.10003662f,
399 -0.02816772f, -0.00442505f, -0.00442505f, -0.02816772f, 0.10003662f, 0.43051147f,
400 0.43252563f, 0.10223389f, -0.02813721f, -0.00457764f, -0.00427246f, -0.02819824f,
401 0.09786987f, 0.42846680f, 0.43453979f, 0.10446167f, -0.02810669f, -0.00469971f,
402 -0.00415039f, -0.02819824f, 0.09570312f, 0.42639160f, 0.43649292f, 0.10668945f,
403 -0.02807617f, -0.00485229f, -0.00402832f, -0.02819824f, 0.09353638f, 0.42431641f,
404 0.43844604f, 0.10894775f, -0.02801514f, -0.00500488f, -0.00387573f, -0.02816772f,
405 0.09140015f, 0.42218018f, 0.44036865f, 0.11120605f, -0.02795410f, -0.00515747f,
406 -0.00375366f, -0.02816772f, 0.08929443f, 0.42004395f, 0.44226074f, 0.11349487f,
407 -0.02786255f, -0.00531006f, -0.00363159f, -0.02813721f, 0.08718872f, 0.41787720f,
408 0.44412231f, 0.11581421f, -0.02780151f, -0.00546265f, -0.00350952f, -0.02810669f,
409 0.08511353f, 0.41571045f, 0.44598389f, 0.11813354f, -0.02770996f, -0.00561523f,
410 -0.00338745f, -0.02804565f, 0.08306885f, 0.41351318f, 0.44778442f, 0.12045288f,
411 -0.02758789f, -0.00579834f, -0.00326538f, -0.02801514f, 0.08102417f, 0.41128540f,
412 0.44958496f, 0.12280273f, -0.02746582f, -0.00595093f, -0.00314331f, -0.02795410f,
413 0.07901001f, 0.40902710f, 0.45135498f, 0.12518311f, -0.02734375f, -0.00610352f,
414 -0.00305176f, -0.02789307f, 0.07699585f, 0.40676880f, 0.45309448f, 0.12756348f,
415 -0.02722168f, -0.00628662f, -0.00292969f, -0.02780151f, 0.07504272f, 0.40447998f,
416 0.45480347f, 0.12997437f, -0.02706909f, -0.00643921f, -0.00280762f, -0.02774048f,
417 0.07305908f, 0.40216064f, 0.45648193f, 0.13238525f, -0.02691650f, -0.00662231f,
418 -0.00271606f, -0.02764893f, 0.07113647f, 0.39984131f, 0.45812988f, 0.13479614f,
419 -0.02673340f, -0.00680542f, -0.00259399f, -0.02755737f, 0.06918335f, 0.39749146f,
420 0.45974731f, 0.13726807f, -0.02655029f, -0.00698853f, -0.00250244f, -0.02743530f,
421 0.06729126f, 0.39511108f, 0.46133423f, 0.13970947f, -0.02636719f, -0.00717163f,
422 -0.00241089f, -0.02734375f, 0.06539917f, 0.39273071f, 0.46289062f, 0.14218140f,
423 -0.02615356f, -0.00735474f, -0.00231934f, -0.02722168f, 0.06353760f, 0.39031982f,
424 0.46444702f, 0.14468384f, -0.02593994f, -0.00753784f, -0.00222778f, -0.02709961f,
425 0.06170654f, 0.38790894f, 0.46594238f, 0.14718628f, -0.02569580f, -0.00772095f,
426 -0.00213623f, -0.02697754f, 0.05987549f, 0.38546753f, 0.46743774f, 0.14968872f,
427 -0.02545166f, -0.00790405f, -0.00204468f, -0.02685547f, 0.05804443f, 0.38302612f,
428 0.46887207f, 0.15222168f, -0.02520752f, -0.00808716f, -0.00195312f, -0.02670288f,
429 0.05627441f, 0.38055420f, 0.47027588f, 0.15475464f, -0.02493286f, -0.00830078f,
430 -0.00186157f, -0.02658081f, 0.05450439f, 0.37805176f, 0.47167969f, 0.15731812f,
431 -0.02462769f, -0.00848389f, -0.00177002f, -0.02642822f, 0.05276489f, 0.37554932f,
432 0.47302246f, 0.15988159f, -0.02435303f, -0.00869751f, -0.00170898f, -0.02627563f,
433 0.05102539f, 0.37304688f, 0.47436523f, 0.16244507f, -0.02401733f, -0.00888062f,
434 -0.00161743f, -0.02609253f, 0.04931641f, 0.37048340f, 0.47564697f, 0.16503906f,
435 -0.02371216f, -0.00909424f, -0.00152588f, -0.02593994f, 0.04763794f, 0.36795044f,
436 0.47689819f, 0.16763306f, -0.02337646f, -0.00930786f, -0.00146484f, -0.02578735f,
437 0.04595947f, 0.36538696f, 0.47814941f, 0.17025757f, -0.02301025f, -0.00952148f,
438 -0.00140381f, -0.02560425f, 0.04431152f, 0.36279297f, 0.47933960f, 0.17288208f,
439 -0.02264404f, -0.00970459f, -0.00131226f, -0.02542114f, 0.04269409f, 0.36019897f,
440 0.48049927f, 0.17550659f, -0.02227783f, -0.00991821f, -0.00125122f, -0.02523804f,
441 0.04107666f, 0.35760498f, 0.48162842f, 0.17816162f, -0.02188110f, -0.01013184f,
442 -0.00119019f, -0.02505493f, 0.03948975f, 0.35498047f, 0.48272705f, 0.18081665f,
443 -0.02145386f, -0.01034546f, -0.00112915f, -0.02487183f, 0.03793335f, 0.35235596f,
444 0.48379517f, 0.18350220f, -0.02102661f, -0.01058960f, -0.00106812f, -0.02468872f,
445 0.03637695f, 0.34970093f, 0.48483276f, 0.18615723f, -0.02059937f, -0.01080322f,
446 -0.00100708f, -0.02447510f, 0.03485107f, 0.34704590f, 0.48583984f, 0.18887329f,
447 -0.02014160f, -0.01101685f, -0.00094604f, -0.02429199f, 0.03335571f, 0.34439087f,
448 0.48681641f, 0.19155884f, -0.01968384f, -0.01123047f, -0.00088501f, -0.02407837f,
449 0.03186035f, 0.34170532f, 0.48776245f, 0.19427490f, -0.01919556f, -0.01147461f,
450 -0.00082397f, -0.02386475f, 0.03039551f, 0.33901978f, 0.48864746f, 0.19696045f,
451 -0.01867676f, -0.01168823f, -0.00079346f, -0.02365112f, 0.02896118f, 0.33633423f,
452 0.48953247f, 0.19970703f, -0.01818848f, -0.01193237f, -0.00073242f, -0.02343750f,
453 0.02752686f, 0.33361816f, 0.49035645f, 0.20242310f, -0.01763916f, -0.01214600f,
454 -0.00067139f, -0.02322388f, 0.02612305f, 0.33090210f, 0.49118042f, 0.20516968f,
455 -0.01708984f, -0.01239014f, -0.00064087f, -0.02301025f, 0.02471924f, 0.32818604f,
456 0.49194336f, 0.20791626f, -0.01654053f, -0.01263428f, -0.00057983f, -0.02279663f,
457 0.02337646f, 0.32543945f, 0.49267578f, 0.21066284f, -0.01596069f, -0.01284790f,
458 -0.00054932f, -0.02255249f, 0.02203369f, 0.32269287f, 0.49337769f, 0.21343994f,
459 -0.01535034f, -0.01309204f, -0.00048828f, -0.02233887f, 0.02069092f, 0.31994629f,
460 0.49404907f, 0.21618652f, -0.01473999f, -0.01333618f, -0.00045776f, -0.02209473f,
461 0.01937866f, 0.31719971f, 0.49465942f, 0.21896362f, -0.01409912f, -0.01358032f,
462 -0.00042725f, -0.02188110f, 0.01809692f, 0.31442261f, 0.49526978f, 0.22174072f,
463 -0.01345825f, -0.01379395f, -0.00039673f, -0.02163696f, 0.01684570f, 0.31164551f,
464 0.49581909f, 0.22451782f, -0.01278687f, -0.01403809f, -0.00033569f, -0.02142334f,
465 0.01559448f, 0.30886841f, 0.49636841f, 0.22732544f, -0.01211548f, -0.01428223f,
466 -0.00030518f, -0.02117920f, 0.01437378f, 0.30609131f, 0.49685669f, 0.23010254f,
467 -0.01141357f, -0.01452637f, -0.00027466f, -0.02093506f, 0.01318359f, 0.30331421f,
468 0.49731445f, 0.23291016f, -0.01071167f, -0.01477051f, -0.00024414f, -0.02069092f,
469 0.01199341f, 0.30050659f, 0.49774170f, 0.23571777f, -0.00997925f, -0.01501465f,
470 -0.00021362f, -0.02044678f, 0.01083374f, 0.29772949f, 0.49810791f, 0.23852539f,
471 -0.00921631f, -0.01525879f, -0.00018311f, -0.02020264f, 0.00967407f, 0.29492188f,
472 0.49847412f, 0.24133301f, -0.00845337f, -0.01550293f, -0.00015259f, -0.01995850f,
473 0.00854492f, 0.29211426f, 0.49877930f, 0.24414062f, -0.00765991f, -0.01574707f,
474 -0.00015259f, -0.01971436f, 0.00744629f, 0.28930664f, 0.49908447f, 0.24697876f,
475 -0.00686646f, -0.01602173f, -0.00012207f, -0.01947021f, 0.00634766f, 0.28646851f,
476 0.49932861f, 0.24978638f, -0.00604248f, -0.01626587f, -0.00009155f, -0.01922607f,
477 0.00527954f, 0.28366089f, 0.49954224f, 0.25259399f, -0.00518799f, -0.01651001f,
478 -0.00006104f, -0.01898193f, 0.00424194f, 0.28085327f, 0.49972534f, 0.25543213f,
479 -0.00433350f, -0.01675415f, -0.00006104f, -0.01873779f, 0.00320435f, 0.27801514f,
480 0.49984741f, 0.25823975f, -0.00344849f, -0.01699829f, -0.00003052f, -0.01849365f,
481 0.00219727f, 0.27520752f, 0.49996948f, 0.26107788f, -0.00256348f, -0.01724243f,
482 -0.00003052f, -0.01824951f, 0.00122070f, 0.27236938f, 0.50003052f, 0.26388550f,
483 -0.00164795f, -0.01748657f, 0.00000000f, -0.01800537f, 0.00024414f, 0.26956177f,
484 0.50006104f, 0.26672363f, -0.00070190f, -0.01776123f,
485 };
486
487 static constexpr std::array<f32, 1024> lut1 = {
488 0.01275635f, -0.07745361f, 0.18670654f, 0.75119019f, 0.19219971f, -0.07821655f,
489 0.01272583f, 0.00000000f, 0.01281738f, -0.07666016f, 0.18124390f, 0.75106812f,
490 0.19772339f, -0.07897949f, 0.01266479f, 0.00003052f, 0.01284790f, -0.07583618f,
491 0.17581177f, 0.75088501f, 0.20330811f, -0.07971191f, 0.01257324f, 0.00006104f,
492 0.01287842f, -0.07501221f, 0.17044067f, 0.75057983f, 0.20892334f, -0.08041382f,
493 0.01248169f, 0.00009155f, 0.01290894f, -0.07415771f, 0.16510010f, 0.75018311f,
494 0.21453857f, -0.08111572f, 0.01239014f, 0.00012207f, 0.01290894f, -0.07330322f,
495 0.15979004f, 0.74966431f, 0.22021484f, -0.08178711f, 0.01229858f, 0.00015259f,
496 0.01290894f, -0.07241821f, 0.15454102f, 0.74908447f, 0.22592163f, -0.08242798f,
497 0.01217651f, 0.00018311f, 0.01290894f, -0.07150269f, 0.14932251f, 0.74838257f,
498 0.23165894f, -0.08303833f, 0.01205444f, 0.00021362f, 0.01290894f, -0.07058716f,
499 0.14416504f, 0.74755859f, 0.23742676f, -0.08364868f, 0.01193237f, 0.00024414f,
500 0.01287842f, -0.06967163f, 0.13903809f, 0.74667358f, 0.24322510f, -0.08419800f,
501 0.01177979f, 0.00027466f, 0.01284790f, -0.06872559f, 0.13397217f, 0.74566650f,
502 0.24905396f, -0.08474731f, 0.01162720f, 0.00033569f, 0.01281738f, -0.06777954f,
503 0.12893677f, 0.74456787f, 0.25491333f, -0.08526611f, 0.01147461f, 0.00036621f,
504 0.01278687f, -0.06683350f, 0.12396240f, 0.74337769f, 0.26077271f, -0.08575439f,
505 0.01129150f, 0.00042725f, 0.01275635f, -0.06585693f, 0.11901855f, 0.74206543f,
506 0.26669312f, -0.08621216f, 0.01110840f, 0.00045776f, 0.01269531f, -0.06488037f,
507 0.11413574f, 0.74069214f, 0.27261353f, -0.08663940f, 0.01092529f, 0.00051880f,
508 0.01263428f, -0.06387329f, 0.10931396f, 0.73919678f, 0.27853394f, -0.08700562f,
509 0.01071167f, 0.00057983f, 0.01257324f, -0.06286621f, 0.10452271f, 0.73760986f,
510 0.28451538f, -0.08737183f, 0.01049805f, 0.00064087f, 0.01251221f, -0.06185913f,
511 0.09979248f, 0.73593140f, 0.29049683f, -0.08770752f, 0.01025391f, 0.00067139f,
512 0.01242065f, -0.06082153f, 0.09512329f, 0.73413086f, 0.29647827f, -0.08801270f,
513 0.01000977f, 0.00073242f, 0.01232910f, -0.05981445f, 0.09051514f, 0.73226929f,
514 0.30249023f, -0.08828735f, 0.00973511f, 0.00079346f, 0.01226807f, -0.05877686f,
515 0.08593750f, 0.73028564f, 0.30853271f, -0.08850098f, 0.00949097f, 0.00088501f,
516 0.01214600f, -0.05773926f, 0.08142090f, 0.72824097f, 0.31457520f, -0.08871460f,
517 0.00918579f, 0.00094604f, 0.01205444f, -0.05670166f, 0.07696533f, 0.72607422f,
518 0.32061768f, -0.08886719f, 0.00891113f, 0.00100708f, 0.01196289f, -0.05563354f,
519 0.07257080f, 0.72381592f, 0.32669067f, -0.08898926f, 0.00860596f, 0.00106812f,
520 0.01187134f, -0.05459595f, 0.06820679f, 0.72146606f, 0.33276367f, -0.08908081f,
521 0.00827026f, 0.00115967f, 0.01174927f, -0.05352783f, 0.06393433f, 0.71902466f,
522 0.33883667f, -0.08911133f, 0.00796509f, 0.00122070f, 0.01162720f, -0.05245972f,
523 0.05969238f, 0.71649170f, 0.34494019f, -0.08914185f, 0.00759888f, 0.00131226f,
524 0.01150513f, -0.05139160f, 0.05551147f, 0.71389771f, 0.35101318f, -0.08911133f,
525 0.00726318f, 0.00137329f, 0.01138306f, -0.05032349f, 0.05139160f, 0.71118164f,
526 0.35711670f, -0.08901978f, 0.00686646f, 0.00146484f, 0.01126099f, -0.04928589f,
527 0.04733276f, 0.70837402f, 0.36322021f, -0.08892822f, 0.00650024f, 0.00155640f,
528 0.01113892f, -0.04821777f, 0.04333496f, 0.70550537f, 0.36932373f, -0.08877563f,
529 0.00610352f, 0.00164795f, 0.01101685f, -0.04714966f, 0.03939819f, 0.70251465f,
530 0.37542725f, -0.08856201f, 0.00567627f, 0.00173950f, 0.01086426f, -0.04608154f,
531 0.03549194f, 0.69946289f, 0.38153076f, -0.08834839f, 0.00527954f, 0.00183105f,
532 0.01074219f, -0.04501343f, 0.03167725f, 0.69631958f, 0.38763428f, -0.08804321f,
533 0.00482178f, 0.00192261f, 0.01058960f, -0.04394531f, 0.02792358f, 0.69308472f,
534 0.39370728f, -0.08773804f, 0.00436401f, 0.00201416f, 0.01043701f, -0.04287720f,
535 0.02420044f, 0.68975830f, 0.39981079f, -0.08737183f, 0.00390625f, 0.00210571f,
536 0.01031494f, -0.04180908f, 0.02056885f, 0.68637085f, 0.40588379f, -0.08694458f,
537 0.00344849f, 0.00222778f, 0.01016235f, -0.04074097f, 0.01699829f, 0.68289185f,
538 0.41195679f, -0.08648682f, 0.00296021f, 0.00231934f, 0.01000977f, -0.03970337f,
539 0.01345825f, 0.67932129f, 0.41802979f, -0.08596802f, 0.00244141f, 0.00244141f,
540 0.00985718f, -0.03863525f, 0.01000977f, 0.67568970f, 0.42407227f, -0.08541870f,
541 0.00192261f, 0.00253296f, 0.00970459f, -0.03759766f, 0.00662231f, 0.67196655f,
542 0.43011475f, -0.08480835f, 0.00140381f, 0.00265503f, 0.00955200f, -0.03652954f,
543 0.00326538f, 0.66815186f, 0.43612671f, -0.08416748f, 0.00085449f, 0.00277710f,
544 0.00936890f, -0.03549194f, 0.00000000f, 0.66427612f, 0.44213867f, -0.08346558f,
545 0.00027466f, 0.00289917f, 0.00921631f, -0.03445435f, -0.00320435f, 0.66030884f,
546 0.44812012f, -0.08270264f, -0.00027466f, 0.00299072f, 0.00906372f, -0.03344727f,
547 -0.00634766f, 0.65631104f, 0.45407104f, -0.08190918f, -0.00088501f, 0.00311279f,
548 0.00891113f, -0.03240967f, -0.00946045f, 0.65219116f, 0.46002197f, -0.08105469f,
549 -0.00146484f, 0.00323486f, 0.00872803f, -0.03140259f, -0.01248169f, 0.64801025f,
550 0.46594238f, -0.08013916f, -0.00210571f, 0.00338745f, 0.00857544f, -0.03039551f,
551 -0.01544189f, 0.64376831f, 0.47183228f, -0.07919312f, -0.00271606f, 0.00350952f,
552 0.00842285f, -0.02938843f, -0.01834106f, 0.63946533f, 0.47772217f, -0.07818604f,
553 -0.00335693f, 0.00363159f, 0.00823975f, -0.02838135f, -0.02117920f, 0.63507080f,
554 0.48358154f, -0.07711792f, -0.00402832f, 0.00375366f, 0.00808716f, -0.02740479f,
555 -0.02395630f, 0.63061523f, 0.48937988f, -0.07598877f, -0.00469971f, 0.00390625f,
556 0.00793457f, -0.02642822f, -0.02667236f, 0.62609863f, 0.49517822f, -0.07482910f,
557 -0.00537109f, 0.00402832f, 0.00775146f, -0.02545166f, -0.02932739f, 0.62152100f,
558 0.50094604f, -0.07357788f, -0.00607300f, 0.00418091f, 0.00759888f, -0.02450562f,
559 -0.03192139f, 0.61685181f, 0.50665283f, -0.07229614f, -0.00677490f, 0.00430298f,
560 0.00741577f, -0.02352905f, -0.03445435f, 0.61215210f, 0.51235962f, -0.07098389f,
561 -0.00750732f, 0.00445557f, 0.00726318f, -0.02258301f, -0.03689575f, 0.60736084f,
562 0.51800537f, -0.06958008f, -0.00823975f, 0.00460815f, 0.00711060f, -0.02166748f,
563 -0.03930664f, 0.60253906f, 0.52362061f, -0.06811523f, -0.00897217f, 0.00476074f,
564 0.00692749f, -0.02075195f, -0.04165649f, 0.59762573f, 0.52920532f, -0.06661987f,
565 -0.00973511f, 0.00488281f, 0.00677490f, -0.01983643f, -0.04394531f, 0.59268188f,
566 0.53475952f, -0.06506348f, -0.01052856f, 0.00503540f, 0.00662231f, -0.01892090f,
567 -0.04617310f, 0.58767700f, 0.54025269f, -0.06344604f, -0.01129150f, 0.00518799f,
568 0.00643921f, -0.01803589f, -0.04830933f, 0.58261108f, 0.54571533f, -0.06173706f,
569 -0.01208496f, 0.00534058f, 0.00628662f, -0.01715088f, -0.05041504f, 0.57748413f,
570 0.55111694f, -0.05999756f, -0.01290894f, 0.00549316f, 0.00613403f, -0.01626587f,
571 -0.05245972f, 0.57232666f, 0.55648804f, -0.05819702f, -0.01373291f, 0.00564575f,
572 0.00598145f, -0.01541138f, -0.05444336f, 0.56707764f, 0.56182861f, -0.05636597f,
573 -0.01455688f, 0.00582886f, 0.00582886f, -0.01455688f, -0.05636597f, 0.56182861f,
574 0.56707764f, -0.05444336f, -0.01541138f, 0.00598145f, 0.00564575f, -0.01373291f,
575 -0.05819702f, 0.55648804f, 0.57232666f, -0.05245972f, -0.01626587f, 0.00613403f,
576 0.00549316f, -0.01290894f, -0.05999756f, 0.55111694f, 0.57748413f, -0.05041504f,
577 -0.01715088f, 0.00628662f, 0.00534058f, -0.01208496f, -0.06173706f, 0.54571533f,
578 0.58261108f, -0.04830933f, -0.01803589f, 0.00643921f, 0.00518799f, -0.01129150f,
579 -0.06344604f, 0.54025269f, 0.58767700f, -0.04617310f, -0.01892090f, 0.00662231f,
580 0.00503540f, -0.01052856f, -0.06506348f, 0.53475952f, 0.59268188f, -0.04394531f,
581 -0.01983643f, 0.00677490f, 0.00488281f, -0.00973511f, -0.06661987f, 0.52920532f,
582 0.59762573f, -0.04165649f, -0.02075195f, 0.00692749f, 0.00476074f, -0.00897217f,
583 -0.06811523f, 0.52362061f, 0.60253906f, -0.03930664f, -0.02166748f, 0.00711060f,
584 0.00460815f, -0.00823975f, -0.06958008f, 0.51800537f, 0.60736084f, -0.03689575f,
585 -0.02258301f, 0.00726318f, 0.00445557f, -0.00750732f, -0.07098389f, 0.51235962f,
586 0.61215210f, -0.03445435f, -0.02352905f, 0.00741577f, 0.00430298f, -0.00677490f,
587 -0.07229614f, 0.50665283f, 0.61685181f, -0.03192139f, -0.02450562f, 0.00759888f,
588 0.00418091f, -0.00607300f, -0.07357788f, 0.50094604f, 0.62152100f, -0.02932739f,
589 -0.02545166f, 0.00775146f, 0.00402832f, -0.00537109f, -0.07482910f, 0.49517822f,
590 0.62609863f, -0.02667236f, -0.02642822f, 0.00793457f, 0.00390625f, -0.00469971f,
591 -0.07598877f, 0.48937988f, 0.63061523f, -0.02395630f, -0.02740479f, 0.00808716f,
592 0.00375366f, -0.00402832f, -0.07711792f, 0.48358154f, 0.63507080f, -0.02117920f,
593 -0.02838135f, 0.00823975f, 0.00363159f, -0.00335693f, -0.07818604f, 0.47772217f,
594 0.63946533f, -0.01834106f, -0.02938843f, 0.00842285f, 0.00350952f, -0.00271606f,
595 -0.07919312f, 0.47183228f, 0.64376831f, -0.01544189f, -0.03039551f, 0.00857544f,
596 0.00338745f, -0.00210571f, -0.08013916f, 0.46594238f, 0.64801025f, -0.01248169f,
597 -0.03140259f, 0.00872803f, 0.00323486f, -0.00146484f, -0.08105469f, 0.46002197f,
598 0.65219116f, -0.00946045f, -0.03240967f, 0.00891113f, 0.00311279f, -0.00088501f,
599 -0.08190918f, 0.45407104f, 0.65631104f, -0.00634766f, -0.03344727f, 0.00906372f,
600 0.00299072f, -0.00027466f, -0.08270264f, 0.44812012f, 0.66030884f, -0.00320435f,
601 -0.03445435f, 0.00921631f, 0.00289917f, 0.00027466f, -0.08346558f, 0.44213867f,
602 0.66427612f, 0.00000000f, -0.03549194f, 0.00936890f, 0.00277710f, 0.00085449f,
603 -0.08416748f, 0.43612671f, 0.66815186f, 0.00326538f, -0.03652954f, 0.00955200f,
604 0.00265503f, 0.00140381f, -0.08480835f, 0.43011475f, 0.67196655f, 0.00662231f,
605 -0.03759766f, 0.00970459f, 0.00253296f, 0.00192261f, -0.08541870f, 0.42407227f,
606 0.67568970f, 0.01000977f, -0.03863525f, 0.00985718f, 0.00244141f, 0.00244141f,
607 -0.08596802f, 0.41802979f, 0.67932129f, 0.01345825f, -0.03970337f, 0.01000977f,
608 0.00231934f, 0.00296021f, -0.08648682f, 0.41195679f, 0.68289185f, 0.01699829f,
609 -0.04074097f, 0.01016235f, 0.00222778f, 0.00344849f, -0.08694458f, 0.40588379f,
610 0.68637085f, 0.02056885f, -0.04180908f, 0.01031494f, 0.00210571f, 0.00390625f,
611 -0.08737183f, 0.39981079f, 0.68975830f, 0.02420044f, -0.04287720f, 0.01043701f,
612 0.00201416f, 0.00436401f, -0.08773804f, 0.39370728f, 0.69308472f, 0.02792358f,
613 -0.04394531f, 0.01058960f, 0.00192261f, 0.00482178f, -0.08804321f, 0.38763428f,
614 0.69631958f, 0.03167725f, -0.04501343f, 0.01074219f, 0.00183105f, 0.00527954f,
615 -0.08834839f, 0.38153076f, 0.69946289f, 0.03549194f, -0.04608154f, 0.01086426f,
616 0.00173950f, 0.00567627f, -0.08856201f, 0.37542725f, 0.70251465f, 0.03939819f,
617 -0.04714966f, 0.01101685f, 0.00164795f, 0.00610352f, -0.08877563f, 0.36932373f,
618 0.70550537f, 0.04333496f, -0.04821777f, 0.01113892f, 0.00155640f, 0.00650024f,
619 -0.08892822f, 0.36322021f, 0.70837402f, 0.04733276f, -0.04928589f, 0.01126099f,
620 0.00146484f, 0.00686646f, -0.08901978f, 0.35711670f, 0.71118164f, 0.05139160f,
621 -0.05032349f, 0.01138306f, 0.00137329f, 0.00726318f, -0.08911133f, 0.35101318f,
622 0.71389771f, 0.05551147f, -0.05139160f, 0.01150513f, 0.00131226f, 0.00759888f,
623 -0.08914185f, 0.34494019f, 0.71649170f, 0.05969238f, -0.05245972f, 0.01162720f,
624 0.00122070f, 0.00796509f, -0.08911133f, 0.33883667f, 0.71902466f, 0.06393433f,
625 -0.05352783f, 0.01174927f, 0.00115967f, 0.00827026f, -0.08908081f, 0.33276367f,
626 0.72146606f, 0.06820679f, -0.05459595f, 0.01187134f, 0.00106812f, 0.00860596f,
627 -0.08898926f, 0.32669067f, 0.72381592f, 0.07257080f, -0.05563354f, 0.01196289f,
628 0.00100708f, 0.00891113f, -0.08886719f, 0.32061768f, 0.72607422f, 0.07696533f,
629 -0.05670166f, 0.01205444f, 0.00094604f, 0.00918579f, -0.08871460f, 0.31457520f,
630 0.72824097f, 0.08142090f, -0.05773926f, 0.01214600f, 0.00088501f, 0.00949097f,
631 -0.08850098f, 0.30853271f, 0.73028564f, 0.08593750f, -0.05877686f, 0.01226807f,
632 0.00079346f, 0.00973511f, -0.08828735f, 0.30249023f, 0.73226929f, 0.09051514f,
633 -0.05981445f, 0.01232910f, 0.00073242f, 0.01000977f, -0.08801270f, 0.29647827f,
634 0.73413086f, 0.09512329f, -0.06082153f, 0.01242065f, 0.00067139f, 0.01025391f,
635 -0.08770752f, 0.29049683f, 0.73593140f, 0.09979248f, -0.06185913f, 0.01251221f,
636 0.00064087f, 0.01049805f, -0.08737183f, 0.28451538f, 0.73760986f, 0.10452271f,
637 -0.06286621f, 0.01257324f, 0.00057983f, 0.01071167f, -0.08700562f, 0.27853394f,
638 0.73919678f, 0.10931396f, -0.06387329f, 0.01263428f, 0.00051880f, 0.01092529f,
639 -0.08663940f, 0.27261353f, 0.74069214f, 0.11413574f, -0.06488037f, 0.01269531f,
640 0.00045776f, 0.01110840f, -0.08621216f, 0.26669312f, 0.74206543f, 0.11901855f,
641 -0.06585693f, 0.01275635f, 0.00042725f, 0.01129150f, -0.08575439f, 0.26077271f,
642 0.74337769f, 0.12396240f, -0.06683350f, 0.01278687f, 0.00036621f, 0.01147461f,
643 -0.08526611f, 0.25491333f, 0.74456787f, 0.12893677f, -0.06777954f, 0.01281738f,
644 0.00033569f, 0.01162720f, -0.08474731f, 0.24905396f, 0.74566650f, 0.13397217f,
645 -0.06872559f, 0.01284790f, 0.00027466f, 0.01177979f, -0.08419800f, 0.24322510f,
646 0.74667358f, 0.13903809f, -0.06967163f, 0.01287842f, 0.00024414f, 0.01193237f,
647 -0.08364868f, 0.23742676f, 0.74755859f, 0.14416504f, -0.07058716f, 0.01290894f,
648 0.00021362f, 0.01205444f, -0.08303833f, 0.23165894f, 0.74838257f, 0.14932251f,
649 -0.07150269f, 0.01290894f, 0.00018311f, 0.01217651f, -0.08242798f, 0.22592163f,
650 0.74908447f, 0.15454102f, -0.07241821f, 0.01290894f, 0.00015259f, 0.01229858f,
651 -0.08178711f, 0.22021484f, 0.74966431f, 0.15979004f, -0.07330322f, 0.01290894f,
652 0.00012207f, 0.01239014f, -0.08111572f, 0.21453857f, 0.75018311f, 0.16510010f,
653 -0.07415771f, 0.01290894f, 0.00009155f, 0.01248169f, -0.08041382f, 0.20892334f,
654 0.75057983f, 0.17044067f, -0.07501221f, 0.01287842f, 0.00006104f, 0.01257324f,
655 -0.07971191f, 0.20330811f, 0.75088501f, 0.17581177f, -0.07583618f, 0.01284790f,
656 0.00003052f, 0.01266479f, -0.07897949f, 0.19772339f, 0.75106812f, 0.18124390f,
657 -0.07666016f, 0.01281738f, 0.00000000f, 0.01272583f, -0.07821655f, 0.19219971f,
658 0.75119019f, 0.18670654f, -0.07745361f, 0.01275635f,
659 };
660
661 static constexpr std::array<f32, 1024> lut2 = {
662 -0.00036621f, 0.00143433f, -0.00408936f, 0.99996948f, 0.00247192f, -0.00048828f,
663 0.00006104f, 0.00000000f, -0.00079346f, 0.00329590f, -0.01052856f, 0.99975586f,
664 0.00918579f, -0.00241089f, 0.00051880f, -0.00003052f, -0.00122070f, 0.00512695f,
665 -0.01684570f, 0.99929810f, 0.01605225f, -0.00439453f, 0.00097656f, -0.00006104f,
666 -0.00161743f, 0.00689697f, -0.02297974f, 0.99862671f, 0.02304077f, -0.00640869f,
667 0.00143433f, -0.00009155f, -0.00201416f, 0.00866699f, -0.02899170f, 0.99774170f,
668 0.03018188f, -0.00845337f, 0.00192261f, -0.00015259f, -0.00238037f, 0.01037598f,
669 -0.03488159f, 0.99664307f, 0.03741455f, -0.01055908f, 0.00241089f, -0.00018311f,
670 -0.00274658f, 0.01202393f, -0.04061890f, 0.99533081f, 0.04483032f, -0.01266479f,
671 0.00292969f, -0.00024414f, -0.00308228f, 0.01364136f, -0.04620361f, 0.99377441f,
672 0.05233765f, -0.01483154f, 0.00344849f, -0.00027466f, -0.00341797f, 0.01522827f,
673 -0.05163574f, 0.99200439f, 0.05999756f, -0.01699829f, 0.00396729f, -0.00033569f,
674 -0.00375366f, 0.01678467f, -0.05691528f, 0.99002075f, 0.06777954f, -0.01922607f,
675 0.00451660f, -0.00039673f, -0.00405884f, 0.01828003f, -0.06207275f, 0.98782349f,
676 0.07568359f, -0.02145386f, 0.00506592f, -0.00042725f, -0.00436401f, 0.01971436f,
677 -0.06707764f, 0.98541260f, 0.08370972f, -0.02374268f, 0.00564575f, -0.00048828f,
678 -0.00463867f, 0.02114868f, -0.07192993f, 0.98278809f, 0.09185791f, -0.02603149f,
679 0.00622559f, -0.00054932f, -0.00494385f, 0.02252197f, -0.07666016f, 0.97991943f,
680 0.10012817f, -0.02835083f, 0.00680542f, -0.00061035f, -0.00518799f, 0.02383423f,
681 -0.08123779f, 0.97686768f, 0.10848999f, -0.03073120f, 0.00738525f, -0.00070190f,
682 -0.00543213f, 0.02511597f, -0.08566284f, 0.97360229f, 0.11700439f, -0.03308105f,
683 0.00799561f, -0.00076294f, -0.00567627f, 0.02636719f, -0.08993530f, 0.97012329f,
684 0.12561035f, -0.03549194f, 0.00860596f, -0.00082397f, -0.00592041f, 0.02755737f,
685 -0.09405518f, 0.96643066f, 0.13436890f, -0.03790283f, 0.00924683f, -0.00091553f,
686 -0.00613403f, 0.02868652f, -0.09805298f, 0.96252441f, 0.14318848f, -0.04034424f,
687 0.00985718f, -0.00097656f, -0.00631714f, 0.02981567f, -0.10189819f, 0.95843506f,
688 0.15213013f, -0.04281616f, 0.01049805f, -0.00106812f, -0.00653076f, 0.03085327f,
689 -0.10559082f, 0.95413208f, 0.16119385f, -0.04528809f, 0.01113892f, -0.00112915f,
690 -0.00671387f, 0.03189087f, -0.10916138f, 0.94961548f, 0.17034912f, -0.04779053f,
691 0.01181030f, -0.00122070f, -0.00686646f, 0.03286743f, -0.11254883f, 0.94491577f,
692 0.17959595f, -0.05029297f, 0.01248169f, -0.00131226f, -0.00701904f, 0.03378296f,
693 -0.11584473f, 0.94000244f, 0.18893433f, -0.05279541f, 0.01315308f, -0.00140381f,
694 -0.00717163f, 0.03466797f, -0.11895752f, 0.93490601f, 0.19839478f, -0.05532837f,
695 0.01382446f, -0.00149536f, -0.00732422f, 0.03552246f, -0.12194824f, 0.92962646f,
696 0.20791626f, -0.05786133f, 0.01449585f, -0.00158691f, -0.00744629f, 0.03631592f,
697 -0.12478638f, 0.92413330f, 0.21752930f, -0.06042480f, 0.01519775f, -0.00167847f,
698 -0.00753784f, 0.03707886f, -0.12750244f, 0.91848755f, 0.22723389f, -0.06298828f,
699 0.01586914f, -0.00177002f, -0.00765991f, 0.03781128f, -0.13006592f, 0.91262817f,
700 0.23703003f, -0.06555176f, 0.01657104f, -0.00189209f, -0.00775146f, 0.03848267f,
701 -0.13250732f, 0.90658569f, 0.24691772f, -0.06808472f, 0.01727295f, -0.00198364f,
702 -0.00784302f, 0.03909302f, -0.13479614f, 0.90036011f, 0.25683594f, -0.07064819f,
703 0.01797485f, -0.00210571f, -0.00790405f, 0.03970337f, -0.13696289f, 0.89395142f,
704 0.26687622f, -0.07321167f, 0.01870728f, -0.00219727f, -0.00796509f, 0.04025269f,
705 -0.13900757f, 0.88739014f, 0.27694702f, -0.07577515f, 0.01940918f, -0.00231934f,
706 -0.00802612f, 0.04077148f, -0.14089966f, 0.88064575f, 0.28710938f, -0.07833862f,
707 0.02011108f, -0.00244141f, -0.00808716f, 0.04122925f, -0.14263916f, 0.87374878f,
708 0.29733276f, -0.08090210f, 0.02084351f, -0.00253296f, -0.00811768f, 0.04165649f,
709 -0.14428711f, 0.86666870f, 0.30761719f, -0.08343506f, 0.02154541f, -0.00265503f,
710 -0.00814819f, 0.04205322f, -0.14578247f, 0.85940552f, 0.31793213f, -0.08596802f,
711 0.02227783f, -0.00277710f, -0.00814819f, 0.04238892f, -0.14715576f, 0.85202026f,
712 0.32833862f, -0.08847046f, 0.02297974f, -0.00289917f, -0.00817871f, 0.04272461f,
713 -0.14840698f, 0.84445190f, 0.33874512f, -0.09097290f, 0.02371216f, -0.00302124f,
714 -0.00817871f, 0.04299927f, -0.14953613f, 0.83673096f, 0.34924316f, -0.09347534f,
715 0.02441406f, -0.00314331f, -0.00817871f, 0.04321289f, -0.15054321f, 0.82888794f,
716 0.35977173f, -0.09594727f, 0.02514648f, -0.00326538f, -0.00814819f, 0.04342651f,
717 -0.15142822f, 0.82086182f, 0.37033081f, -0.09838867f, 0.02584839f, -0.00341797f,
718 -0.00814819f, 0.04357910f, -0.15219116f, 0.81271362f, 0.38092041f, -0.10079956f,
719 0.02655029f, -0.00354004f, -0.00811768f, 0.04373169f, -0.15283203f, 0.80441284f,
720 0.39154053f, -0.10321045f, 0.02725220f, -0.00366211f, -0.00808716f, 0.04382324f,
721 -0.15338135f, 0.79598999f, 0.40219116f, -0.10559082f, 0.02795410f, -0.00381470f,
722 -0.00805664f, 0.04388428f, -0.15377808f, 0.78741455f, 0.41287231f, -0.10794067f,
723 0.02865601f, -0.00393677f, -0.00799561f, 0.04388428f, -0.15408325f, 0.77871704f,
724 0.42358398f, -0.11026001f, 0.02935791f, -0.00405884f, -0.00793457f, 0.04388428f,
725 -0.15426636f, 0.76989746f, 0.43429565f, -0.11251831f, 0.03002930f, -0.00421143f,
726 -0.00787354f, 0.04385376f, -0.15435791f, 0.76095581f, 0.44500732f, -0.11477661f,
727 0.03070068f, -0.00433350f, -0.00781250f, 0.04379272f, -0.15435791f, 0.75192261f,
728 0.45574951f, -0.11697388f, 0.03137207f, -0.00448608f, -0.00775146f, 0.04367065f,
729 -0.15420532f, 0.74273682f, 0.46649170f, -0.11914062f, 0.03201294f, -0.00460815f,
730 -0.00769043f, 0.04354858f, -0.15399170f, 0.73345947f, 0.47723389f, -0.12127686f,
731 0.03268433f, -0.00473022f, -0.00759888f, 0.04339600f, -0.15365601f, 0.72406006f,
732 0.48794556f, -0.12335205f, 0.03329468f, -0.00488281f, -0.00750732f, 0.04321289f,
733 -0.15322876f, 0.71456909f, 0.49868774f, -0.12539673f, 0.03393555f, -0.00500488f,
734 -0.00741577f, 0.04296875f, -0.15270996f, 0.70498657f, 0.50936890f, -0.12738037f,
735 0.03454590f, -0.00515747f, -0.00732422f, 0.04272461f, -0.15209961f, 0.69528198f,
736 0.52008057f, -0.12930298f, 0.03515625f, -0.00527954f, -0.00723267f, 0.04248047f,
737 -0.15136719f, 0.68551636f, 0.53076172f, -0.13119507f, 0.03573608f, -0.00543213f,
738 -0.00714111f, 0.04217529f, -0.15057373f, 0.67565918f, 0.54138184f, -0.13299561f,
739 0.03631592f, -0.00555420f, -0.00701904f, 0.04183960f, -0.14968872f, 0.66571045f,
740 0.55200195f, -0.13476562f, 0.03689575f, -0.00567627f, -0.00692749f, 0.04150391f,
741 -0.14871216f, 0.65567017f, 0.56259155f, -0.13647461f, 0.03741455f, -0.00582886f,
742 -0.00680542f, 0.04113770f, -0.14767456f, 0.64556885f, 0.57315063f, -0.13812256f,
743 0.03796387f, -0.00595093f, -0.00668335f, 0.04074097f, -0.14651489f, 0.63540649f,
744 0.58364868f, -0.13970947f, 0.03845215f, -0.00607300f, -0.00656128f, 0.04031372f,
745 -0.14529419f, 0.62518311f, 0.59411621f, -0.14120483f, 0.03897095f, -0.00619507f,
746 -0.00643921f, 0.03988647f, -0.14401245f, 0.61486816f, 0.60452271f, -0.14263916f,
747 0.03942871f, -0.00631714f, -0.00631714f, 0.03942871f, -0.14263916f, 0.60452271f,
748 0.61486816f, -0.14401245f, 0.03988647f, -0.00643921f, -0.00619507f, 0.03897095f,
749 -0.14120483f, 0.59411621f, 0.62518311f, -0.14529419f, 0.04031372f, -0.00656128f,
750 -0.00607300f, 0.03845215f, -0.13970947f, 0.58364868f, 0.63540649f, -0.14651489f,
751 0.04074097f, -0.00668335f, -0.00595093f, 0.03796387f, -0.13812256f, 0.57315063f,
752 0.64556885f, -0.14767456f, 0.04113770f, -0.00680542f, -0.00582886f, 0.03741455f,
753 -0.13647461f, 0.56259155f, 0.65567017f, -0.14871216f, 0.04150391f, -0.00692749f,
754 -0.00567627f, 0.03689575f, -0.13476562f, 0.55200195f, 0.66571045f, -0.14968872f,
755 0.04183960f, -0.00701904f, -0.00555420f, 0.03631592f, -0.13299561f, 0.54138184f,
756 0.67565918f, -0.15057373f, 0.04217529f, -0.00714111f, -0.00543213f, 0.03573608f,
757 -0.13119507f, 0.53076172f, 0.68551636f, -0.15136719f, 0.04248047f, -0.00723267f,
758 -0.00527954f, 0.03515625f, -0.12930298f, 0.52008057f, 0.69528198f, -0.15209961f,
759 0.04272461f, -0.00732422f, -0.00515747f, 0.03454590f, -0.12738037f, 0.50936890f,
760 0.70498657f, -0.15270996f, 0.04296875f, -0.00741577f, -0.00500488f, 0.03393555f,
761 -0.12539673f, 0.49868774f, 0.71456909f, -0.15322876f, 0.04321289f, -0.00750732f,
762 -0.00488281f, 0.03329468f, -0.12335205f, 0.48794556f, 0.72406006f, -0.15365601f,
763 0.04339600f, -0.00759888f, -0.00473022f, 0.03268433f, -0.12127686f, 0.47723389f,
764 0.73345947f, -0.15399170f, 0.04354858f, -0.00769043f, -0.00460815f, 0.03201294f,
765 -0.11914062f, 0.46649170f, 0.74273682f, -0.15420532f, 0.04367065f, -0.00775146f,
766 -0.00448608f, 0.03137207f, -0.11697388f, 0.45574951f, 0.75192261f, -0.15435791f,
767 0.04379272f, -0.00781250f, -0.00433350f, 0.03070068f, -0.11477661f, 0.44500732f,
768 0.76095581f, -0.15435791f, 0.04385376f, -0.00787354f, -0.00421143f, 0.03002930f,
769 -0.11251831f, 0.43429565f, 0.76989746f, -0.15426636f, 0.04388428f, -0.00793457f,
770 -0.00405884f, 0.02935791f, -0.11026001f, 0.42358398f, 0.77871704f, -0.15408325f,
771 0.04388428f, -0.00799561f, -0.00393677f, 0.02865601f, -0.10794067f, 0.41287231f,
772 0.78741455f, -0.15377808f, 0.04388428f, -0.00805664f, -0.00381470f, 0.02795410f,
773 -0.10559082f, 0.40219116f, 0.79598999f, -0.15338135f, 0.04382324f, -0.00808716f,
774 -0.00366211f, 0.02725220f, -0.10321045f, 0.39154053f, 0.80441284f, -0.15283203f,
775 0.04373169f, -0.00811768f, -0.00354004f, 0.02655029f, -0.10079956f, 0.38092041f,
776 0.81271362f, -0.15219116f, 0.04357910f, -0.00814819f, -0.00341797f, 0.02584839f,
777 -0.09838867f, 0.37033081f, 0.82086182f, -0.15142822f, 0.04342651f, -0.00814819f,
778 -0.00326538f, 0.02514648f, -0.09594727f, 0.35977173f, 0.82888794f, -0.15054321f,
779 0.04321289f, -0.00817871f, -0.00314331f, 0.02441406f, -0.09347534f, 0.34924316f,
780 0.83673096f, -0.14953613f, 0.04299927f, -0.00817871f, -0.00302124f, 0.02371216f,
781 -0.09097290f, 0.33874512f, 0.84445190f, -0.14840698f, 0.04272461f, -0.00817871f,
782 -0.00289917f, 0.02297974f, -0.08847046f, 0.32833862f, 0.85202026f, -0.14715576f,
783 0.04238892f, -0.00814819f, -0.00277710f, 0.02227783f, -0.08596802f, 0.31793213f,
784 0.85940552f, -0.14578247f, 0.04205322f, -0.00814819f, -0.00265503f, 0.02154541f,
785 -0.08343506f, 0.30761719f, 0.86666870f, -0.14428711f, 0.04165649f, -0.00811768f,
786 -0.00253296f, 0.02084351f, -0.08090210f, 0.29733276f, 0.87374878f, -0.14263916f,
787 0.04122925f, -0.00808716f, -0.00244141f, 0.02011108f, -0.07833862f, 0.28710938f,
788 0.88064575f, -0.14089966f, 0.04077148f, -0.00802612f, -0.00231934f, 0.01940918f,
789 -0.07577515f, 0.27694702f, 0.88739014f, -0.13900757f, 0.04025269f, -0.00796509f,
790 -0.00219727f, 0.01870728f, -0.07321167f, 0.26687622f, 0.89395142f, -0.13696289f,
791 0.03970337f, -0.00790405f, -0.00210571f, 0.01797485f, -0.07064819f, 0.25683594f,
792 0.90036011f, -0.13479614f, 0.03909302f, -0.00784302f, -0.00198364f, 0.01727295f,
793 -0.06808472f, 0.24691772f, 0.90658569f, -0.13250732f, 0.03848267f, -0.00775146f,
794 -0.00189209f, 0.01657104f, -0.06555176f, 0.23703003f, 0.91262817f, -0.13006592f,
795 0.03781128f, -0.00765991f, -0.00177002f, 0.01586914f, -0.06298828f, 0.22723389f,
796 0.91848755f, -0.12750244f, 0.03707886f, -0.00753784f, -0.00167847f, 0.01519775f,
797 -0.06042480f, 0.21752930f, 0.92413330f, -0.12478638f, 0.03631592f, -0.00744629f,
798 -0.00158691f, 0.01449585f, -0.05786133f, 0.20791626f, 0.92962646f, -0.12194824f,
799 0.03552246f, -0.00732422f, -0.00149536f, 0.01382446f, -0.05532837f, 0.19839478f,
800 0.93490601f, -0.11895752f, 0.03466797f, -0.00717163f, -0.00140381f, 0.01315308f,
801 -0.05279541f, 0.18893433f, 0.94000244f, -0.11584473f, 0.03378296f, -0.00701904f,
802 -0.00131226f, 0.01248169f, -0.05029297f, 0.17959595f, 0.94491577f, -0.11254883f,
803 0.03286743f, -0.00686646f, -0.00122070f, 0.01181030f, -0.04779053f, 0.17034912f,
804 0.94961548f, -0.10916138f, 0.03189087f, -0.00671387f, -0.00112915f, 0.01113892f,
805 -0.04528809f, 0.16119385f, 0.95413208f, -0.10559082f, 0.03085327f, -0.00653076f,
806 -0.00106812f, 0.01049805f, -0.04281616f, 0.15213013f, 0.95843506f, -0.10189819f,
807 0.02981567f, -0.00631714f, -0.00097656f, 0.00985718f, -0.04034424f, 0.14318848f,
808 0.96252441f, -0.09805298f, 0.02868652f, -0.00613403f, -0.00091553f, 0.00924683f,
809 -0.03790283f, 0.13436890f, 0.96643066f, -0.09405518f, 0.02755737f, -0.00592041f,
810 -0.00082397f, 0.00860596f, -0.03549194f, 0.12561035f, 0.97012329f, -0.08993530f,
811 0.02636719f, -0.00567627f, -0.00076294f, 0.00799561f, -0.03308105f, 0.11700439f,
812 0.97360229f, -0.08566284f, 0.02511597f, -0.00543213f, -0.00070190f, 0.00738525f,
813 -0.03073120f, 0.10848999f, 0.97686768f, -0.08123779f, 0.02383423f, -0.00518799f,
814 -0.00061035f, 0.00680542f, -0.02835083f, 0.10012817f, 0.97991943f, -0.07666016f,
815 0.02252197f, -0.00494385f, -0.00054932f, 0.00622559f, -0.02603149f, 0.09185791f,
816 0.98278809f, -0.07192993f, 0.02114868f, -0.00463867f, -0.00048828f, 0.00564575f,
817 -0.02374268f, 0.08370972f, 0.98541260f, -0.06707764f, 0.01971436f, -0.00436401f,
818 -0.00042725f, 0.00506592f, -0.02145386f, 0.07568359f, 0.98782349f, -0.06207275f,
819 0.01828003f, -0.00405884f, -0.00039673f, 0.00451660f, -0.01922607f, 0.06777954f,
820 0.99002075f, -0.05691528f, 0.01678467f, -0.00375366f, -0.00033569f, 0.00396729f,
821 -0.01699829f, 0.05999756f, 0.99200439f, -0.05163574f, 0.01522827f, -0.00341797f,
822 -0.00027466f, 0.00344849f, -0.01483154f, 0.05233765f, 0.99377441f, -0.04620361f,
823 0.01364136f, -0.00308228f, -0.00024414f, 0.00292969f, -0.01266479f, 0.04483032f,
824 0.99533081f, -0.04061890f, 0.01202393f, -0.00274658f, -0.00018311f, 0.00241089f,
825 -0.01055908f, 0.03741455f, 0.99664307f, -0.03488159f, 0.01037598f, -0.00238037f,
826 -0.00015259f, 0.00192261f, -0.00845337f, 0.03018188f, 0.99774170f, -0.02899170f,
827 0.00866699f, -0.00201416f, -0.00009155f, 0.00143433f, -0.00640869f, 0.02304077f,
828 0.99862671f, -0.02297974f, 0.00689697f, -0.00161743f, -0.00006104f, 0.00097656f,
829 -0.00439453f, 0.01605225f, 0.99929810f, -0.01684570f, 0.00512695f, -0.00122070f,
830 -0.00003052f, 0.00051880f, -0.00241089f, 0.00918579f, 0.99975586f, -0.01052856f,
831 0.00329590f, -0.00079346f, 0.00000000f, 0.00006104f, -0.00048828f, 0.00247192f,
832 0.99996948f, -0.00408936f, 0.00143433f, -0.00036621f,
833 };
834
835 const auto get_lut = [&]() -> std::span<const f32> {
836 if (sample_rate_ratio <= 1.0f) {
837 return std::span<const f32>(lut2.data(), lut2.size());
838 } else if (sample_rate_ratio < 1.3f) {
839 return std::span<const f32>(lut1.data(), lut1.size());
840 } else {
841 return std::span<const f32>(lut0.data(), lut0.size());
842 }
843 };
844
845 auto lut{get_lut()};
846 u32 read_index{0};
847 for (u32 i = 0; i < samples_to_write; i++) {
848 const auto lut_index{(fraction.get_frac() >> 8) * 8};
849 const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
850 const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
851 const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
852 const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]};
853 const Common::FixedPoint<56, 8> sample4{input[read_index + 4] * lut[lut_index + 4]};
854 const Common::FixedPoint<56, 8> sample5{input[read_index + 5] * lut[lut_index + 5]};
855 const Common::FixedPoint<56, 8> sample6{input[read_index + 6] * lut[lut_index + 6]};
856 const Common::FixedPoint<56, 8> sample7{input[read_index + 7] * lut[lut_index + 7]};
857 output[i] = (sample0 + sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7)
858 .to_int_floor();
859 fraction += sample_rate_ratio;
860 read_index += static_cast<u32>(fraction.to_int_floor());
861 fraction.clear_int();
862 }
863}
864
865void Resample(std::span<s32> output, std::span<const s16> input,
866 const Common::FixedPoint<49, 15>& sample_rate_ratio,
867 Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write,
868 const SrcQuality src_quality) {
869
870 switch (src_quality) {
871 case SrcQuality::Low:
872 ResampleLowQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
873 break;
874 case SrcQuality::Medium:
875 ResampleNormalQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
876 break;
877 case SrcQuality::High:
878 ResampleHighQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
879 break;
880 }
881}
882
883} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10#include "common/fixed_point.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Resample an input buffer into an output buffer, according to the sample_rate_ratio.
15 *
16 * @param output - Output buffer.
17 * @param input - Input buffer.
18 * @param sample_rate_ratio - Ratio for resampling.
19 e.g 32000/48000 = 0.666 input samples read per output.
20 * @param fraction - Current read fraction, written to and should be passed back in for
21 * multiple calls.
22 * @param samples_to_write - Number of samples to write.
23 * @param src_quality - Resampling quality.
24 */
25void Resample(std::span<s32> output, std::span<const s16> input,
26 const Common::FixedPoint<49, 15>& sample_rate_ratio,
27 Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality);
28
29} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/resample/upsample.h"
8#include "audio_core/renderer/upsampler/upsampler_info.h"
9
10namespace AudioCore::AudioRenderer {
11/**
12 * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K.
13 *
14 * @param output - Output buffer.
15 * @param input - Input buffer.
16 * @param target_sample_count - Number of samples for output.
17 * @param state - Upsampler state, updated each call.
18 */
19static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
20 const u32 target_sample_count, const u32 source_sample_count,
21 UpsamplerState* state) {
22 constexpr u32 WindowSize = 10;
23 constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{
24 51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f,
25 -1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f,
26 };
27 constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{
28 105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f,
29 -1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f,
30 };
31 constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{
32 122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f,
33 -1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f,
34 };
35 constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{
36 23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f,
37 -0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f,
38 };
39 constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{
40 80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f,
41 -1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f,
42 };
43
44 if (!state->initialized) {
45 switch (source_sample_count) {
46 case 40:
47 state->window_size = WindowSize;
48 state->ratio = 6.0f;
49 state->history.fill(0);
50 break;
51
52 case 80:
53 state->window_size = WindowSize;
54 state->ratio = 3.0f;
55 state->history.fill(0);
56 break;
57
58 case 160:
59 state->window_size = WindowSize;
60 state->ratio = 1.5f;
61 state->history.fill(0);
62 break;
63
64 default:
65 LOG_ERROR(Service_Audio, "Invalid upsampling source count {}!", source_sample_count);
66 // This continues anyway, but let's assume 160 for sanity
67 state->window_size = WindowSize;
68 state->ratio = 1.5f;
69 state->history.fill(0);
70 break;
71 }
72
73 state->history_input_index = 0;
74 state->history_output_index = 9;
75 state->history_start_index = 0;
76 state->history_end_index = UpsamplerState::HistorySize - 1;
77 state->initialized = true;
78 }
79
80 if (target_sample_count == 0) {
81 return;
82 }
83
84 u32 read_index{0};
85
86 auto increment = [&]() -> void {
87 state->history[state->history_input_index] = input[read_index++];
88 state->history_input_index =
89 static_cast<u16>((state->history_input_index + 1) % UpsamplerState::HistorySize);
90 state->history_output_index =
91 static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
92 };
93
94 auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1,
95 std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 {
96 auto output_index{state->history_output_index};
97 auto start_pos{output_index - state->history_start_index + 1U};
98 auto end_pos{10U};
99
100 if (start_pos < 10) {
101 end_pos = start_pos;
102 }
103
104 u64 prev_contrib{0};
105 u32 coeff_index{0};
106 for (; coeff_index < end_pos; coeff_index++, output_index--) {
107 prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
108 coeffs1[coeff_index].to_raw();
109 }
110
111 auto end_index{state->history_end_index};
112 for (; start_pos < 9; start_pos++, coeff_index++, end_index--) {
113 prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) *
114 coeffs1[coeff_index].to_raw();
115 }
116
117 output_index =
118 static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
119 start_pos = state->history_end_index - output_index + 1U;
120 end_pos = 10U;
121
122 if (start_pos < 10) {
123 end_pos = start_pos;
124 }
125
126 u64 next_contrib{0};
127 coeff_index = 0;
128 for (; coeff_index < end_pos; coeff_index++, output_index++) {
129 next_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
130 coeffs2[coeff_index].to_raw();
131 }
132
133 auto start_index{state->history_start_index};
134 for (; start_pos < 9; start_pos++, start_index++, coeff_index++) {
135 next_contrib += static_cast<u64>(state->history[start_index].to_raw()) *
136 coeffs2[coeff_index].to_raw();
137 }
138
139 return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8);
140 };
141
142 switch (state->ratio.to_int_floor()) {
143 // 40 -> 240
144 case 6:
145 for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
146 switch (state->sample_index) {
147 case 0:
148 increment();
149 output[write_index] = state->history[state->history_output_index].to_int_floor();
150 break;
151
152 case 1:
153 output[write_index] = calculate_sample(SincWindow3, SincWindow4);
154 break;
155
156 case 2:
157 output[write_index] = calculate_sample(SincWindow2, SincWindow1);
158 break;
159
160 case 3:
161 output[write_index] = calculate_sample(SincWindow5, SincWindow5);
162 break;
163
164 case 4:
165 output[write_index] = calculate_sample(SincWindow1, SincWindow2);
166 break;
167
168 case 5:
169 output[write_index] = calculate_sample(SincWindow4, SincWindow3);
170 break;
171 }
172 state->sample_index = static_cast<u8>((state->sample_index + 1) % 6);
173 }
174 break;
175
176 // 80 -> 240
177 case 3:
178 for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
179 switch (state->sample_index) {
180 case 0:
181 increment();
182 output[write_index] = state->history[state->history_output_index].to_int_floor();
183 break;
184
185 case 1:
186 output[write_index] = calculate_sample(SincWindow2, SincWindow1);
187 break;
188
189 case 2:
190 output[write_index] = calculate_sample(SincWindow1, SincWindow2);
191 break;
192 }
193 state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
194 }
195 break;
196
197 // 160 -> 240
198 default:
199 for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
200 switch (state->sample_index) {
201 case 0:
202 increment();
203 output[write_index] = state->history[state->history_output_index].to_int_floor();
204 break;
205
206 case 1:
207 output[write_index] = calculate_sample(SincWindow1, SincWindow2);
208 break;
209
210 case 2:
211 increment();
212 output[write_index] = calculate_sample(SincWindow2, SincWindow1);
213 break;
214 }
215 state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
216 }
217
218 break;
219 }
220}
221
222auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
223 std::string& string) -> void {
224 string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}",
225 source_sample_count, source_sample_rate);
226 const auto upsampler{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
227 if (upsampler != nullptr) {
228 string += fmt::format("\n\tUpsampler\n\t\tenabled {} sample count {}\n\tinputs: ",
229 upsampler->enabled, upsampler->sample_count);
230 for (u32 i = 0; i < upsampler->input_count; i++) {
231 string += fmt::format("{:02X}, ", upsampler->inputs[i]);
232 }
233 }
234 string += "\n";
235}
236
237void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) {
238 const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
239 const auto input_count{std::min(info->input_count, buffer_count)};
240 const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count};
241
242 for (u32 i = 0; i < input_count; i++) {
243 const auto channel{inputs_[i]};
244
245 if (channel >= 0 && channel < static_cast<s16>(processor.buffer_count)) {
246 auto state{&info->states[i]};
247 std::span<s32> output{
248 reinterpret_cast<s32*>(samples_buffer + info->sample_count * channel * sizeof(s32)),
249 info->sample_count};
250 auto input{processor.mix_buffers.subspan(channel * processor.sample_count,
251 processor.sample_count)};
252
253 SrcProcessFrame(output, input, info->sample_count, source_sample_count, state);
254 }
255 }
256}
257
258bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) {
259 return true;
260}
261
262} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for upsampling a mix buffer to 48Khz.
18 * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz.
19 */
20struct UpsampleCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Pointer to the output samples buffer.
45 CpuAddr samples_buffer;
46 /// Pointer to input mix buffer indexes.
47 CpuAddr inputs;
48 /// Number of input mix buffers.
49 u32 buffer_count;
50 /// Unknown, unused.
51 u32 unk_20;
52 /// Source data sample count.
53 u32 source_sample_count;
54 /// Source data sample rate.
55 u32 source_sample_rate;
56 /// Pointer to the upsampler info for this command.
57 CpuAddr upsampler_info;
58};
59
60} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <vector>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/sink/circular_buffer.h"
8#include "core/memory.h"
9
10namespace AudioCore::AudioRenderer {
11
12void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
13 std::string& string) {
14 string += fmt::format(
15 "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ",
16 input_count, size, pos);
17 for (u32 i = 0; i < input_count; i++) {
18 string += fmt::format("{:02X}, ", inputs[i]);
19 }
20 string += "\n";
21}
22
23void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
24 constexpr s32 min{std::numeric_limits<s16>::min()};
25 constexpr s32 max{std::numeric_limits<s16>::max()};
26
27 std::vector<s16> output(processor.sample_count);
28 for (u32 channel = 0; channel < input_count; channel++) {
29 auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count,
30 processor.sample_count)};
31 for (u32 sample_index = 0; sample_index < processor.sample_count; sample_index++) {
32 output[sample_index] = static_cast<s16>(std::clamp(input[sample_index], min, max));
33 }
34
35 processor.memory->WriteBlockUnsafe(address + pos, output.data(),
36 output.size() * sizeof(s16));
37 pos += static_cast<u32>(processor.sample_count * sizeof(s16));
38 if (pos >= size) {
39 pos = 0;
40 }
41 }
42}
43
44bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
45 return true;
46}
47
48} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for sinking samples to a circular buffer.
18 */
19struct CircularBufferSinkCommand : ICommand {
20 /**
21 * Print this command's information to a string.
22 *
23 * @param processor - The CommandListProcessor processing this command.
24 * @param string - The string to print into.
25 */
26 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
27
28 /**
29 * Process this command.
30 *
31 * @param processor - The CommandListProcessor processing this command.
32 */
33 void Process(const ADSP::CommandListProcessor& processor) override;
34
35 /**
36 * Verify this command's data is valid.
37 *
38 * @param processor - The CommandListProcessor processing this command.
39 * @return True if the command is valid, otherwise false.
40 */
41 bool Verify(const ADSP::CommandListProcessor& processor) override;
42
43 /// Number of input mix buffers
44 u32 input_count;
45 /// Input mix buffer indexes
46 std::array<s16, MaxChannels> inputs;
47 /// Circular buffer address
48 CpuAddr address;
49 /// Circular buffer size
50 u32 size;
51 /// Current buffer offset
52 u32 pos;
53};
54
55} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/sink/device.h"
8#include "audio_core/sink/sink.h"
9
10namespace AudioCore::AudioRenderer {
11
12void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
13 std::string& string) {
14 string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ",
15 std::string_view(name), session_id, input_count);
16 for (u32 i = 0; i < input_count; i++) {
17 string += fmt::format("{:02X}, ", inputs[i]);
18 }
19 string += "\n";
20}
21
22void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
23 constexpr s32 min = std::numeric_limits<s16>::min();
24 constexpr s32 max = std::numeric_limits<s16>::max();
25
26 auto stream{processor.GetOutputSinkStream()};
27 stream->SetSystemChannels(input_count);
28
29 Sink::SinkBuffer out_buffer{
30 .frames{TargetSampleCount},
31 .frames_played{0},
32 .tag{0},
33 .consumed{false},
34 };
35
36 std::vector<s16> samples(out_buffer.frames * input_count);
37
38 for (u32 channel = 0; channel < input_count; channel++) {
39 const auto offset{inputs[channel] * out_buffer.frames};
40
41 for (u32 index = 0; index < out_buffer.frames; index++) {
42 samples[index * input_count + channel] =
43 static_cast<s16>(std::clamp(sample_buffer[offset + index], min, max));
44 }
45 }
46
47 out_buffer.tag = reinterpret_cast<u64>(samples.data());
48 stream->AppendBuffer(out_buffer, samples);
49}
50
51bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
52 return true;
53}
54
55} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8#include <string>
9
10#include "audio_core/renderer/command/icommand.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for sinking samples to an output device.
20 */
21struct DeviceSinkCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Device name
46 char name[0x100];
47 /// System session id (unused)
48 s32 session_id;
49 /// Sample buffer to sink
50 std::span<s32> sample_buffer;
51 /// Number of input channels
52 u32 input_count;
53 /// Mix buffer indexes for each channel
54 std::array<s16, MaxChannels> inputs;
55};
56
57} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/aux_.h"
5
6namespace AudioCore::AudioRenderer {
7
8void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
9 const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
14 mix_id = in_params.mix_id;
15 process_order = in_params.process_order;
16 enabled = in_params.enabled;
17 if (buffer_unmapped || in_params.is_new) {
18 const bool send_unmapped{!pool_mapper.TryAttachBuffer(
19 error_info, workbuffers[0], in_specific->send_buffer_info_address,
20 sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
21 const bool return_unmapped{!pool_mapper.TryAttachBuffer(
22 error_info, workbuffers[1], in_specific->return_buffer_info_address,
23 sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
24
25 buffer_unmapped = send_unmapped || return_unmapped;
26
27 if (!buffer_unmapped) {
28 auto send{workbuffers[0].GetReference(false)};
29 send_buffer_info = send + sizeof(AuxInfoDsp);
30 send_buffer = send + sizeof(AuxBufferInfo);
31
32 auto ret{workbuffers[1].GetReference(false)};
33 return_buffer_info = ret + sizeof(AuxInfoDsp);
34 return_buffer = ret + sizeof(AuxBufferInfo);
35 }
36 } else {
37 error_info.error_code = ResultSuccess;
38 error_info.address = CpuAddr(0);
39 }
40}
41
42void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
43 const PoolMapper& pool_mapper) {
44 auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
45 auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
46
47 std::memcpy(params, in_specific, sizeof(ParameterVersion2));
48 mix_id = in_params.mix_id;
49 process_order = in_params.process_order;
50 enabled = in_params.enabled;
51
52 if (buffer_unmapped || in_params.is_new) {
53 const bool send_unmapped{!pool_mapper.TryAttachBuffer(
54 error_info, workbuffers[0], params->send_buffer_info_address,
55 sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
56 const bool return_unmapped{!pool_mapper.TryAttachBuffer(
57 error_info, workbuffers[1], params->return_buffer_info_address,
58 sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
59
60 buffer_unmapped = send_unmapped || return_unmapped;
61
62 if (!buffer_unmapped) {
63 auto send{workbuffers[0].GetReference(false)};
64 send_buffer_info = send + sizeof(AuxInfoDsp);
65 send_buffer = send + sizeof(AuxBufferInfo);
66
67 auto ret{workbuffers[1].GetReference(false)};
68 return_buffer_info = ret + sizeof(AuxInfoDsp);
69 return_buffer = ret + sizeof(AuxBufferInfo);
70 }
71 } else {
72 error_info.error_code = ResultSuccess;
73 error_info.address = CpuAddr(0);
74 }
75}
76
77void AuxInfo::UpdateForCommandGeneration() {
78 if (enabled) {
79 usage_state = UsageState::Enabled;
80 } else {
81 usage_state = UsageState::Disabled;
82 }
83}
84
85void AuxInfo::InitializeResultState(EffectResultState& result_state) {}
86
87void AuxInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
88
89CpuAddr AuxInfo::GetWorkbuffer(s32 index) {
90 return workbuffers[index].GetReference(true);
91}
92
93} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Auxiliary Buffer used for Aux commands.
15 * Send and return buffers are available (names from the game's perspective).
16 * Send is read by the host, containing a buffer of samples to be used for whatever purpose.
17 * Return is written by the host, writing a mix buffer back to the game.
18 * This allows the game to use pre-processed samples skipping the other render processing,
19 * and to examine or modify what the audio renderer has generated.
20 */
21class AuxInfo : public EffectInfoBase {
22public:
23 struct ParameterVersion1 {
24 /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
25 /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
26 /* 0x30 */ u32 mix_buffer_count;
27 /* 0x34 */ u32 sample_rate;
28 /* 0x38 */ u32 count_max;
29 /* 0x3C */ u32 mix_buffer_count_max;
30 /* 0x40 */ CpuAddr send_buffer_info_address;
31 /* 0x48 */ CpuAddr send_buffer_address;
32 /* 0x50 */ CpuAddr return_buffer_info_address;
33 /* 0x58 */ CpuAddr return_buffer_address;
34 /* 0x60 */ u32 mix_buffer_sample_size;
35 /* 0x64 */ u32 sample_count;
36 /* 0x68 */ u32 mix_buffer_sample_count;
37 };
38 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
39 "AuxInfo::ParameterVersion1 has the wrong size!");
40
41 struct ParameterVersion2 {
42 /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
43 /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
44 /* 0x30 */ u32 mix_buffer_count;
45 /* 0x34 */ u32 sample_rate;
46 /* 0x38 */ u32 count_max;
47 /* 0x3C */ u32 mix_buffer_count_max;
48 /* 0x40 */ CpuAddr send_buffer_info_address;
49 /* 0x48 */ CpuAddr send_buffer_address;
50 /* 0x50 */ CpuAddr return_buffer_info_address;
51 /* 0x58 */ CpuAddr return_buffer_address;
52 /* 0x60 */ u32 mix_buffer_sample_size;
53 /* 0x64 */ u32 sample_count;
54 /* 0x68 */ u32 mix_buffer_sample_count;
55 };
56 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
57 "AuxInfo::ParameterVersion2 has the wrong size!");
58
59 struct AuxInfoDsp {
60 /* 0x00 */ u32 read_offset;
61 /* 0x04 */ u32 write_offset;
62 /* 0x08 */ u32 lost_sample_count;
63 /* 0x0C */ u32 total_sample_count;
64 /* 0x10 */ char unk10[0x30];
65 };
66 static_assert(sizeof(AuxInfoDsp) == 0x40, "AuxInfo::AuxInfoDsp has the wrong size!");
67
68 struct AuxBufferInfo {
69 /* 0x00 */ AuxInfoDsp cpu_info;
70 /* 0x40 */ AuxInfoDsp dsp_info;
71 };
72 static_assert(sizeof(AuxBufferInfo) == 0x80, "AuxInfo::AuxBufferInfo has the wrong size!");
73
74 /**
75 * Update the info with new parameters, version 1.
76 *
77 * @param error_info - Used to write call result code.
78 * @param in_params - New parameters to update the info with.
79 * @param pool_mapper - Pool for mapping buffers.
80 */
81 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
82 const PoolMapper& pool_mapper) override;
83
84 /**
85 * Update the info with new parameters, version 2.
86 *
87 * @param error_info - Used to write call result code.
88 * @param in_params - New parameters to update the info with.
89 * @param pool_mapper - Pool for mapping buffers.
90 */
91 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
92 const PoolMapper& pool_mapper) override;
93
94 /**
95 * Update the info after command generation. Usually only changes its state.
96 */
97 void UpdateForCommandGeneration() override;
98
99 /**
100 * Initialize a new result state. Version 2 only, unused.
101 *
102 * @param result_state - Result state to initialize.
103 */
104 void InitializeResultState(EffectResultState& result_state) override;
105
106 /**
107 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
108 *
109 * @param cpu_state - Host-side result state to update.
110 * @param dsp_state - AudioRenderer-side result state to update from.
111 */
112 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
113
114 /**
115 * Get a workbuffer assigned to this effect with the given index.
116 *
117 * @param index - Workbuffer index.
118 * @return Address of the buffer.
119 */
120 CpuAddr GetWorkbuffer(s32 index) override;
121};
122
123} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/biquad_filter.h"
5
6namespace AudioCore::AudioRenderer {
7
8void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
14 mix_id = in_params.mix_id;
15 process_order = in_params.process_order;
16 enabled = in_params.enabled;
17
18 error_info.error_code = ResultSuccess;
19 error_info.address = CpuAddr(0);
20}
21
22void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
23 const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
24 auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
25 auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
26
27 std::memcpy(params, in_specific, sizeof(ParameterVersion2));
28 mix_id = in_params.mix_id;
29 process_order = in_params.process_order;
30 enabled = in_params.enabled;
31
32 error_info.error_code = ResultSuccess;
33 error_info.address = CpuAddr(0);
34}
35
36void BiquadFilterInfo::UpdateForCommandGeneration() {
37 if (enabled) {
38 usage_state = UsageState::Enabled;
39 } else {
40 usage_state = UsageState::Disabled;
41 }
42
43 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
44 params->state = ParameterState::Updated;
45}
46
47void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {}
48
49void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state,
50 EffectResultState& dsp_state) {}
51
52} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13
14class BiquadFilterInfo : public EffectInfoBase {
15public:
16 struct ParameterVersion1 {
17 /* 0x00 */ std::array<s8, MaxChannels> inputs;
18 /* 0x06 */ std::array<s8, MaxChannels> outputs;
19 /* 0x0C */ std::array<s16, 3> b;
20 /* 0x12 */ std::array<s16, 2> a;
21 /* 0x16 */ s8 channel_count;
22 /* 0x17 */ ParameterState state;
23 };
24 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
25 "BiquadFilterInfo::ParameterVersion1 has the wrong size!");
26
27 struct ParameterVersion2 {
28 /* 0x00 */ std::array<s8, MaxChannels> inputs;
29 /* 0x06 */ std::array<s8, MaxChannels> outputs;
30 /* 0x0C */ std::array<s16, 3> b;
31 /* 0x12 */ std::array<s16, 2> a;
32 /* 0x16 */ s8 channel_count;
33 /* 0x17 */ ParameterState state;
34 };
35 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
36 "BiquadFilterInfo::ParameterVersion2 has the wrong size!");
37
38 /**
39 * Update the info with new parameters, version 1.
40 *
41 * @param error_info - Used to write call result code.
42 * @param in_params - New parameters to update the info with.
43 * @param pool_mapper - Pool for mapping buffers.
44 */
45 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
46 const PoolMapper& pool_mapper) override;
47
48 /**
49 * Update the info with new parameters, version 2.
50 *
51 * @param error_info - Used to write call result code.
52 * @param in_params - New parameters to update the info with.
53 * @param pool_mapper - Pool for mapping buffers.
54 */
55 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
56 const PoolMapper& pool_mapper) override;
57
58 /**
59 * Update the info after command generation. Usually only changes its state.
60 */
61 void UpdateForCommandGeneration() override;
62
63 /**
64 * Initialize a new result state. Version 2 only, unused.
65 *
66 * @param result_state - Result state to initialize.
67 */
68 void InitializeResultState(EffectResultState& result_state) override;
69
70 /**
71 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
72 *
73 * @param cpu_state - Host-side result state to update.
74 * @param dsp_state - AudioRenderer-side result state to update from.
75 */
76 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
77};
78
79} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/buffer_mixer.h"
5
6namespace AudioCore::AudioRenderer {
7
8void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
14 mix_id = in_params.mix_id;
15 process_order = in_params.process_order;
16 enabled = in_params.enabled;
17
18 error_info.error_code = ResultSuccess;
19 error_info.address = CpuAddr(0);
20}
21
22void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
23 const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
24 auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
25 auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
26
27 std::memcpy(params, in_specific, sizeof(ParameterVersion2));
28 mix_id = in_params.mix_id;
29 process_order = in_params.process_order;
30 enabled = in_params.enabled;
31
32 error_info.error_code = ResultSuccess;
33 error_info.address = CpuAddr(0);
34}
35
36void BufferMixerInfo::UpdateForCommandGeneration() {
37 if (enabled) {
38 usage_state = UsageState::Enabled;
39 } else {
40 usage_state = UsageState::Disabled;
41 }
42}
43
44void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {}
45
46void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state,
47 EffectResultState& dsp_state) {}
48
49} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13
14class BufferMixerInfo : public EffectInfoBase {
15public:
16 struct ParameterVersion1 {
17 /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
18 /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
19 /* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
20 /* 0x90 */ u32 mix_count;
21 };
22 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
23 "BufferMixerInfo::ParameterVersion1 has the wrong size!");
24
25 struct ParameterVersion2 {
26 /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
27 /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
28 /* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
29 /* 0x90 */ u32 mix_count;
30 };
31 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
32 "BufferMixerInfo::ParameterVersion2 has the wrong size!");
33
34 /**
35 * Update the info with new parameters, version 1.
36 *
37 * @param error_info - Used to write call result code.
38 * @param in_params - New parameters to update the info with.
39 * @param pool_mapper - Pool for mapping buffers.
40 */
41 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
42 const PoolMapper& pool_mapper) override;
43
44 /**
45 * Update the info with new parameters, version 2.
46 *
47 * @param error_info - Used to write call result code.
48 * @param in_params - New parameters to update the info with.
49 * @param pool_mapper - Pool for mapping buffers.
50 */
51 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
52 const PoolMapper& pool_mapper) override;
53
54 /**
55 * Update the info after command generation. Usually only changes its state.
56 */
57 void UpdateForCommandGeneration() override;
58
59 /**
60 * Initialize a new result state. Version 2 only, unused.
61 *
62 * @param result_state - Result state to initialize.
63 */
64 void InitializeResultState(EffectResultState& result_state) override;
65
66 /**
67 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
68 *
69 * @param cpu_state - Host-side result state to update.
70 * @param dsp_state - AudioRenderer-side result state to update from.
71 */
72 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
73};
74
75} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/aux_.h"
5#include "audio_core/renderer/effect/capture.h"
6
7namespace AudioCore::AudioRenderer {
8
9void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
10 const PoolMapper& pool_mapper) {
11 auto in_specific{
12 reinterpret_cast<const AuxInfo::ParameterVersion1*>(in_params.specific.data())};
13 auto params{reinterpret_cast<AuxInfo::ParameterVersion1*>(parameter.data())};
14
15 std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion1));
16 mix_id = in_params.mix_id;
17 process_order = in_params.process_order;
18 enabled = in_params.enabled;
19 if (buffer_unmapped || in_params.is_new) {
20 buffer_unmapped = !pool_mapper.TryAttachBuffer(
21 error_info, workbuffers[0], in_specific->send_buffer_info_address,
22 in_specific->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
23
24 if (!buffer_unmapped) {
25 const auto send_address{workbuffers[0].GetReference(false)};
26 send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
27 send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
28 return_buffer_info = 0;
29 return_buffer = 0;
30 }
31 } else {
32 error_info.error_code = ResultSuccess;
33 error_info.address = CpuAddr(0);
34 }
35}
36
37void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
38 const PoolMapper& pool_mapper) {
39 auto in_specific{
40 reinterpret_cast<const AuxInfo::ParameterVersion2*>(in_params.specific.data())};
41 auto params{reinterpret_cast<AuxInfo::ParameterVersion2*>(parameter.data())};
42
43 std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion2));
44 mix_id = in_params.mix_id;
45 process_order = in_params.process_order;
46 enabled = in_params.enabled;
47
48 if (buffer_unmapped || in_params.is_new) {
49 buffer_unmapped = !pool_mapper.TryAttachBuffer(
50 error_info, workbuffers[0], params->send_buffer_info_address,
51 params->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
52
53 if (!buffer_unmapped) {
54 const auto send_address{workbuffers[0].GetReference(false)};
55 send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
56 send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
57 return_buffer_info = 0;
58 return_buffer = 0;
59 }
60 } else {
61 error_info.error_code = ResultSuccess;
62 error_info.address = CpuAddr(0);
63 }
64}
65
66void CaptureInfo::UpdateForCommandGeneration() {
67 if (enabled) {
68 usage_state = UsageState::Enabled;
69 } else {
70 usage_state = UsageState::Disabled;
71 }
72}
73
74void CaptureInfo::InitializeResultState(EffectResultState& result_state) {}
75
76void CaptureInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
77
78CpuAddr CaptureInfo::GetWorkbuffer(s32 index) {
79 return workbuffers[index].GetReference(true);
80}
81
82} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13
14class CaptureInfo : public EffectInfoBase {
15public:
16 /**
17 * Update the info with new parameters, version 1.
18 *
19 * @param error_info - Used to write call result code.
20 * @param in_params - New parameters to update the info with.
21 * @param pool_mapper - Pool for mapping buffers.
22 */
23 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
24 const PoolMapper& pool_mapper) override;
25
26 /**
27 * Update the info with new parameters, version 2.
28 *
29 * @param error_info - Used to write call result code.
30 * @param in_params - New parameters to update the info with.
31 * @param pool_mapper - Pool for mapping buffers.
32 */
33 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
34 const PoolMapper& pool_mapper) override;
35
36 /**
37 * Update the info after command generation. Usually only changes its state.
38 */
39 void UpdateForCommandGeneration() override;
40
41 /**
42 * Initialize a new result state. Version 2 only, unused.
43 *
44 * @param result_state - Result state to initialize.
45 */
46 void InitializeResultState(EffectResultState& result_state) override;
47
48 /**
49 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
50 *
51 * @param cpu_state - Host-side result state to update.
52 * @param dsp_state - AudioRenderer-side result state to update from.
53 */
54 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
55
56 /**
57 * Get a workbuffer assigned to this effect with the given index.
58 *
59 * @param index - Workbuffer index.
60 * @return Address of the buffer.
61 */
62 CpuAddr GetWorkbuffer(s32 index) override;
63};
64
65} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/compressor.h"
5
6namespace AudioCore::AudioRenderer {
7
8void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {}
10
11void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
12 const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
13 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
14 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
15
16 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
17 mix_id = in_params.mix_id;
18 process_order = in_params.process_order;
19 enabled = in_params.enabled;
20
21 error_info.error_code = ResultSuccess;
22 error_info.address = CpuAddr(0);
23}
24
25void CompressorInfo::UpdateForCommandGeneration() {
26 if (enabled) {
27 usage_state = UsageState::Enabled;
28 } else {
29 usage_state = UsageState::Disabled;
30 }
31
32 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
33 params->state = ParameterState::Updated;
34}
35
36CpuAddr CompressorInfo::GetWorkbuffer(s32 index) {
37 return GetSingleBuffer(index);
38}
39
40} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h"
11#include "common/fixed_point.h"
12
13namespace AudioCore::AudioRenderer {
14
15class CompressorInfo : public EffectInfoBase {
16public:
17 struct ParameterVersion1 {
18 /* 0x00 */ std::array<s8, MaxChannels> inputs;
19 /* 0x06 */ std::array<s8, MaxChannels> outputs;
20 /* 0x0C */ s16 channel_count_max;
21 /* 0x0E */ s16 channel_count;
22 /* 0x10 */ s32 sample_rate;
23 /* 0x14 */ f32 threshold;
24 /* 0x18 */ f32 compressor_ratio;
25 /* 0x1C */ s32 attack_time;
26 /* 0x20 */ s32 release_time;
27 /* 0x24 */ f32 unk_24;
28 /* 0x28 */ f32 unk_28;
29 /* 0x2C */ f32 unk_2C;
30 /* 0x30 */ f32 out_gain;
31 /* 0x34 */ ParameterState state;
32 /* 0x35 */ bool makeup_gain_enabled;
33 };
34 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
35 "CompressorInfo::ParameterVersion1 has the wrong size!");
36
37 struct ParameterVersion2 {
38 /* 0x00 */ std::array<s8, MaxChannels> inputs;
39 /* 0x06 */ std::array<s8, MaxChannels> outputs;
40 /* 0x0C */ s16 channel_count_max;
41 /* 0x0E */ s16 channel_count;
42 /* 0x10 */ s32 sample_rate;
43 /* 0x14 */ f32 threshold;
44 /* 0x18 */ f32 compressor_ratio;
45 /* 0x1C */ s32 attack_time;
46 /* 0x20 */ s32 release_time;
47 /* 0x24 */ f32 unk_24;
48 /* 0x28 */ f32 unk_28;
49 /* 0x2C */ f32 unk_2C;
50 /* 0x30 */ f32 out_gain;
51 /* 0x34 */ ParameterState state;
52 /* 0x35 */ bool makeup_gain_enabled;
53 };
54 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
55 "CompressorInfo::ParameterVersion2 has the wrong size!");
56
57 struct State {
58 f32 unk_00;
59 f32 unk_04;
60 f32 unk_08;
61 f32 unk_0C;
62 f32 unk_10;
63 f32 unk_14;
64 f32 unk_18;
65 f32 makeup_gain;
66 f32 unk_20;
67 char unk_24[0x1C];
68 };
69 static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
70 "CompressorInfo::State has the wrong size!");
71
72 /**
73 * Update the info with new parameters, version 1.
74 *
75 * @param error_info - Used to write call result code.
76 * @param in_params - New parameters to update the info with.
77 * @param pool_mapper - Pool for mapping buffers.
78 */
79 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
80 const PoolMapper& pool_mapper) override;
81
82 /**
83 * Update the info with new parameters, version 2.
84 *
85 * @param error_info - Used to write call result code.
86 * @param in_params - New parameters to update the info with.
87 * @param pool_mapper - Pool for mapping buffers.
88 */
89 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
90 const PoolMapper& pool_mapper) override;
91
92 /**
93 * Update the info after command generation. Usually only changes its state.
94 */
95 void UpdateForCommandGeneration() override;
96
97 /**
98 * Get a workbuffer assigned to this effect with the given index.
99 *
100 * @param index - Workbuffer index.
101 * @return Address of the buffer.
102 */
103 CpuAddr GetWorkbuffer(s32 index) override;
104};
105
106} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/delay.h"
5
6namespace AudioCore::AudioRenderer {
7
8void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
9 const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 if (IsChannelCountValid(in_specific->channel_count_max)) {
14 const auto old_state{params->state};
15 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
16 mix_id = in_params.mix_id;
17 process_order = in_params.process_order;
18 enabled = in_params.enabled;
19
20 if (!IsChannelCountValid(in_specific->channel_count)) {
21 params->channel_count = params->channel_count_max;
22 }
23
24 if (!IsChannelCountValid(in_specific->channel_count) ||
25 old_state != ParameterState::Updated) {
26 params->state = old_state;
27 }
28
29 if (buffer_unmapped || in_params.is_new) {
30 usage_state = UsageState::New;
31 params->state = ParameterState::Initialized;
32 buffer_unmapped = !pool_mapper.TryAttachBuffer(
33 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
34 return;
35 }
36 }
37 error_info.error_code = ResultSuccess;
38 error_info.address = CpuAddr(0);
39}
40
41void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
42 const PoolMapper& pool_mapper) {
43 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
44 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
45
46 if (IsChannelCountValid(in_specific->channel_count_max)) {
47 const auto old_state{params->state};
48 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
49 mix_id = in_params.mix_id;
50 process_order = in_params.process_order;
51 enabled = in_params.enabled;
52
53 if (!IsChannelCountValid(in_specific->channel_count)) {
54 params->channel_count = params->channel_count_max;
55 }
56
57 if (!IsChannelCountValid(in_specific->channel_count) ||
58 old_state != ParameterState::Updated) {
59 params->state = old_state;
60 }
61
62 if (buffer_unmapped || in_params.is_new) {
63 usage_state = UsageState::New;
64 params->state = ParameterState::Initialized;
65 buffer_unmapped = !pool_mapper.TryAttachBuffer(
66 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
67 return;
68 }
69 }
70 error_info.error_code = ResultSuccess;
71 error_info.address = CpuAddr(0);
72}
73
74void DelayInfo::UpdateForCommandGeneration() {
75 if (enabled) {
76 usage_state = UsageState::Enabled;
77 } else {
78 usage_state = UsageState::Disabled;
79 }
80
81 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
82 params->state = ParameterState::Updated;
83}
84
85void DelayInfo::InitializeResultState(EffectResultState& result_state) {}
86
87void DelayInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
88
89CpuAddr DelayInfo::GetWorkbuffer(s32 index) {
90 return GetSingleBuffer(index);
91}
92
93} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8
9#include "audio_core/common/common.h"
10#include "audio_core/renderer/effect/effect_info_base.h"
11#include "common/common_types.h"
12#include "common/fixed_point.h"
13
14namespace AudioCore::AudioRenderer {
15
16class DelayInfo : public EffectInfoBase {
17public:
18 struct ParameterVersion1 {
19 /* 0x00 */ std::array<s8, MaxChannels> inputs;
20 /* 0x06 */ std::array<s8, MaxChannels> outputs;
21 /* 0x0C */ u16 channel_count_max;
22 /* 0x0E */ u16 channel_count;
23 /* 0x10 */ u32 delay_time_max;
24 /* 0x14 */ u32 delay_time;
25 /* 0x18 */ Common::FixedPoint<18, 14> sample_rate;
26 /* 0x1C */ Common::FixedPoint<18, 14> in_gain;
27 /* 0x20 */ Common::FixedPoint<18, 14> feedback_gain;
28 /* 0x24 */ Common::FixedPoint<18, 14> wet_gain;
29 /* 0x28 */ Common::FixedPoint<18, 14> dry_gain;
30 /* 0x2C */ Common::FixedPoint<18, 14> channel_spread;
31 /* 0x30 */ Common::FixedPoint<18, 14> lowpass_amount;
32 /* 0x34 */ ParameterState state;
33 };
34 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
35 "DelayInfo::ParameterVersion1 has the wrong size!");
36
37 struct ParameterVersion2 {
38 /* 0x00 */ std::array<s8, MaxChannels> inputs;
39 /* 0x06 */ std::array<s8, MaxChannels> outputs;
40 /* 0x0C */ s16 channel_count_max;
41 /* 0x0E */ s16 channel_count;
42 /* 0x10 */ s32 delay_time_max;
43 /* 0x14 */ s32 delay_time;
44 /* 0x18 */ s32 sample_rate;
45 /* 0x1C */ s32 in_gain;
46 /* 0x20 */ s32 feedback_gain;
47 /* 0x24 */ s32 wet_gain;
48 /* 0x28 */ s32 dry_gain;
49 /* 0x2C */ s32 channel_spread;
50 /* 0x30 */ s32 lowpass_amount;
51 /* 0x34 */ ParameterState state;
52 };
53 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
54 "DelayInfo::ParameterVersion2 has the wrong size!");
55
56 struct DelayLine {
57 Common::FixedPoint<50, 14> Read() const {
58 return buffer[buffer_pos];
59 }
60
61 void Write(const Common::FixedPoint<50, 14> value) {
62 buffer[buffer_pos] = value;
63 buffer_pos = static_cast<u32>((buffer_pos + 1) % buffer.size());
64 }
65
66 s32 sample_count_max{};
67 s32 sample_count{};
68 std::vector<Common::FixedPoint<50, 14>> buffer{};
69 u32 buffer_pos{};
70 Common::FixedPoint<18, 14> decay_rate{};
71 };
72
73 struct State {
74 /* 0x000 */ std::array<s32, 8> unk_000;
75 /* 0x020 */ std::array<DelayLine, MaxChannels> delay_lines;
76 /* 0x0B0 */ Common::FixedPoint<18, 14> feedback_gain;
77 /* 0x0B4 */ Common::FixedPoint<18, 14> delay_feedback_gain;
78 /* 0x0B8 */ Common::FixedPoint<18, 14> delay_feedback_cross_gain;
79 /* 0x0BC */ Common::FixedPoint<18, 14> lowpass_gain;
80 /* 0x0C0 */ Common::FixedPoint<18, 14> lowpass_feedback_gain;
81 /* 0x0C4 */ std::array<Common::FixedPoint<50, 14>, MaxChannels> lowpass_z;
82 };
83 static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
84 "DelayInfo::State has the wrong size!");
85
86 /**
87 * Update the info with new parameters, version 1.
88 *
89 * @param error_info - Used to write call result code.
90 * @param in_params - New parameters to update the info with.
91 * @param pool_mapper - Pool for mapping buffers.
92 */
93 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
94 const PoolMapper& pool_mapper) override;
95
96 /**
97 * Update the info with new parameters, version 2.
98 *
99 * @param error_info - Used to write call result code.
100 * @param in_params - New parameters to update the info with.
101 * @param pool_mapper - Pool for mapping buffers.
102 */
103 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
104 const PoolMapper& pool_mapper) override;
105
106 /**
107 * Update the info after command generation. Usually only changes its state.
108 */
109 void UpdateForCommandGeneration() override;
110
111 /**
112 * Initialize a new result state. Version 2 only, unused.
113 *
114 * @param result_state - Result state to initialize.
115 */
116 void InitializeResultState(EffectResultState& result_state) override;
117
118 /**
119 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
120 *
121 * @param cpu_state - Host-side result state to update.
122 * @param dsp_state - AudioRenderer-side result state to update from.
123 */
124 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
125
126 /**
127 * Get a workbuffer assigned to this effect with the given index.
128 *
129 * @param index - Workbuffer index.
130 * @return Address of the buffer.
131 */
132 CpuAddr GetWorkbuffer(s32 index) override;
133};
134
135} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/effect_context.h"
5
6namespace AudioCore::AudioRenderer {
7
8void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
9 std::span<EffectResultState> result_states_cpu_,
10 std::span<EffectResultState> result_states_dsp_,
11 const size_t dsp_state_count_) {
12 effect_infos = effect_infos_;
13 effect_count = effect_count_;
14 result_states_cpu = result_states_cpu_;
15 result_states_dsp = result_states_dsp_;
16 dsp_state_count = dsp_state_count_;
17}
18
19EffectInfoBase& EffectContext::GetInfo(const u32 index) {
20 return effect_infos[index];
21}
22
23EffectResultState& EffectContext::GetResultState(const u32 index) {
24 return result_states_cpu[index];
25}
26
27EffectResultState& EffectContext::GetDspSharedResultState(const u32 index) {
28 return result_states_dsp[index];
29}
30
31u32 EffectContext::GetCount() const {
32 return effect_count;
33}
34
35void EffectContext::UpdateStateByDspShared() {
36 for (size_t i = 0; i < dsp_state_count; i++) {
37 effect_infos[i].UpdateResultState(result_states_cpu[i], result_states_dsp[i]);
38 }
39}
40
41} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/effect/effect_info_base.h"
9#include "audio_core/renderer/effect/effect_result_state.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13
14class EffectContext {
15public:
16 /**
17 * Initialize the effect context
18 * @param effect_infos List of effect infos for this context
19 * @param effect_count The number of effects in the list
20 * @param result_states_cpu The workbuffer of result states for the CPU for this context
21 * @param result_states_dsp The workbuffer of result states for the DSP for this context
22 * @param state_count The number of result states
23 */
24 void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
25 std::span<EffectResultState> result_states_cpu_,
26 std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count);
27
28 /**
29 * Get the EffectInfo for a given index
30 * @param index Which effect to return
31 * @return Pointer to the effect
32 */
33 EffectInfoBase& GetInfo(const u32 index);
34
35 /**
36 * Get the CPU result state for a given index
37 * @param index Which result to return
38 * @return Pointer to the effect result state
39 */
40 EffectResultState& GetResultState(const u32 index);
41
42 /**
43 * Get the DSP result state for a given index
44 * @param index Which result to return
45 * @return Pointer to the effect result state
46 */
47 EffectResultState& GetDspSharedResultState(const u32 index);
48
49 /**
50 * Get the number of effects in this context
51 * @return The number of effects
52 */
53 u32 GetCount() const;
54
55 /**
56 * Update the CPU and DSP result states for all effects
57 */
58 void UpdateStateByDspShared();
59
60private:
61 /// Workbuffer for all of the effects
62 std::span<EffectInfoBase> effect_infos{};
63 /// Number of effects in the workbuffer
64 u32 effect_count{};
65 /// Workbuffer of states for all effects, kept host-side and not directly modified, dsp states
66 /// are copied here on the next render frame
67 std::span<EffectResultState> result_states_cpu{};
68 /// Workbuffer of states for all effects, used by the AudioRenderer to track effect state
69 /// between calls
70 std::span<EffectResultState> result_states_dsp{};
71 /// Number of result states in the workbuffers
72 size_t dsp_state_count{};
73};
74
75} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/behavior/behavior_info.h"
10#include "audio_core/renderer/effect/effect_result_state.h"
11#include "audio_core/renderer/memory/address_info.h"
12#include "audio_core/renderer/memory/pool_mapper.h"
13#include "common/common_types.h"
14
15namespace AudioCore::AudioRenderer {
16/**
17 * Base of all effects. Holds various data and functions used for all derived effects.
18 * Should not be used directly.
19 */
20class EffectInfoBase {
21public:
22 enum class Type : u8 {
23 Invalid,
24 Mix,
25 Aux,
26 Delay,
27 Reverb,
28 I3dl2Reverb,
29 BiquadFilter,
30 LightLimiter,
31 Capture,
32 Compressor,
33 };
34
35 enum class UsageState {
36 Invalid,
37 New,
38 Enabled,
39 Disabled,
40 };
41
42 enum class OutStatus : u8 {
43 Invalid,
44 New,
45 Initialized,
46 Used,
47 Removed,
48 };
49
50 enum class ParameterState : u8 {
51 Initialized,
52 Updating,
53 Updated,
54 };
55
56 struct InParameterVersion1 {
57 /* 0x00 */ Type type;
58 /* 0x01 */ bool is_new;
59 /* 0x02 */ bool enabled;
60 /* 0x04 */ u32 mix_id;
61 /* 0x08 */ CpuAddr workbuffer;
62 /* 0x10 */ CpuAddr workbuffer_size;
63 /* 0x18 */ u32 process_order;
64 /* 0x1C */ char unk1C[0x4];
65 /* 0x20 */ std::array<u8, 0xA0> specific;
66 };
67 static_assert(sizeof(InParameterVersion1) == 0xC0,
68 "EffectInfoBase::InParameterVersion1 has the wrong size!");
69
70 struct InParameterVersion2 {
71 /* 0x00 */ Type type;
72 /* 0x01 */ bool is_new;
73 /* 0x02 */ bool enabled;
74 /* 0x04 */ u32 mix_id;
75 /* 0x08 */ CpuAddr workbuffer;
76 /* 0x10 */ CpuAddr workbuffer_size;
77 /* 0x18 */ u32 process_order;
78 /* 0x1C */ char unk1C[0x4];
79 /* 0x20 */ std::array<u8, 0xA0> specific;
80 };
81 static_assert(sizeof(InParameterVersion2) == 0xC0,
82 "EffectInfoBase::InParameterVersion2 has the wrong size!");
83
84 struct OutStatusVersion1 {
85 /* 0x00 */ OutStatus state;
86 /* 0x01 */ char unk01[0xF];
87 };
88 static_assert(sizeof(OutStatusVersion1) == 0x10,
89 "EffectInfoBase::OutStatusVersion1 has the wrong size!");
90
91 struct OutStatusVersion2 {
92 /* 0x00 */ OutStatus state;
93 /* 0x01 */ char unk01[0xF];
94 /* 0x10 */ EffectResultState result_state;
95 };
96 static_assert(sizeof(OutStatusVersion2) == 0x90,
97 "EffectInfoBase::OutStatusVersion2 has the wrong size!");
98
99 struct State {
100 std::array<u8, 0x500> buffer;
101 };
102 static_assert(sizeof(State) == 0x500, "EffectInfoBase::State has the wrong size!");
103
104 EffectInfoBase() {
105 Cleanup();
106 }
107
108 virtual ~EffectInfoBase() = default;
109
110 /**
111 * Cleanup this effect, resetting it to a starting state.
112 */
113 void Cleanup() {
114 type = Type::Invalid;
115 enabled = false;
116 mix_id = UnusedMixId;
117 process_order = InvalidProcessOrder;
118 buffer_unmapped = false;
119 parameter = {};
120 for (auto& workbuffer : workbuffers) {
121 workbuffer.Setup(CpuAddr(0), 0);
122 }
123 }
124
125 /**
126 * Forcibly unmap all assigned workbuffers from the AudioRenderer.
127 *
128 * @param pool_mapper - Mapper to unmap the buffers.
129 */
130 void ForceUnmapBuffers(const PoolMapper& pool_mapper) {
131 for (auto& workbuffer : workbuffers) {
132 if (workbuffer.GetReference(false) != 0) {
133 pool_mapper.ForceUnmapPointer(workbuffer);
134 }
135 }
136 }
137
138 /**
139 * Check if this effect is enabled.
140 *
141 * @return True if effect is enabled, otherwise false.
142 */
143 bool IsEnabled() const {
144 return enabled;
145 }
146
147 /**
148 * Check if this effect should not be generated.
149 *
150 * @return True if effect should be skipped, otherwise false.
151 */
152 bool ShouldSkip() const {
153 return buffer_unmapped;
154 }
155
156 /**
157 * Get the type of this effect.
158 *
159 * @return The type of this effect. See EffectInfoBase::Type
160 */
161 Type GetType() const {
162 return type;
163 }
164
165 /**
166 * Set the type of this effect.
167 *
168 * @param type_ - The new type of this effect.
169 */
170 void SetType(const Type type_) {
171 type = type_;
172 }
173
174 /**
175 * Get the mix id of this effect.
176 *
177 * @return Mix id of this effect.
178 */
179 s32 GetMixId() const {
180 return mix_id;
181 }
182
183 /**
184 * Get the processing order of this effect.
185 *
186 * @return Process order of this effect.
187 */
188 s32 GetProcessingOrder() const {
189 return process_order;
190 }
191
192 /**
193 * Get this effect's parameter data.
194 *
195 * @return Pointer to the parametter, must be cast to the correct type.
196 */
197 u8* GetParameter() {
198 return parameter.data();
199 }
200
201 /**
202 * Get this effect's parameter data.
203 *
204 * @return Pointer to the parametter, must be cast to the correct type.
205 */
206 u8* GetStateBuffer() {
207 return state.data();
208 }
209
210 /**
211 * Set this effect's usage state.
212 *
213 * @param usage - new usage state of this effect.
214 */
215 void SetUsage(const UsageState usage) {
216 usage_state = usage;
217 }
218
219 /**
220 * Check if this effects need to have its workbuffer information updated.
221 * Version 1.
222 *
223 * @param params - Input parameters.
224 * @return True if workbuffers need updating, otherwise false.
225 */
226 bool ShouldUpdateWorkBufferInfo(const InParameterVersion1& params) const {
227 return buffer_unmapped || params.is_new;
228 }
229
230 /**
231 * Check if this effects need to have its workbuffer information updated.
232 * Version 2.
233 *
234 * @param params - Input parameters.
235 * @return True if workbuffers need updating, otherwise false.
236 */
237 bool ShouldUpdateWorkBufferInfo(const InParameterVersion2& params) const {
238 return buffer_unmapped || params.is_new;
239 }
240
241 /**
242 * Get the current usage state of this effect.
243 *
244 * @return The current usage state.
245 */
246 UsageState GetUsage() const {
247 return usage_state;
248 }
249
250 /**
251 * Write the current state. Version 1.
252 *
253 * @param out_status - Status to write.
254 * @param renderer_active - Is the AudioRenderer active?
255 */
256 void StoreStatus(OutStatusVersion1& out_status, const bool renderer_active) const {
257 if (renderer_active) {
258 if (usage_state != UsageState::Disabled) {
259 out_status.state = OutStatus::Used;
260 } else {
261 out_status.state = OutStatus::Removed;
262 }
263 } else if (usage_state == UsageState::New) {
264 out_status.state = OutStatus::Used;
265 } else {
266 out_status.state = OutStatus::Removed;
267 }
268 }
269
270 /**
271 * Write the current state. Version 2.
272 *
273 * @param out_status - Status to write.
274 * @param renderer_active - Is the AudioRenderer active?
275 */
276 void StoreStatus(OutStatusVersion2& out_status, const bool renderer_active) const {
277 if (renderer_active) {
278 if (usage_state != UsageState::Disabled) {
279 out_status.state = OutStatus::Used;
280 } else {
281 out_status.state = OutStatus::Removed;
282 }
283 } else if (usage_state == UsageState::New) {
284 out_status.state = OutStatus::Used;
285 } else {
286 out_status.state = OutStatus::Removed;
287 }
288 }
289
290 /**
291 * Update the info with new parameters, version 1.
292 *
293 * @param error_info - Used to write call result code.
294 * @param in_params - New parameters to update the info with.
295 * @param pool_mapper - Pool for mapping buffers.
296 */
297 virtual void Update(BehaviorInfo::ErrorInfo& error_info,
298 [[maybe_unused]] const InParameterVersion1& params,
299 [[maybe_unused]] const PoolMapper& pool_mapper) {
300 error_info.error_code = ResultSuccess;
301 error_info.address = CpuAddr(0);
302 }
303
304 /**
305 * Update the info with new parameters, version 2.
306 *
307 * @param error_info - Used to write call result code.
308 * @param in_params - New parameters to update the info with.
309 * @param pool_mapper - Pool for mapping buffers.
310 */
311 virtual void Update(BehaviorInfo::ErrorInfo& error_info,
312 [[maybe_unused]] const InParameterVersion2& params,
313 [[maybe_unused]] const PoolMapper& pool_mapper) {
314 error_info.error_code = ResultSuccess;
315 error_info.address = CpuAddr(0);
316 }
317
318 /**
319 * Update the info after command generation. Usually only changes its state.
320 */
321 virtual void UpdateForCommandGeneration() {}
322
323 /**
324 * Initialize a new result state. Version 2 only, unused.
325 *
326 * @param result_state - Result state to initialize.
327 */
328 virtual void InitializeResultState([[maybe_unused]] EffectResultState& result_state) {}
329
330 /**
331 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
332 *
333 * @param cpu_state - Host-side result state to update.
334 * @param dsp_state - AudioRenderer-side result state to update from.
335 */
336 virtual void UpdateResultState([[maybe_unused]] EffectResultState& cpu_state,
337 [[maybe_unused]] EffectResultState& dsp_state) {}
338
339 /**
340 * Get a workbuffer assigned to this effect with the given index.
341 *
342 * @param index - Workbuffer index.
343 * @return Address of the buffer.
344 */
345 virtual CpuAddr GetWorkbuffer([[maybe_unused]] s32 index) {
346 return 0;
347 }
348
349 /**
350 * Get the first workbuffer assigned to this effect.
351 *
352 * @param index - Workbuffer index. Unused.
353 * @return Address of the buffer.
354 */
355 CpuAddr GetSingleBuffer([[maybe_unused]] const s32 index) {
356 if (enabled) {
357 return workbuffers[0].GetReference(true);
358 }
359
360 if (usage_state != UsageState::Disabled) {
361 const auto ref{workbuffers[0].GetReference(false)};
362 const auto size{workbuffers[0].GetSize()};
363 if (ref != 0 && size > 0) {
364 // Invalidate DSP cache
365 }
366 }
367 return 0;
368 }
369
370 /**
371 * Get the send buffer info, used by Aux and Capture.
372 *
373 * @return Address of the buffer info.
374 */
375 CpuAddr GetSendBufferInfo() const {
376 return send_buffer_info;
377 }
378
379 /**
380 * Get the send buffer, used by Aux and Capture.
381 *
382 * @return Address of the buffer.
383 */
384 CpuAddr GetSendBuffer() const {
385 return send_buffer;
386 }
387
388 /**
389 * Get the return buffer info, used by Aux and Capture.
390 *
391 * @return Address of the buffer info.
392 */
393 CpuAddr GetReturnBufferInfo() const {
394 return return_buffer_info;
395 }
396
397 /**
398 * Get the return buffer, used by Aux and Capture.
399 *
400 * @return Address of the buffer.
401 */
402 CpuAddr GetReturnBuffer() const {
403 return return_buffer;
404 }
405
406protected:
407 /// Type of this effect. May be changed
408 Type type{Type::Invalid};
409 /// Is this effect enabled?
410 bool enabled{};
411 /// Are this effect's buffers unmapped?
412 bool buffer_unmapped{};
413 /// Current usage state
414 UsageState usage_state{UsageState::Invalid};
415 /// Mix id of this effect
416 s32 mix_id{UnusedMixId};
417 /// Process order of this effect
418 s32 process_order{InvalidProcessOrder};
419 /// Workbuffers assigned to this effect
420 std::array<AddressInfo, 2> workbuffers{AddressInfo(CpuAddr(0), 0), AddressInfo(CpuAddr(0), 0)};
421 /// Aux/Capture buffer info for reading
422 CpuAddr send_buffer_info;
423 /// Aux/Capture buffer for reading
424 CpuAddr send_buffer;
425 /// Aux/Capture buffer info for writing
426 CpuAddr return_buffer_info;
427 /// Aux/Capture buffer for writing
428 CpuAddr return_buffer;
429 /// Parameters of this effect
430 std::array<u8, sizeof(InParameterVersion2)> parameter{};
431 /// State of this effect used by the AudioRenderer across calls
432 std::array<u8, sizeof(State)> state{};
433};
434
435} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/effect/aux_.h"
7#include "audio_core/renderer/effect/biquad_filter.h"
8#include "audio_core/renderer/effect/buffer_mixer.h"
9#include "audio_core/renderer/effect/capture.h"
10#include "audio_core/renderer/effect/compressor.h"
11#include "audio_core/renderer/effect/delay.h"
12#include "audio_core/renderer/effect/i3dl2.h"
13#include "audio_core/renderer/effect/light_limiter.h"
14#include "audio_core/renderer/effect/reverb.h"
15#include "common/common_types.h"
16
17namespace AudioCore::AudioRenderer {
18/**
19 * Reset an effect, and create a new one of the given type.
20 *
21 * @param effect - Effect to reset and re-construct.
22 * @param type - Type of the new effect to create.
23 */
24static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type) {
25 *effect = {};
26
27 switch (type) {
28 case EffectInfoBase::Type::Invalid:
29 std::construct_at<EffectInfoBase>(effect);
30 effect->SetType(EffectInfoBase::Type::Invalid);
31 break;
32 case EffectInfoBase::Type::Mix:
33 std::construct_at<BufferMixerInfo>(reinterpret_cast<BufferMixerInfo*>(effect));
34 effect->SetType(EffectInfoBase::Type::Mix);
35 break;
36 case EffectInfoBase::Type::Aux:
37 std::construct_at<AuxInfo>(reinterpret_cast<AuxInfo*>(effect));
38 effect->SetType(EffectInfoBase::Type::Aux);
39 break;
40 case EffectInfoBase::Type::Delay:
41 std::construct_at<DelayInfo>(reinterpret_cast<DelayInfo*>(effect));
42 effect->SetType(EffectInfoBase::Type::Delay);
43 break;
44 case EffectInfoBase::Type::Reverb:
45 std::construct_at<ReverbInfo>(reinterpret_cast<ReverbInfo*>(effect));
46 effect->SetType(EffectInfoBase::Type::Reverb);
47 break;
48 case EffectInfoBase::Type::I3dl2Reverb:
49 std::construct_at<I3dl2ReverbInfo>(reinterpret_cast<I3dl2ReverbInfo*>(effect));
50 effect->SetType(EffectInfoBase::Type::I3dl2Reverb);
51 break;
52 case EffectInfoBase::Type::BiquadFilter:
53 std::construct_at<BiquadFilterInfo>(reinterpret_cast<BiquadFilterInfo*>(effect));
54 effect->SetType(EffectInfoBase::Type::BiquadFilter);
55 break;
56 case EffectInfoBase::Type::LightLimiter:
57 std::construct_at<LightLimiterInfo>(reinterpret_cast<LightLimiterInfo*>(effect));
58 effect->SetType(EffectInfoBase::Type::LightLimiter);
59 break;
60 case EffectInfoBase::Type::Capture:
61 std::construct_at<CaptureInfo>(reinterpret_cast<CaptureInfo*>(effect));
62 effect->SetType(EffectInfoBase::Type::Capture);
63 break;
64 case EffectInfoBase::Type::Compressor:
65 std::construct_at<CompressorInfo>(reinterpret_cast<CompressorInfo*>(effect));
66 effect->SetType(EffectInfoBase::Type::Compressor);
67 break;
68 }
69}
70
71} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "common/common_types.h"
9
10namespace AudioCore::AudioRenderer {
11
12struct EffectResultState {
13 std::array<u8, 0x80> state;
14};
15
16} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/i3dl2.h"
5
6namespace AudioCore::AudioRenderer {
7
8void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 if (IsChannelCountValid(in_specific->channel_count_max)) {
14 const auto old_state{params->state};
15 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
16 mix_id = in_params.mix_id;
17 process_order = in_params.process_order;
18 enabled = in_params.enabled;
19
20 if (!IsChannelCountValid(in_specific->channel_count)) {
21 params->channel_count = params->channel_count_max;
22 }
23
24 if (!IsChannelCountValid(in_specific->channel_count) ||
25 old_state != ParameterState::Updated) {
26 params->state = old_state;
27 }
28
29 if (buffer_unmapped || in_params.is_new) {
30 usage_state = UsageState::New;
31 params->state = ParameterState::Initialized;
32 buffer_unmapped = !pool_mapper.TryAttachBuffer(
33 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
34 return;
35 }
36 }
37 error_info.error_code = ResultSuccess;
38 error_info.address = CpuAddr(0);
39}
40
41void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
42 const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
43 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
44 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
45
46 if (IsChannelCountValid(in_specific->channel_count_max)) {
47 const auto old_state{params->state};
48 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
49 mix_id = in_params.mix_id;
50 process_order = in_params.process_order;
51 enabled = in_params.enabled;
52
53 if (!IsChannelCountValid(in_specific->channel_count)) {
54 params->channel_count = params->channel_count_max;
55 }
56
57 if (!IsChannelCountValid(in_specific->channel_count) ||
58 old_state != ParameterState::Updated) {
59 params->state = old_state;
60 }
61
62 if (buffer_unmapped || in_params.is_new) {
63 usage_state = UsageState::New;
64 params->state = ParameterState::Initialized;
65 buffer_unmapped = !pool_mapper.TryAttachBuffer(
66 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
67 return;
68 }
69 }
70 error_info.error_code = ResultSuccess;
71 error_info.address = CpuAddr(0);
72}
73
74void I3dl2ReverbInfo::UpdateForCommandGeneration() {
75 if (enabled) {
76 usage_state = UsageState::Enabled;
77 } else {
78 usage_state = UsageState::Disabled;
79 }
80
81 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
82 params->state = ParameterState::Updated;
83}
84
85void I3dl2ReverbInfo::InitializeResultState(EffectResultState& result_state) {}
86
87void I3dl2ReverbInfo::UpdateResultState(EffectResultState& cpu_state,
88 EffectResultState& dsp_state) {}
89
90CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) {
91 return GetSingleBuffer(index);
92}
93
94} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8
9#include "audio_core/common/common.h"
10#include "audio_core/renderer/effect/effect_info_base.h"
11#include "common/common_types.h"
12#include "common/fixed_point.h"
13
14namespace AudioCore::AudioRenderer {
15
16class I3dl2ReverbInfo : public EffectInfoBase {
17public:
18 struct ParameterVersion1 {
19 /* 0x00 */ std::array<s8, MaxChannels> inputs;
20 /* 0x06 */ std::array<s8, MaxChannels> outputs;
21 /* 0x0C */ u16 channel_count_max;
22 /* 0x0E */ u16 channel_count;
23 /* 0x10 */ char unk10[0x4];
24 /* 0x14 */ u32 sample_rate;
25 /* 0x18 */ f32 room_HF_gain;
26 /* 0x1C */ f32 reference_HF;
27 /* 0x20 */ f32 late_reverb_decay_time;
28 /* 0x24 */ f32 late_reverb_HF_decay_ratio;
29 /* 0x28 */ f32 room_gain;
30 /* 0x2C */ f32 reflection_gain;
31 /* 0x30 */ f32 reverb_gain;
32 /* 0x34 */ f32 late_reverb_diffusion;
33 /* 0x38 */ f32 reflection_delay;
34 /* 0x3C */ f32 late_reverb_delay_time;
35 /* 0x40 */ f32 late_reverb_density;
36 /* 0x44 */ f32 dry_gain;
37 /* 0x48 */ ParameterState state;
38 /* 0x49 */ char unk49[0x3];
39 };
40 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
41 "I3dl2ReverbInfo::ParameterVersion1 has the wrong size!");
42
43 struct ParameterVersion2 {
44 /* 0x00 */ std::array<s8, MaxChannels> inputs;
45 /* 0x06 */ std::array<s8, MaxChannels> outputs;
46 /* 0x0C */ u16 channel_count_max;
47 /* 0x0E */ u16 channel_count;
48 /* 0x10 */ char unk10[0x4];
49 /* 0x14 */ u32 sample_rate;
50 /* 0x18 */ f32 room_HF_gain;
51 /* 0x1C */ f32 reference_HF;
52 /* 0x20 */ f32 late_reverb_decay_time;
53 /* 0x24 */ f32 late_reverb_HF_decay_ratio;
54 /* 0x28 */ f32 room_gain;
55 /* 0x2C */ f32 reflection_gain;
56 /* 0x30 */ f32 reverb_gain;
57 /* 0x34 */ f32 late_reverb_diffusion;
58 /* 0x38 */ f32 reflection_delay;
59 /* 0x3C */ f32 late_reverb_delay_time;
60 /* 0x40 */ f32 late_reverb_density;
61 /* 0x44 */ f32 dry_gain;
62 /* 0x48 */ ParameterState state;
63 /* 0x49 */ char unk49[0x3];
64 };
65 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
66 "I3dl2ReverbInfo::ParameterVersion2 has the wrong size!");
67
68 static constexpr u32 MaxDelayLines = 4;
69 static constexpr u32 MaxDelayTaps = 20;
70
71 struct I3dl2DelayLine {
72 void Initialize(const s32 delay_time) {
73 max_delay = delay_time;
74 buffer.resize(delay_time + 1, 0);
75 buffer_end = &buffer[delay_time];
76 output = &buffer[0];
77 SetDelay(delay_time);
78 wet_gain = 0.0f;
79 }
80
81 void SetDelay(const s32 delay_time) {
82 if (max_delay < delay_time) {
83 return;
84 }
85 delay = delay_time;
86 input = &buffer[(output - buffer.data() + delay) % (max_delay + 1)];
87 }
88
89 Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) {
90 Write(sample);
91
92 auto out_sample{Read()};
93
94 output++;
95 if (output >= buffer_end) {
96 output = buffer.data();
97 }
98
99 return out_sample;
100 }
101
102 Common::FixedPoint<50, 14> Read() {
103 return *output;
104 }
105
106 void Write(const Common::FixedPoint<50, 14> sample) {
107 *(input++) = sample;
108 if (input >= buffer_end) {
109 input = buffer.data();
110 }
111 }
112
113 Common::FixedPoint<50, 14> TapOut(const s32 index) {
114 auto out{input - (index + 1)};
115 if (out < buffer.data()) {
116 out += max_delay + 1;
117 }
118 return *out;
119 }
120
121 std::vector<Common::FixedPoint<50, 14>> buffer{};
122 Common::FixedPoint<50, 14>* buffer_end{};
123 s32 max_delay{};
124 Common::FixedPoint<50, 14>* input{};
125 Common::FixedPoint<50, 14>* output{};
126 s32 delay{};
127 f32 wet_gain{};
128 };
129
130 struct State {
131 f32 lowpass_0;
132 f32 lowpass_1;
133 f32 lowpass_2;
134 I3dl2DelayLine early_delay_line;
135 std::array<s32, MaxDelayTaps> early_tap_steps;
136 f32 early_gain;
137 f32 late_gain;
138 s32 early_to_late_taps;
139 std::array<I3dl2DelayLine, MaxDelayLines> fdn_delay_lines;
140 std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines0;
141 std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines1;
142 f32 last_reverb_echo;
143 I3dl2DelayLine center_delay_line;
144 std::array<std::array<f32, 3>, MaxDelayLines> lowpass_coeff;
145 std::array<f32, MaxDelayLines> shelf_filter;
146 f32 dry_gain;
147 };
148 static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
149 "I3dl2ReverbInfo::State is too large!");
150
151 /**
152 * Update the info with new parameters, version 1.
153 *
154 * @param error_info - Used to write call result code.
155 * @param in_params - New parameters to update the info with.
156 * @param pool_mapper - Pool for mapping buffers.
157 */
158 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
159 const PoolMapper& pool_mapper) override;
160
161 /**
162 * Update the info with new parameters, version 2.
163 *
164 * @param error_info - Used to write call result code.
165 * @param in_params - New parameters to update the info with.
166 * @param pool_mapper - Pool for mapping buffers.
167 */
168 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
169 const PoolMapper& pool_mapper) override;
170
171 /**
172 * Update the info after command generation. Usually only changes its state.
173 */
174 void UpdateForCommandGeneration() override;
175
176 /**
177 * Initialize a new result state. Version 2 only, unused.
178 *
179 * @param result_state - Result state to initialize.
180 */
181 void InitializeResultState(EffectResultState& result_state) override;
182
183 /**
184 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
185 *
186 * @param cpu_state - Host-side result state to update.
187 * @param dsp_state - AudioRenderer-side result state to update from.
188 */
189 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
190
191 /**
192 * Get a workbuffer assigned to this effect with the given index.
193 *
194 * @param index - Workbuffer index.
195 * @return Address of the buffer.
196 */
197 CpuAddr GetWorkbuffer(s32 index) override;
198};
199
200} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/light_limiter.h"
5
6namespace AudioCore::AudioRenderer {
7
8void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
14 mix_id = in_params.mix_id;
15 process_order = in_params.process_order;
16 enabled = in_params.enabled;
17
18 if (buffer_unmapped || in_params.is_new) {
19 usage_state = UsageState::New;
20 params->state = ParameterState::Initialized;
21 buffer_unmapped = !pool_mapper.TryAttachBuffer(
22 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
23 } else {
24 error_info.error_code = ResultSuccess;
25 error_info.address = CpuAddr(0);
26 }
27}
28
29void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
30 const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
31 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
32 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
33
34 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
35 mix_id = in_params.mix_id;
36 process_order = in_params.process_order;
37 enabled = in_params.enabled;
38
39 if (buffer_unmapped || in_params.is_new) {
40 usage_state = UsageState::New;
41 params->state = ParameterState::Initialized;
42 buffer_unmapped = !pool_mapper.TryAttachBuffer(
43 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
44 } else {
45 error_info.error_code = ResultSuccess;
46 error_info.address = CpuAddr(0);
47 }
48}
49
50void LightLimiterInfo::UpdateForCommandGeneration() {
51 if (enabled) {
52 usage_state = UsageState::Enabled;
53 } else {
54 usage_state = UsageState::Disabled;
55 }
56
57 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
58 params->state = ParameterState::Updated;
59 params->statistics_reset_required = false;
60}
61
62void LightLimiterInfo::InitializeResultState(EffectResultState& result_state) {
63 auto result_state_{reinterpret_cast<StatisticsInternal*>(result_state.state.data())};
64
65 result_state_->channel_max_sample.fill(0);
66 result_state_->channel_compression_gain_min.fill(1.0f);
67}
68
69void LightLimiterInfo::UpdateResultState(EffectResultState& cpu_state,
70 EffectResultState& dsp_state) {
71 auto cpu_statistics{reinterpret_cast<StatisticsInternal*>(cpu_state.state.data())};
72 auto dsp_statistics{reinterpret_cast<StatisticsInternal*>(dsp_state.state.data())};
73
74 *cpu_statistics = *dsp_statistics;
75}
76
77CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) {
78 return GetSingleBuffer(index);
79}
80
81} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8
9#include "audio_core/common/common.h"
10#include "audio_core/renderer/effect/effect_info_base.h"
11#include "common/common_types.h"
12#include "common/fixed_point.h"
13
14namespace AudioCore::AudioRenderer {
15
16class LightLimiterInfo : public EffectInfoBase {
17public:
18 enum class ProcessingMode {
19 Mode0,
20 Mode1,
21 };
22
23 struct ParameterVersion1 {
24 /* 0x00 */ std::array<s8, MaxChannels> inputs;
25 /* 0x06 */ std::array<s8, MaxChannels> outputs;
26 /* 0x0C */ u16 channel_count_max;
27 /* 0x0E */ u16 channel_count;
28 /* 0x0C */ u32 sample_rate;
29 /* 0x14 */ s32 look_ahead_time_max;
30 /* 0x18 */ s32 attack_time;
31 /* 0x1C */ s32 release_time;
32 /* 0x20 */ s32 look_ahead_time;
33 /* 0x24 */ f32 attack_coeff;
34 /* 0x28 */ f32 release_coeff;
35 /* 0x2C */ f32 threshold;
36 /* 0x30 */ f32 input_gain;
37 /* 0x34 */ f32 output_gain;
38 /* 0x38 */ s32 look_ahead_samples_min;
39 /* 0x3C */ s32 look_ahead_samples_max;
40 /* 0x40 */ ParameterState state;
41 /* 0x41 */ bool statistics_enabled;
42 /* 0x42 */ bool statistics_reset_required;
43 /* 0x43 */ ProcessingMode processing_mode;
44 };
45 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
46 "LightLimiterInfo::ParameterVersion1 has the wrong size!");
47
48 struct ParameterVersion2 {
49 /* 0x00 */ std::array<s8, MaxChannels> inputs;
50 /* 0x06 */ std::array<s8, MaxChannels> outputs;
51 /* 0x0C */ u16 channel_count_max;
52 /* 0x0E */ u16 channel_count;
53 /* 0x0C */ u32 sample_rate;
54 /* 0x14 */ s32 look_ahead_time_max;
55 /* 0x18 */ s32 attack_time;
56 /* 0x1C */ s32 release_time;
57 /* 0x20 */ s32 look_ahead_time;
58 /* 0x24 */ f32 attack_coeff;
59 /* 0x28 */ f32 release_coeff;
60 /* 0x2C */ f32 threshold;
61 /* 0x30 */ f32 input_gain;
62 /* 0x34 */ f32 output_gain;
63 /* 0x38 */ s32 look_ahead_samples_min;
64 /* 0x3C */ s32 look_ahead_samples_max;
65 /* 0x40 */ ParameterState state;
66 /* 0x41 */ bool statistics_enabled;
67 /* 0x42 */ bool statistics_reset_required;
68 /* 0x43 */ ProcessingMode processing_mode;
69 };
70 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
71 "LightLimiterInfo::ParameterVersion2 has the wrong size!");
72
73 struct State {
74 std::array<Common::FixedPoint<49, 15>, MaxChannels> samples_average;
75 std::array<Common::FixedPoint<49, 15>, MaxChannels> compression_gain;
76 std::array<s32, MaxChannels> look_ahead_sample_offsets;
77 std::array<std::vector<Common::FixedPoint<49, 15>>, MaxChannels> look_ahead_sample_buffers;
78 };
79 static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
80 "LightLimiterInfo::State has the wrong size!");
81
82 struct StatisticsInternal {
83 /* 0x00 */ std::array<f32, MaxChannels> channel_max_sample;
84 /* 0x18 */ std::array<f32, MaxChannels> channel_compression_gain_min;
85 };
86 static_assert(sizeof(StatisticsInternal) == 0x30,
87 "LightLimiterInfo::StatisticsInternal has the wrong size!");
88
89 /**
90 * Update the info with new parameters, version 1.
91 *
92 * @param error_info - Used to write call result code.
93 * @param in_params - New parameters to update the info with.
94 * @param pool_mapper - Pool for mapping buffers.
95 */
96 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
97 const PoolMapper& pool_mapper) override;
98
99 /**
100 * Update the info with new parameters, version 2.
101 *
102 * @param error_info - Used to write call result code.
103 * @param in_params - New parameters to update the info with.
104 * @param pool_mapper - Pool for mapping buffers.
105 */
106 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
107 const PoolMapper& pool_mapper) override;
108
109 /**
110 * Update the info after command generation. Usually only changes its state.
111 */
112 void UpdateForCommandGeneration() override;
113
114 /**
115 * Initialize a new limiter statistics result state. Version 2 only.
116 *
117 * @param result_state - Result state to initialize.
118 */
119 void InitializeResultState(EffectResultState& result_state) override;
120
121 /**
122 * Update the host-side limiter statistics with the ADSP-side one. Version 2 only.
123 *
124 * @param cpu_state - Host-side result state to update.
125 * @param dsp_state - AudioRenderer-side result state to update from.
126 */
127 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
128
129 /**
130 * Get a workbuffer assigned to this effect with the given index.
131 *
132 * @param index - Workbuffer index.
133 * @return Address of the buffer.
134 */
135 CpuAddr GetWorkbuffer(s32 index) override;
136};
137
138} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/reverb.h"
5
6namespace AudioCore::AudioRenderer {
7
8void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
9 const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 if (IsChannelCountValid(in_specific->channel_count_max)) {
14 const auto old_state{params->state};
15 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
16 mix_id = in_params.mix_id;
17 process_order = in_params.process_order;
18 enabled = in_params.enabled;
19
20 if (!IsChannelCountValid(in_specific->channel_count)) {
21 params->channel_count = params->channel_count_max;
22 }
23
24 if (!IsChannelCountValid(in_specific->channel_count) ||
25 old_state != ParameterState::Updated) {
26 params->state = old_state;
27 }
28
29 if (buffer_unmapped || in_params.is_new) {
30 usage_state = UsageState::New;
31 params->state = ParameterState::Initialized;
32 buffer_unmapped = !pool_mapper.TryAttachBuffer(
33 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
34 return;
35 }
36 }
37 error_info.error_code = ResultSuccess;
38 error_info.address = CpuAddr(0);
39}
40
41void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
42 const PoolMapper& pool_mapper) {
43 auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
44 auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
45
46 if (IsChannelCountValid(in_specific->channel_count_max)) {
47 const auto old_state{params->state};
48 std::memcpy(params, in_specific, sizeof(ParameterVersion2));
49 mix_id = in_params.mix_id;
50 process_order = in_params.process_order;
51 enabled = in_params.enabled;
52
53 if (!IsChannelCountValid(in_specific->channel_count)) {
54 params->channel_count = params->channel_count_max;
55 }
56
57 if (!IsChannelCountValid(in_specific->channel_count) ||
58 old_state != ParameterState::Updated) {
59 params->state = old_state;
60 }
61
62 if (buffer_unmapped || in_params.is_new) {
63 usage_state = UsageState::New;
64 params->state = ParameterState::Initialized;
65 buffer_unmapped = !pool_mapper.TryAttachBuffer(
66 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
67 return;
68 }
69 }
70 error_info.error_code = ResultSuccess;
71 error_info.address = CpuAddr(0);
72}
73
74void ReverbInfo::UpdateForCommandGeneration() {
75 if (enabled) {
76 usage_state = UsageState::Enabled;
77 } else {
78 usage_state = UsageState::Disabled;
79 }
80
81 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
82 params->state = ParameterState::Updated;
83}
84
85void ReverbInfo::InitializeResultState(EffectResultState& result_state) {}
86
87void ReverbInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
88
89CpuAddr ReverbInfo::GetWorkbuffer(s32 index) {
90 return GetSingleBuffer(index);
91}
92
93} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8
9#include "audio_core/common/common.h"
10#include "audio_core/renderer/effect/effect_info_base.h"
11#include "common/common_types.h"
12#include "common/fixed_point.h"
13
14namespace AudioCore::AudioRenderer {
15
16class ReverbInfo : public EffectInfoBase {
17public:
18 struct ParameterVersion1 {
19 /* 0x00 */ std::array<s8, MaxChannels> inputs;
20 /* 0x06 */ std::array<s8, MaxChannels> outputs;
21 /* 0x0C */ u16 channel_count_max;
22 /* 0x0E */ u16 channel_count;
23 /* 0x10 */ u32 sample_rate;
24 /* 0x14 */ u32 early_mode;
25 /* 0x18 */ s32 early_gain;
26 /* 0x1C */ s32 pre_delay;
27 /* 0x20 */ s32 late_mode;
28 /* 0x24 */ s32 late_gain;
29 /* 0x28 */ s32 decay_time;
30 /* 0x2C */ s32 high_freq_Decay_ratio;
31 /* 0x30 */ s32 colouration;
32 /* 0x34 */ s32 base_gain;
33 /* 0x38 */ s32 wet_gain;
34 /* 0x3C */ s32 dry_gain;
35 /* 0x40 */ ParameterState state;
36 };
37 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
38 "ReverbInfo::ParameterVersion1 has the wrong size!");
39
40 struct ParameterVersion2 {
41 /* 0x00 */ std::array<s8, MaxChannels> inputs;
42 /* 0x06 */ std::array<s8, MaxChannels> outputs;
43 /* 0x0C */ u16 channel_count_max;
44 /* 0x0E */ u16 channel_count;
45 /* 0x10 */ u32 sample_rate;
46 /* 0x14 */ u32 early_mode;
47 /* 0x18 */ s32 early_gain;
48 /* 0x1C */ s32 pre_delay;
49 /* 0x20 */ s32 late_mode;
50 /* 0x24 */ s32 late_gain;
51 /* 0x28 */ s32 decay_time;
52 /* 0x2C */ s32 high_freq_decay_ratio;
53 /* 0x30 */ s32 colouration;
54 /* 0x34 */ s32 base_gain;
55 /* 0x38 */ s32 wet_gain;
56 /* 0x3C */ s32 dry_gain;
57 /* 0x40 */ ParameterState state;
58 };
59 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
60 "ReverbInfo::ParameterVersion2 has the wrong size!");
61
62 static constexpr u32 MaxDelayLines = 4;
63 static constexpr u32 MaxDelayTaps = 10;
64 static constexpr u32 NumEarlyModes = 5;
65 static constexpr u32 NumLateModes = 5;
66
67 struct ReverbDelayLine {
68 void Initialize(const s32 delay_time, const f32 decay_rate) {
69 buffer.resize(delay_time + 1, 0);
70 buffer_end = &buffer[delay_time];
71 output = &buffer[0];
72 decay = decay_rate;
73 sample_count_max = delay_time;
74 SetDelay(delay_time);
75 }
76
77 void SetDelay(const s32 delay_time) {
78 if (sample_count_max < delay_time) {
79 return;
80 }
81 sample_count = delay_time;
82 input = &buffer[(output - buffer.data() + sample_count) % (sample_count_max + 1)];
83 }
84
85 Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) {
86 Write(sample);
87
88 auto out_sample{Read()};
89
90 output++;
91 if (output >= buffer_end) {
92 output = buffer.data();
93 }
94
95 return out_sample;
96 }
97
98 Common::FixedPoint<50, 14> Read() {
99 return *output;
100 }
101
102 void Write(const Common::FixedPoint<50, 14> sample) {
103 *(input++) = sample;
104 if (input >= buffer_end) {
105 input = buffer.data();
106 }
107 }
108
109 Common::FixedPoint<50, 14> TapOut(const s32 index) {
110 auto out{input - (index + 1)};
111 if (out < buffer.data()) {
112 out += sample_count;
113 }
114 return *out;
115 }
116
117 s32 sample_count{};
118 s32 sample_count_max{};
119 std::vector<Common::FixedPoint<50, 14>> buffer{};
120 Common::FixedPoint<50, 14>* buffer_end;
121 Common::FixedPoint<50, 14>* input{};
122 Common::FixedPoint<50, 14>* output{};
123 Common::FixedPoint<50, 14> decay{};
124 };
125
126 struct State {
127 ReverbDelayLine pre_delay_line;
128 ReverbDelayLine center_delay_line;
129 std::array<s32, MaxDelayTaps> early_delay_times;
130 std::array<Common::FixedPoint<50, 14>, MaxDelayTaps> early_gains;
131 s32 pre_delay_time;
132 std::array<ReverbDelayLine, MaxDelayLines> decay_delay_lines;
133 std::array<ReverbDelayLine, MaxDelayLines> fdn_delay_lines;
134 std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_gain;
135 std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_prev_gain;
136 std::array<Common::FixedPoint<50, 14>, MaxDelayLines> prev_feedback_output;
137 };
138 static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
139 "ReverbInfo::State is too large!");
140
141 /**
142 * Update the info with new parameters, version 1.
143 *
144 * @param error_info - Used to write call result code.
145 * @param in_params - New parameters to update the info with.
146 * @param pool_mapper - Pool for mapping buffers.
147 */
148 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
149 const PoolMapper& pool_mapper) override;
150
151 /**
152 * Update the info with new parameters, version 2.
153 *
154 * @param error_info - Used to write call result code.
155 * @param in_params - New parameters to update the info with.
156 * @param pool_mapper - Pool for mapping buffers.
157 */
158 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
159 const PoolMapper& pool_mapper) override;
160
161 /**
162 * Update the info after command generation. Usually only changes its state.
163 */
164 void UpdateForCommandGeneration() override;
165
166 /**
167 * Initialize a new result state. Version 2 only, unused.
168 *
169 * @param result_state - Result state to initialize.
170 */
171 void InitializeResultState(EffectResultState& result_state) override;
172
173 /**
174 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
175 *
176 * @param cpu_state - Host-side result state to update.
177 * @param dsp_state - AudioRenderer-side result state to update from.
178 */
179 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
180
181 /**
182 * Get a workbuffer assigned to this effect with the given index.
183 *
184 * @param index - Workbuffer index.
185 * @return Address of the buffer.
186 */
187 CpuAddr GetWorkbuffer(s32 index) override;
188};
189
190} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/memory/memory_pool_info.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10
11/**
12 * Represents a region of mapped or unmapped memory.
13 */
14class AddressInfo {
15public:
16 AddressInfo() = default;
17 AddressInfo(CpuAddr cpu_address_, u64 size_) : cpu_address{cpu_address_}, size{size_} {}
18
19 /**
20 * Setup a new AddressInfo.
21 *
22 * @param cpu_address - The CPU address of this region.
23 * @param size - The size of this region.
24 */
25 void Setup(CpuAddr cpu_address_, u64 size_) {
26 cpu_address = cpu_address_;
27 size = size_;
28 memory_pool = nullptr;
29 dsp_address = 0;
30 }
31
32 /**
33 * Get the CPU address.
34 *
35 * @return The CpuAddr address
36 */
37 CpuAddr GetCpuAddr() const {
38 return cpu_address;
39 }
40
41 /**
42 * Assign this region to a memory pool.
43 *
44 * @param memory_pool_ - Memory pool to assign.
45 * @return The CpuAddr address of this region.
46 */
47 void SetPool(MemoryPoolInfo* memory_pool_) {
48 memory_pool = memory_pool_;
49 }
50
51 /**
52 * Get the size of this region.
53 *
54 * @return The size of this region.
55 */
56 u64 GetSize() const {
57 return size;
58 }
59
60 /**
61 * Get the ADSP address for this region.
62 *
63 * @return The ADSP address for this region.
64 */
65 CpuAddr GetForceMappedDspAddr() const {
66 return dsp_address;
67 }
68
69 /**
70 * Set the ADSP address for this region.
71 *
72 * @param dsp_addr - The new ADSP address for this region.
73 */
74 void SetForceMappedDspAddr(CpuAddr dsp_addr) {
75 dsp_address = dsp_addr;
76 }
77
78 /**
79 * Check whether this region has an active memory pool.
80 *
81 * @return True if this region has a mapped memory pool, otherwise false.
82 */
83 bool HasMappedMemoryPool() const {
84 return memory_pool != nullptr && memory_pool->GetDspAddress() != 0;
85 }
86
87 /**
88 * Check whether this region is mapped to the ADSP.
89 *
90 * @return True if this region is mapped, otherwise false.
91 */
92 bool IsMapped() const {
93 return HasMappedMemoryPool() || dsp_address != 0;
94 }
95
96 /**
97 * Get a usable reference to this region of memory.
98 *
99 * @param mark_in_use - Whether this region should be marked as being in use.
100 * @return A valid memory address if valid, otherwise 0.
101 */
102 CpuAddr GetReference(bool mark_in_use) {
103 if (!HasMappedMemoryPool()) {
104 return dsp_address;
105 }
106
107 if (mark_in_use) {
108 memory_pool->SetUsed(true);
109 }
110
111 return memory_pool->Translate(cpu_address, size);
112 }
113
114private:
115 /// CPU address of this region
116 CpuAddr cpu_address;
117 /// Size of this region
118 u64 size;
119 /// The memory this region is mapped to
120 MemoryPoolInfo* memory_pool;
121 /// ADSP address of this region
122 CpuAddr dsp_address;
123};
124
125} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/memory/memory_pool_info.h"
5
6namespace AudioCore::AudioRenderer {
7
8CpuAddr MemoryPoolInfo::GetCpuAddress() const {
9 return cpu_address;
10}
11
12CpuAddr MemoryPoolInfo::GetDspAddress() const {
13 return dsp_address;
14}
15
16u64 MemoryPoolInfo::GetSize() const {
17 return size;
18}
19
20MemoryPoolInfo::Location MemoryPoolInfo::GetLocation() const {
21 return location;
22}
23
24void MemoryPoolInfo::SetCpuAddress(const CpuAddr address, const u64 size_) {
25 cpu_address = address;
26 size = size_;
27}
28
29void MemoryPoolInfo::SetDspAddress(const CpuAddr address) {
30 dsp_address = address;
31}
32
33bool MemoryPoolInfo::Contains(const CpuAddr address_, const u64 size_) const {
34 return cpu_address <= address_ && (address_ + size_) <= (cpu_address + size);
35}
36
37bool MemoryPoolInfo::IsMapped() const {
38 return dsp_address != 0;
39}
40
41CpuAddr MemoryPoolInfo::Translate(const CpuAddr address, const u64 size_) const {
42 if (!Contains(address, size_)) {
43 return 0;
44 }
45
46 if (!IsMapped()) {
47 return 0;
48 }
49
50 return dsp_address + (address - cpu_address);
51}
52
53void MemoryPoolInfo::SetUsed(const bool used) {
54 in_use = used;
55}
56
57bool MemoryPoolInfo::IsUsed() const {
58 return in_use;
59}
60
61} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12/**
13 * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper).
14 */
15class MemoryPoolInfo {
16public:
17 /**
18 * The location of this pool.
19 * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper).
20 * DSP pools are mapped in the current process sysmodule.
21 */
22 enum class Location {
23 CPU = 1,
24 DSP = 2,
25 };
26
27 /**
28 * Current state of the pool
29 */
30 enum class State {
31 Invalid,
32 Aquired,
33 RequestDetach,
34 Detached,
35 RequestAttach,
36 Attached,
37 Released,
38 };
39
40 /**
41 * Result code for updating the pool (See InfoUpdater::Update)
42 */
43 enum class ResultState {
44 Success,
45 BadParam,
46 MapFailed,
47 InUse,
48 };
49
50 /**
51 * Input parameters coming from the game which are used to update current pools
52 * (See InfoUpdater::Update)
53 */
54 struct InParameter {
55 /* 0x00 */ u64 address;
56 /* 0x08 */ u64 size;
57 /* 0x10 */ State state;
58 /* 0x14 */ bool in_use;
59 /* 0x18 */ char unk18[0x8];
60 };
61 static_assert(sizeof(InParameter) == 0x20, "MemoryPoolInfo::InParameter has the wrong size!");
62
63 /**
64 * Output status sent back to the game on update (See InfoUpdater::Update)
65 */
66 struct OutStatus {
67 /* 0x00 */ State state;
68 /* 0x04 */ char unk04[0xC];
69 };
70 static_assert(sizeof(OutStatus) == 0x10, "MemoryPoolInfo::OutStatus has the wrong size!");
71
72 MemoryPoolInfo() = default;
73 MemoryPoolInfo(Location location_) : location{location_} {}
74
75 /**
76 * Get the CPU address for this pool.
77 *
78 * @return The CPU address of this pool.
79 */
80 CpuAddr GetCpuAddress() const;
81
82 /**
83 * Get the DSP address for this pool.
84 *
85 * @return The DSP address of this pool.
86 */
87 CpuAddr GetDspAddress() const;
88
89 /**
90 * Get the size of this pool.
91 *
92 * @return The size of this pool.
93 */
94 u64 GetSize() const;
95
96 /**
97 * Get the location of this pool.
98 *
99 * @return The location for the pool (see MemoryPoolInfo::Location).
100 */
101 Location GetLocation() const;
102
103 /**
104 * Set the CPU address for this pool.
105 *
106 * @param address - The new CPU address for this pool.
107 * @param size - The new size for this pool.
108 */
109 void SetCpuAddress(CpuAddr address, u64 size);
110
111 /**
112 * Set the DSP address for this pool.
113 *
114 * @param address - The new DSP address for this pool.
115 */
116 void SetDspAddress(CpuAddr address);
117
118 /**
119 * Check whether the pool contains a given range.
120 *
121 * @param address - The buffer address to look for.
122 * @param size - The size of the given buffer.
123 * @return True if the range is within this pool, otherwise false.
124 */
125 bool Contains(CpuAddr address, u64 size) const;
126
127 /**
128 * Check whether this pool is mapped, which is when the dsp address is set.
129 *
130 * @return True if the pool is mapped, otherwise false.
131 */
132 bool IsMapped() const;
133
134 /**
135 * Translates a given CPU range into a relative offset for the DSP.
136 *
137 * @param address - The buffer address to look for.
138 * @param size - The size of the given buffer.
139 * @return Pointer to the DSP-mapped memory.
140 */
141 CpuAddr Translate(CpuAddr address, u64 size) const;
142
143 /**
144 * Set or unset whether this memory pool is in use.
145 *
146 * @param used - Use state for this pool.
147 */
148 void SetUsed(bool used);
149
150 /**
151 * Get whether this pool is in use.
152 *
153 * @return True if in use, otherwise false.
154 */
155 bool IsUsed() const;
156
157private:
158 /// Base address for the CPU-side memory
159 CpuAddr cpu_address{};
160 /// Base address for the DSP-side memory
161 CpuAddr dsp_address{};
162 /// Size of this pool
163 u64 size{};
164 /// Location of this pool, either CPU or DSP
165 Location location{Location::DSP};
166 /// If this pool is in use
167 bool in_use{};
168};
169
170} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/memory/address_info.h"
5#include "audio_core/renderer/memory/pool_mapper.h"
6#include "core/hle/kernel/k_process.h"
7#include "core/hle/kernel/svc.h"
8
9namespace AudioCore::AudioRenderer {
10
11PoolMapper::PoolMapper(u32 process_handle_, bool force_map_)
12 : process_handle{process_handle_}, force_map{force_map_} {}
13
14PoolMapper::PoolMapper(u32 process_handle_, std::span<MemoryPoolInfo> pool_infos_, u32 pool_count_,
15 bool force_map_)
16 : process_handle{process_handle_}, pool_infos{pool_infos_.data()},
17 pool_count{pool_count_}, force_map{force_map_} {}
18
19void PoolMapper::ClearUseState(std::span<MemoryPoolInfo> pools, const u32 count) {
20 for (u32 i = 0; i < count; i++) {
21 pools[i].SetUsed(false);
22 }
23}
24
25MemoryPoolInfo* PoolMapper::FindMemoryPool(MemoryPoolInfo* pools, const u64 count,
26 const CpuAddr address, const u64 size) const {
27 auto pool{pools};
28 for (u64 i = 0; i < count; i++, pool++) {
29 if (pool->Contains(address, size)) {
30 return pool;
31 }
32 }
33 return nullptr;
34}
35
36MemoryPoolInfo* PoolMapper::FindMemoryPool(const CpuAddr address, const u64 size) const {
37 auto pool{pool_infos};
38 for (u64 i = 0; i < pool_count; i++, pool++) {
39 if (pool->Contains(address, size)) {
40 return pool;
41 }
42 }
43 return nullptr;
44}
45
46bool PoolMapper::FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools,
47 const u32 count) const {
48 if (address_info.GetCpuAddr() == 0) {
49 address_info.SetPool(nullptr);
50 return false;
51 }
52
53 auto found_pool{
54 FindMemoryPool(pools, count, address_info.GetCpuAddr(), address_info.GetSize())};
55 if (found_pool != nullptr) {
56 address_info.SetPool(found_pool);
57 return true;
58 }
59
60 if (force_map) {
61 address_info.SetForceMappedDspAddr(address_info.GetCpuAddr());
62 } else {
63 address_info.SetPool(nullptr);
64 }
65
66 return false;
67}
68
69bool PoolMapper::FillDspAddr(AddressInfo& address_info) const {
70 if (address_info.GetCpuAddr() == 0) {
71 address_info.SetPool(nullptr);
72 return false;
73 }
74
75 auto found_pool{FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())};
76 if (found_pool != nullptr) {
77 address_info.SetPool(found_pool);
78 return true;
79 }
80
81 if (force_map) {
82 address_info.SetForceMappedDspAddr(address_info.GetCpuAddr());
83 } else {
84 address_info.SetPool(nullptr);
85 }
86
87 return false;
88}
89
90bool PoolMapper::TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info,
91 const CpuAddr address, const u64 size) const {
92 address_info.Setup(address, size);
93
94 if (!FillDspAddr(address_info)) {
95 error_info.error_code = Service::Audio::ERR_POOL_MAPPING_FAILED;
96 error_info.address = address;
97 return force_map;
98 }
99
100 error_info.error_code = ResultSuccess;
101 error_info.address = CpuAddr(0);
102 return true;
103}
104
105bool PoolMapper::IsForceMapEnabled() const {
106 return force_map;
107}
108
109u32 PoolMapper::GetProcessHandle(const MemoryPoolInfo* pool) const {
110 switch (pool->GetLocation()) {
111 case MemoryPoolInfo::Location::CPU:
112 return process_handle;
113 case MemoryPoolInfo::Location::DSP:
114 return Kernel::Svc::CurrentProcess;
115 }
116 LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location!");
117 return Kernel::Svc::CurrentProcess;
118}
119
120bool PoolMapper::Map([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr,
121 [[maybe_unused]] const u64 size) const {
122 // nn::audio::dsp::MapUserPointer(handle, cpu_addr, size);
123 return true;
124}
125
126bool PoolMapper::Map(MemoryPoolInfo& pool) const {
127 switch (pool.GetLocation()) {
128 case MemoryPoolInfo::Location::CPU:
129 // Map with process_handle
130 pool.SetDspAddress(pool.GetCpuAddress());
131 return true;
132 case MemoryPoolInfo::Location::DSP:
133 // Map with Kernel::Svc::CurrentProcess
134 pool.SetDspAddress(pool.GetCpuAddress());
135 return true;
136 default:
137 LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!",
138 static_cast<u32>(pool.GetLocation()));
139 return false;
140 }
141}
142
143bool PoolMapper::Unmap([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr,
144 [[maybe_unused]] const u64 size) const {
145 // nn::audio::dsp::UnmapUserPointer(handle, cpu_addr, size);
146 return true;
147}
148
149bool PoolMapper::Unmap(MemoryPoolInfo& pool) const {
150 [[maybe_unused]] u32 handle{0};
151
152 switch (pool.GetLocation()) {
153 case MemoryPoolInfo::Location::CPU:
154 handle = process_handle;
155 break;
156 case MemoryPoolInfo::Location::DSP:
157 handle = Kernel::Svc::CurrentProcess;
158 break;
159 }
160 // nn::audio::dsp::UnmapUserPointer(handle, pool->cpu_address, pool->size);
161 pool.SetCpuAddress(0, 0);
162 pool.SetDspAddress(0);
163 return true;
164}
165
166void PoolMapper::ForceUnmapPointer(const AddressInfo& address_info) const {
167 if (force_map) {
168 [[maybe_unused]] auto found_pool{
169 FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())};
170 // nn::audio::dsp::UnmapUserPointer(this->processHandle, address_info.GetCpuAddr(), 0);
171 }
172}
173
174MemoryPoolInfo::ResultState PoolMapper::Update(MemoryPoolInfo& pool,
175 const MemoryPoolInfo::InParameter& in_params,
176 MemoryPoolInfo::OutStatus& out_params) const {
177 if (in_params.state != MemoryPoolInfo::State::RequestAttach &&
178 in_params.state != MemoryPoolInfo::State::RequestDetach) {
179 return MemoryPoolInfo::ResultState::Success;
180 }
181
182 if (in_params.address == 0 || in_params.size == 0 || !Common::Is4KBAligned(in_params.address) ||
183 !Common::Is4KBAligned(in_params.size)) {
184 return MemoryPoolInfo::ResultState::BadParam;
185 }
186
187 switch (in_params.state) {
188 case MemoryPoolInfo::State::RequestAttach:
189 pool.SetCpuAddress(in_params.address, in_params.size);
190
191 Map(pool);
192
193 if (pool.IsMapped()) {
194 out_params.state = MemoryPoolInfo::State::Attached;
195 return MemoryPoolInfo::ResultState::Success;
196 }
197 pool.SetCpuAddress(0, 0);
198 return MemoryPoolInfo::ResultState::MapFailed;
199
200 case MemoryPoolInfo::State::RequestDetach:
201 if (pool.GetCpuAddress() != in_params.address || pool.GetSize() != in_params.size) {
202 return MemoryPoolInfo::ResultState::BadParam;
203 }
204
205 if (pool.IsUsed()) {
206 return MemoryPoolInfo::ResultState::InUse;
207 }
208
209 Unmap(pool);
210
211 pool.SetCpuAddress(0, 0);
212 pool.SetDspAddress(0);
213 out_params.state = MemoryPoolInfo::State::Detached;
214 return MemoryPoolInfo::ResultState::Success;
215
216 default:
217 LOG_ERROR(Service_Audio, "Invalid MemoryPoolInfo::State!");
218 break;
219 }
220
221 return MemoryPoolInfo::ResultState::Success;
222}
223
224bool PoolMapper::InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory,
225 const u64 size_) const {
226 switch (pool.GetLocation()) {
227 case MemoryPoolInfo::Location::CPU:
228 return false;
229 case MemoryPoolInfo::Location::DSP:
230 pool.SetCpuAddress(reinterpret_cast<u64>(memory), size_);
231 if (Map(Kernel::Svc::CurrentProcess, reinterpret_cast<u64>(memory), size_)) {
232 pool.SetDspAddress(pool.GetCpuAddress());
233 return true;
234 }
235 return false;
236 default:
237 LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!",
238 static_cast<u32>(pool.GetLocation()));
239 return false;
240 }
241}
242
243} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/behavior/behavior_info.h"
9#include "audio_core/renderer/memory/memory_pool_info.h"
10#include "common/common_types.h"
11#include "core/hle/service/audio/errors.h"
12
13namespace AudioCore::AudioRenderer {
14class AddressInfo;
15
16/**
17 * Utility functions for managing MemoryPoolInfos
18 */
19class PoolMapper {
20public:
21 explicit PoolMapper(u32 process_handle, bool force_map);
22 explicit PoolMapper(u32 process_handle, std::span<MemoryPoolInfo> pool_infos, u32 pool_count,
23 bool force_map);
24
25 /**
26 * Clear the usage state for all given pools.
27 *
28 * @param pools - The memory pools to clear.
29 * @param count - The number of pools.
30 */
31 static void ClearUseState(std::span<MemoryPoolInfo> pools, u32 count);
32
33 /**
34 * Find the memory pool containing the given address and size from a given list of pools.
35 *
36 * @param pools - The memory pools to search within.
37 * @param count - The number of pools.
38 * @param address - The address of the region to find.
39 * @param size - The size of the region to find.
40 * @return Pointer to the memory pool if found, otherwise nullptr.
41 */
42 MemoryPoolInfo* FindMemoryPool(MemoryPoolInfo* pools, u64 count, CpuAddr address,
43 u64 size) const;
44
45 /**
46 * Find the memory pool containing the given address and size from the PoolMapper's memory pool.
47 *
48 * @param address - The address of the region to find.
49 * @param size - The size of the region to find.
50 * @return Pointer to the memory pool if found, otherwise nullptr.
51 */
52 MemoryPoolInfo* FindMemoryPool(CpuAddr address, u64 size) const;
53
54 /**
55 * Set the PoolMapper's memory pool to one in the given list of pools, which contains
56 * address_info.
57 *
58 * @param address_info - The expected region to find within pools.
59 * @param pools - The list of pools to search within.
60 * @param count - The number of pools given.
61 * @return True if successfully mapped, otherwise false.
62 */
63 bool FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools, u32 count) const;
64
65 /**
66 * Set the PoolMapper's memory pool to the one containing address_info.
67 *
68 * @param address_info - The address to find the memory pool for.
69 * @return True if successfully mapped, otherwise false.
70 */
71 bool FillDspAddr(AddressInfo& address_info) const;
72
73 /**
74 * Try to attach a {address, size} region to the given address_info, and map it. Fills in the
75 * given error_info and address_info.
76 *
77 * @param error_info - Output error info.
78 * @param address_info - Output address info, initialized with the given {address, size} and
79 * attempted to map.
80 * @param address - Address of the region to map.
81 * @param size - Size of the region to map.
82 * @return True if successfully attached, otherwise false.
83 */
84 bool TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info,
85 CpuAddr address, u64 size) const;
86
87 /**
88 * Return whether force mapping is enabled.
89 *
90 * @return True if force mapping is enabled, otherwise false.
91 */
92 bool IsForceMapEnabled() const;
93
94 /**
95 * Get the process handle, depending on location.
96 *
97 * @param pool - The pool to check the location of.
98 * @return CurrentProcessHandle if location == DSP,
99 * the PoolMapper's process_handle if location == CPU
100 */
101 u32 GetProcessHandle(const MemoryPoolInfo* pool) const;
102
103 /**
104 * Map the given region with the given handle. This is a no-op.
105 *
106 * @param handle - The process handle to map to.
107 * @param cpu_addr - Address to map.
108 * @param size - Size to map.
109 * @return True if successfully mapped, otherwise false.
110 */
111 bool Map(u32 handle, CpuAddr cpu_addr, u64 size) const;
112
113 /**
114 * Map the given memory pool.
115 *
116 * @param pool - The pool to map.
117 * @return True if successfully mapped, otherwise false.
118 */
119 bool Map(MemoryPoolInfo& pool) const;
120
121 /**
122 * Unmap the given region with the given handle.
123 *
124 * @param handle - The process handle to unmap to.
125 * @param cpu_addr - Address to unmap.
126 * @param size - Size to unmap.
127 * @return True if successfully unmapped, otherwise false.
128 */
129 bool Unmap(u32 handle, CpuAddr cpu_addr, u64 size) const;
130
131 /**
132 * Unmap the given memory pool.
133 *
134 * @param pool - The pool to unmap.
135 * @return True if successfully unmapped, otherwise false.
136 */
137 bool Unmap(MemoryPoolInfo& pool) const;
138
139 /**
140 * Forcibly unmap the given region.
141 *
142 * @param address_info - The region to unmap.
143 */
144 void ForceUnmapPointer(const AddressInfo& address_info) const;
145
146 /**
147 * Update the given memory pool.
148 *
149 * @param pool - Pool to update.
150 * @param in_params - Input parameters for the update.
151 * @param out_params - Output parameters for the update.
152 * @return The result of the update. See MemoryPoolInfo::ResultState
153 */
154 MemoryPoolInfo::ResultState Update(MemoryPoolInfo& pool,
155 const MemoryPoolInfo::InParameter& in_params,
156 MemoryPoolInfo::OutStatus& out_params) const;
157
158 /**
159 * Initialize the PoolMapper's memory pool.
160 *
161 * @param pool - Input pool to initialize.
162 * @param memory - Pointer to the memory region for the pool.
163 * @param size - Size of the memory region for the pool.
164 * @return True if initialized successfully, otherwise false.
165 */
166 bool InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory, u64 size) const;
167
168private:
169 /// Process handle for this mapper, used when location == CPU
170 u32 process_handle;
171 /// List of memory pools assigned to this mapper
172 MemoryPoolInfo* pool_infos{};
173 /// The number of pools
174 u64 pool_count{};
175 /// Is forced mapping enabled
176 bool force_map;
177};
178
179} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <ranges>
5
6#include "audio_core/renderer/mix/mix_context.h"
7#include "audio_core/renderer/splitter/splitter_context.h"
8
9namespace AudioCore::AudioRenderer {
10
11void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_,
12 const u32 count_, std::span<s32> effect_process_order_buffer_,
13 const u32 effect_count_, std::span<u8> node_states_workbuffer,
14 const u64 node_buffer_size, std::span<u8> edge_matrix_workbuffer,
15 const u64 edge_matrix_size) {
16 count = count_;
17 sorted_mix_infos = sorted_mix_infos_;
18 mix_infos = mix_infos_;
19 effect_process_order_buffer = effect_process_order_buffer_;
20 effect_count = effect_count_;
21
22 if (node_states_workbuffer.size() > 0 && edge_matrix_workbuffer.size() > 0) {
23 node_states.Initialize(node_states_workbuffer, node_buffer_size, count);
24 edge_matrix.Initialize(edge_matrix_workbuffer, edge_matrix_size, count);
25 }
26
27 for (s32 i = 0; i < count; i++) {
28 sorted_mix_infos[i] = &mix_infos[i];
29 }
30}
31
32MixInfo* MixContext::GetSortedInfo(const s32 index) {
33 return sorted_mix_infos[index];
34}
35
36void MixContext::SetSortedInfo(const s32 index, MixInfo& mix_info) {
37 sorted_mix_infos[index] = &mix_info;
38}
39
40MixInfo* MixContext::GetInfo(const s32 index) {
41 return &mix_infos[index];
42}
43
44MixInfo* MixContext::GetFinalMixInfo() {
45 return &mix_infos[0];
46}
47
48s32 MixContext::GetCount() const {
49 return count;
50}
51
52void MixContext::UpdateDistancesFromFinalMix() {
53 for (s32 i = 0; i < count; i++) {
54 mix_infos[i].distance_from_final_mix = InvalidDistanceFromFinalMix;
55 }
56
57 for (s32 i = 0; i < count; i++) {
58 auto& mix_info{mix_infos[i]};
59 sorted_mix_infos[i] = &mix_info;
60
61 if (!mix_info.in_use) {
62 continue;
63 }
64
65 auto mix_id{mix_info.mix_id};
66 auto distance_to_final_mix{FinalMixId};
67
68 while (distance_to_final_mix < count) {
69 if (mix_id == FinalMixId) {
70 break;
71 }
72
73 if (mix_id == UnusedMixId) {
74 distance_to_final_mix = InvalidDistanceFromFinalMix;
75 break;
76 }
77
78 auto distance_from_final_mix{mix_infos[mix_id].distance_from_final_mix};
79 if (distance_from_final_mix != InvalidDistanceFromFinalMix) {
80 distance_to_final_mix = distance_from_final_mix + 1;
81 break;
82 }
83
84 distance_to_final_mix++;
85 mix_id = mix_infos[mix_id].dst_mix_id;
86 }
87
88 if (distance_to_final_mix >= count) {
89 distance_to_final_mix = InvalidDistanceFromFinalMix;
90 }
91 mix_info.distance_from_final_mix = distance_to_final_mix;
92 }
93}
94
95void MixContext::SortInfo() {
96 UpdateDistancesFromFinalMix();
97
98 std::ranges::sort(sorted_mix_infos, [](const MixInfo* lhs, const MixInfo* rhs) {
99 return lhs->distance_from_final_mix > rhs->distance_from_final_mix;
100 });
101
102 CalcMixBufferOffset();
103}
104
105void MixContext::CalcMixBufferOffset() {
106 s16 offset{0};
107 for (s32 i = 0; i < count; i++) {
108 auto mix_info{sorted_mix_infos[i]};
109 if (mix_info->in_use) {
110 const auto buffer_count{mix_info->buffer_count};
111 mix_info->buffer_offset = offset;
112 offset += buffer_count;
113 }
114 }
115}
116
117bool MixContext::TSortInfo(const SplitterContext& splitter_context) {
118 if (!splitter_context.UsingSplitter()) {
119 CalcMixBufferOffset();
120 return true;
121 }
122
123 if (!node_states.Tsort(edge_matrix)) {
124 return false;
125 }
126
127 std::vector<s32> sorted_results{node_states.GetSortedResuls()};
128 const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))};
129 for (s32 i = 0; i < result_size; i++) {
130 sorted_mix_infos[i] = &mix_infos[sorted_results[i]];
131 }
132
133 CalcMixBufferOffset();
134 return true;
135}
136
137EdgeMatrix& MixContext::GetEdgeMatrix() {
138 return edge_matrix;
139}
140
141} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/mix/mix_info.h"
9#include "audio_core/renderer/nodes/edge_matrix.h"
10#include "audio_core/renderer/nodes/node_states.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14class SplitterContext;
15
16/*
17 * Manages mixing states, sorting and building a node graph to describe a mix order.
18 */
19class MixContext {
20public:
21 /**
22 * Initialize the mix context.
23 *
24 * @param sorted_mix_infos - Buffer for the sorted mix infos.
25 * @param mix_infos - Buffer for the mix infos.
26 * @param effect_process_order_buffer - Buffer for the effect process orders.
27 * @param effect_count - Number of effects in the buffer.
28 * @param node_states_workbuffer - Buffer for node states.
29 * @param node_buffer_size - Size of the node states buffer.
30 * @param edge_matrix_workbuffer - Buffer for edge matrix.
31 * @param edge_matrix_size - Size of the edge matrix buffer.
32 */
33 void Initialize(std::span<MixInfo*> sorted_mix_infos, std::span<MixInfo> mix_infos, u32 count_,
34 std::span<s32> effect_process_order_buffer, u32 effect_count,
35 std::span<u8> node_states_workbuffer, u64 node_buffer_size,
36 std::span<u8> edge_matrix_workbuffer, u64 edge_matrix_size);
37
38 /**
39 * Get a sorted mix at the given index.
40 *
41 * @param index - Index of sorted mix.
42 * @return The sorted mix.
43 */
44 MixInfo* GetSortedInfo(s32 index);
45
46 /**
47 * Set the sorted info at the given index.
48 *
49 * @param index - Index of sorted mix.
50 * @param mix_info - The new mix for this index.
51 */
52 void SetSortedInfo(s32 index, MixInfo& mix_info);
53
54 /**
55 * Get a mix at the given index.
56 *
57 * @param index - Index of mix.
58 * @return The mix.
59 */
60 MixInfo* GetInfo(s32 index);
61
62 /**
63 * Get the final mix.
64 *
65 * @return The final mix.
66 */
67 MixInfo* GetFinalMixInfo();
68
69 /**
70 * Get the current number of mixes.
71 *
72 * @return The number of active mixes.
73 */
74 s32 GetCount() const;
75
76 /**
77 * Update all of the mixes' distance from the final mix.
78 * Needs to be called after altering the mix graph.
79 */
80 void UpdateDistancesFromFinalMix();
81
82 /**
83 * Non-splitter sort, sorts the sorted mixes based on their distance from the final mix.
84 */
85 void SortInfo();
86
87 /**
88 * Re-calculate the mix buffer offsets for each mix after altering the mix.
89 */
90 void CalcMixBufferOffset();
91
92 /**
93 * Splitter sort, traverse the splitter node graph and sort the sorted mixes from results.
94 *
95 * @param splitter_context - Splitter context for the sort.
96 * @return True if the sort was successful, othewise false.
97 */
98 bool TSortInfo(const SplitterContext& splitter_context);
99
100 /**
101 * Get the edge matrix used for the mix graph.
102 *
103 * @return The edge matrix used.
104 */
105 EdgeMatrix& GetEdgeMatrix();
106
107private:
108 /// Array of sorted mixes
109 std::span<MixInfo*> sorted_mix_infos{};
110 /// Array of mixes
111 std::span<MixInfo> mix_infos{};
112 /// Number of active mixes
113 s32 count{};
114 /// Array of effect process orderings
115 std::span<s32> effect_process_order_buffer{};
116 /// Number of effects in the process ordering buffer
117 u64 effect_count{};
118 /// Node states used in splitter sort
119 NodeStates node_states{};
120 /// Edge matrix for connected nodes used in splitter sort
121 EdgeMatrix edge_matrix{};
122};
123
124} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/behavior/behavior_info.h"
5#include "audio_core/renderer/effect/effect_context.h"
6#include "audio_core/renderer/mix/mix_info.h"
7#include "audio_core/renderer/nodes/edge_matrix.h"
8#include "audio_core/renderer/splitter/splitter_context.h"
9
10namespace AudioCore::AudioRenderer {
11
12MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior)
13 : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_},
14 long_size_pre_delay_supported{behavior.IsLongSizePreDelaySupported()} {
15 ClearEffectProcessingOrder();
16}
17
18void MixInfo::Cleanup() {
19 mix_id = UnusedMixId;
20 dst_mix_id = UnusedMixId;
21 dst_splitter_id = UnusedSplitterId;
22}
23
24void MixInfo::ClearEffectProcessingOrder() {
25 for (s32 i = 0; i < effect_count; i++) {
26 effect_order_buffer[i] = -1;
27 }
28}
29
30bool MixInfo::Update(EdgeMatrix& edge_matrix, const InParameter& in_params,
31 EffectContext& effect_context, SplitterContext& splitter_context,
32 const BehaviorInfo& behavior) {
33 volume = in_params.volume;
34 sample_rate = in_params.sample_rate;
35 buffer_count = static_cast<s16>(in_params.buffer_count);
36 in_use = in_params.in_use;
37 mix_id = in_params.mix_id;
38 node_id = in_params.node_id;
39 mix_volumes = in_params.mix_volumes;
40
41 bool sort_required{false};
42 if (behavior.IsSplitterSupported()) {
43 sort_required = UpdateConnection(edge_matrix, in_params, splitter_context);
44 } else {
45 if (dst_mix_id != in_params.dest_mix_id) {
46 dst_mix_id = in_params.dest_mix_id;
47 sort_required = true;
48 }
49 dst_splitter_id = UnusedSplitterId;
50 }
51
52 ClearEffectProcessingOrder();
53
54 // Check all effects, and set their order if they belong to this mix.
55 const auto count{effect_context.GetCount()};
56 for (u32 i = 0; i < count; i++) {
57 const auto& info{effect_context.GetInfo(i)};
58 if (mix_id == info.GetMixId()) {
59 const auto processing_order{info.GetProcessingOrder()};
60 if (processing_order > effect_count) {
61 break;
62 }
63 effect_order_buffer[processing_order] = i;
64 }
65 }
66
67 return sort_required;
68}
69
70bool MixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params,
71 SplitterContext& splitter_context) {
72 auto has_new_connection{false};
73 if (dst_splitter_id != UnusedSplitterId) {
74 auto& splitter_info{splitter_context.GetInfo(dst_splitter_id)};
75 has_new_connection = splitter_info.HasNewConnection();
76 }
77
78 // Check if this mix matches the input parameters.
79 // If everything is the same, don't bother updating.
80 if (dst_mix_id == in_params.dest_mix_id && dst_splitter_id == in_params.dest_splitter_id &&
81 !has_new_connection) {
82 return false;
83 }
84
85 // Reset the mix in the graph, as we're about to update it.
86 edge_matrix.RemoveEdges(mix_id);
87
88 if (in_params.dest_mix_id == UnusedMixId) {
89 if (in_params.dest_splitter_id != UnusedSplitterId) {
90 // If the splitter is used, connect this mix to each active destination.
91 auto& splitter_info{splitter_context.GetInfo(in_params.dest_splitter_id)};
92 auto const destination_count{splitter_info.GetDestinationCount()};
93
94 for (u32 i = 0; i < destination_count; i++) {
95 auto destination{
96 splitter_context.GetDesintationData(in_params.dest_splitter_id, i)};
97
98 if (destination) {
99 const auto destination_id{destination->GetMixId()};
100 if (destination_id != UnusedMixId) {
101 edge_matrix.Connect(mix_id, destination_id);
102 }
103 }
104 }
105 }
106 } else {
107 // If the splitter is not used, only connect this mix to its destination.
108 edge_matrix.Connect(mix_id, in_params.dest_mix_id);
109 }
110
111 dst_mix_id = in_params.dest_mix_id;
112 dst_splitter_id = in_params.dest_splitter_id;
113 return true;
114}
115
116bool MixInfo::HasAnyConnection() const {
117 return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId;
118}
119
120} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8
9#include "audio_core/common/common.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13class EdgeMatrix;
14class SplitterContext;
15class EffectContext;
16class BehaviorInfo;
17
18/**
19 * A single mix, which may feed through other mixes in a chain until reaching the final output mix.
20 */
21class MixInfo {
22public:
23 struct InParameter {
24 /* 0x000 */ f32 volume;
25 /* 0x004 */ u32 sample_rate;
26 /* 0x008 */ u32 buffer_count;
27 /* 0x00C */ bool in_use;
28 /* 0x00D */ bool is_dirty;
29 /* 0x010 */ s32 mix_id;
30 /* 0x014 */ u32 effect_count;
31 /* 0x018 */ s32 node_id;
32 /* 0x01C */ char unk01C[0x8];
33 /* 0x024 */ std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes;
34 /* 0x924 */ s32 dest_mix_id;
35 /* 0x928 */ s32 dest_splitter_id;
36 /* 0x92C */ char unk92C[0x4];
37 };
38 static_assert(sizeof(InParameter) == 0x930, "MixInfo::InParameter has the wrong size!");
39
40 struct InDirtyParameter {
41 /* 0x00 */ u32 magic;
42 /* 0x04 */ s32 count;
43 /* 0x08 */ char unk08[0x18];
44 };
45 static_assert(sizeof(InDirtyParameter) == 0x20,
46 "MixInfo::InDirtyParameter has the wrong size!");
47
48 MixInfo(std::span<s32> effect_order_buffer, s32 effect_count, BehaviorInfo& behavior);
49
50 /**
51 * Clean up the mix, resetting it to a default state.
52 */
53 void Cleanup();
54
55 /**
56 * Clear the effect process order for all effects in this mix.
57 */
58 void ClearEffectProcessingOrder();
59
60 /**
61 * Update the mix according to the given parameters.
62 *
63 * @param edge_matrix - Updated with new splitter node connections, if supported.
64 * @param in_params - Input parameters.
65 * @param effect_context - Used to update the effect orderings.
66 * @param splitter_context - Used to update the mix graph if supported.
67 * @param behavior - Used for checking which features are supported.
68 * @return True if the mix was updated and a sort is required, otherwise false.
69 */
70 bool Update(EdgeMatrix& edge_matrix, const InParameter& in_params,
71 EffectContext& effect_context, SplitterContext& splitter_context,
72 const BehaviorInfo& behavior);
73
74 /**
75 * Update the mix's connection in the node graph according to the given parameters.
76 *
77 * @param edge_matrix - Updated with new splitter node connections, if supported.
78 * @param in_params - Input parameters.
79 * @param splitter_context - Used to update the mix graph if supported.
80 * @return True if the mix was updated and a sort is required, otherwise false.
81 */
82 bool UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params,
83 SplitterContext& splitter_context);
84
85 /**
86 * Check if this mix is connected to any other.
87 *
88 * @return True if the mix has a connection, otherwise false.
89 */
90 bool HasAnyConnection() const;
91
92 /// Volume of this mix
93 f32 volume{};
94 /// Sample rate of this mix
95 u32 sample_rate{};
96 /// Number of buffers in this mix
97 s16 buffer_count{};
98 /// Is this mix in use?
99 bool in_use{};
100 /// Is this mix enabled?
101 bool enabled{};
102 /// Id of this mix
103 s32 mix_id{UnusedMixId};
104 /// Node id of this mix
105 s32 node_id{};
106 /// Buffer offset for this mix
107 s16 buffer_offset{};
108 /// Distance to the final mix
109 s32 distance_from_final_mix{InvalidDistanceFromFinalMix};
110 /// Array of effect orderings of all effects in this mix
111 std::span<s32> effect_order_buffer;
112 /// Number of effects in this mix
113 const s32 effect_count;
114 /// Id for next mix in the chain
115 s32 dst_mix_id{UnusedMixId};
116 /// Mixing volumes for this mix used when this mix is chained with another
117 std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes{};
118 /// Id for next mix in the graph when splitter is used
119 s32 dst_splitter_id{UnusedSplitterId};
120 /// Is a longer pre-delay time supported for the reverb effect?
121 const bool long_size_pre_delay_supported;
122};
123
124} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <vector>
7
8#include "common/common_types.h"
9
10namespace AudioCore::AudioRenderer {
11/**
12 * Represents an array of bits used for nodes and edges for the mixing graph.
13 */
14struct BitArray {
15 void reset() {
16 buffer.assign(buffer.size(), false);
17 }
18
19 /// Bits
20 std::vector<bool> buffer{};
21 /// Size of the buffer
22 u32 size{};
23};
24
25} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/nodes/edge_matrix.h"
5
6namespace AudioCore::AudioRenderer {
7
8void EdgeMatrix::Initialize([[maybe_unused]] std::span<u8> buffer,
9 [[maybe_unused]] const u64 node_buffer_size, const u32 count_) {
10 count = count_;
11 edges.buffer.resize(count_ * count_);
12 edges.size = count_ * count_;
13 edges.reset();
14}
15
16bool EdgeMatrix::Connected(const u32 id, const u32 destination_id) const {
17 return edges.buffer[count * id + destination_id];
18}
19
20void EdgeMatrix::Connect(const u32 id, const u32 destination_id) {
21 edges.buffer[count * id + destination_id] = true;
22}
23
24void EdgeMatrix::Disconnect(const u32 id, const u32 destination_id) {
25 edges.buffer[count * id + destination_id] = false;
26}
27
28void EdgeMatrix::RemoveEdges(const u32 id) {
29 for (u32 dest = 0; dest < count; dest++) {
30 Disconnect(id, dest);
31 }
32}
33
34u32 EdgeMatrix::GetNodeCount() const {
35 return count;
36}
37
38} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/nodes/bit_array.h"
9#include "common/alignment.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * An edge matrix, holding the connections for each node to every other node in the graph.
15 */
16class EdgeMatrix {
17public:
18 /**
19 * Calculate the size required for its workbuffer.
20 *
21 * @param count - The number of nodes in the graph.
22 * @return The required workbuffer size.
23 */
24 static u64 GetWorkBufferSize(u32 count) {
25 return Common::AlignUp(count * count, 0x40) / sizeof(u64);
26 }
27
28 /**
29 * Initialize this edge matrix.
30 *
31 * @param buffer - The workbuffer to use. Unused.
32 * @param node_buffer_size - The size of the workbuffer. Unused.
33 * @param count - The number of nodes in the graph.
34 */
35 void Initialize(std::span<u8> buffer, u64 node_buffer_size, u32 count);
36
37 /**
38 * Check if a node is connected to another.
39 *
40 * @param id - The node id to check.
41 * @param destination_id - Node id to check connection with.
42 */
43 bool Connected(u32 id, u32 destination_id) const;
44
45 /**
46 * Connect a node to another.
47 *
48 * @param id - The node id to connect.
49 * @param destination_id - Destination to connect it to.
50 */
51 void Connect(u32 id, u32 destination_id);
52
53 /**
54 * Disconnect a node from another.
55 *
56 * @param id - The node id to disconnect.
57 * @param destination_id - Destination to disconnect it from.
58 */
59 void Disconnect(u32 id, u32 destination_id);
60
61 /**
62 * Remove all connections for a given node.
63 *
64 * @param id - The node id to disconnect.
65 */
66 void RemoveEdges(u32 id);
67
68 /**
69 * Get the number of nodes in the graph.
70 *
71 * @return Number of nodes.
72 */
73 u32 GetNodeCount() const;
74
75private:
76 /// Edges for the current graph
77 BitArray edges;
78 /// Number of nodes (not edges) in the graph
79 u32 count;
80};
81
82} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/nodes/node_states.h"
5#include "common/logging/log.h"
6
7namespace AudioCore::AudioRenderer {
8
9void NodeStates::Initialize(std::span<u8> buffer_, [[maybe_unused]] const u64 node_buffer_size,
10 const u32 count) {
11 u64 num_blocks{Common::AlignUp(count, 0x40) / sizeof(u64)};
12 u64 offset{0};
13
14 node_count = count;
15
16 nodes_found.buffer.resize(count);
17 nodes_found.size = count;
18 nodes_found.reset();
19
20 offset += num_blocks;
21
22 nodes_complete.buffer.resize(count);
23 nodes_complete.size = count;
24 nodes_complete.reset();
25
26 offset += num_blocks;
27
28 results = {reinterpret_cast<u32*>(&buffer_[offset]), count};
29
30 offset += count * sizeof(u32);
31
32 stack.stack = {reinterpret_cast<u32*>(&buffer_[offset]), count * count};
33 stack.size = count * count;
34 stack.unk_10 = count * count;
35
36 offset += count * count * sizeof(u32);
37}
38
39bool NodeStates::Tsort(const EdgeMatrix& edge_matrix) {
40 return DepthFirstSearch(edge_matrix, stack);
41}
42
43bool NodeStates::DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack_) {
44 ResetState();
45
46 for (u32 node_id = 0; node_id < node_count; node_id++) {
47 if (GetState(node_id) == SearchState::Unknown) {
48 stack_.push(node_id);
49 }
50
51 while (stack_.Count() > 0) {
52 auto current_node{stack_.top()};
53 switch (GetState(current_node)) {
54 case SearchState::Unknown:
55 SetState(current_node, SearchState::Found);
56 break;
57 case SearchState::Found:
58 SetState(current_node, SearchState::Complete);
59 PushTsortResult(current_node);
60 stack_.pop();
61 continue;
62 case SearchState::Complete:
63 stack_.pop();
64 continue;
65 }
66
67 const auto edge_count{edge_matrix.GetNodeCount()};
68 for (u32 edge_id = 0; edge_id < edge_count; edge_id++) {
69 if (!edge_matrix.Connected(current_node, edge_id)) {
70 continue;
71 }
72
73 switch (GetState(edge_id)) {
74 case SearchState::Unknown:
75 stack_.push(edge_id);
76 break;
77 case SearchState::Found:
78 LOG_ERROR(Service_Audio,
79 "Cycle detected in the node graph, graph is not a DAG! "
80 "Bailing to avoid an infinite loop");
81 ResetState();
82 return false;
83 case SearchState::Complete:
84 break;
85 }
86 }
87 }
88 }
89
90 return true;
91}
92
93NodeStates::SearchState NodeStates::GetState(const u32 id) const {
94 if (nodes_found.buffer[id]) {
95 return SearchState::Found;
96 } else if (nodes_complete.buffer[id]) {
97 return SearchState::Complete;
98 }
99 return SearchState::Unknown;
100}
101
102void NodeStates::PushTsortResult(const u32 id) {
103 results[result_pos++] = id;
104}
105
106void NodeStates::SetState(const u32 id, const SearchState state) {
107 switch (state) {
108 case SearchState::Complete:
109 nodes_found.buffer[id] = false;
110 nodes_complete.buffer[id] = true;
111 break;
112 case SearchState::Found:
113 nodes_found.buffer[id] = true;
114 nodes_complete.buffer[id] = false;
115 break;
116 case SearchState::Unknown:
117 nodes_found.buffer[id] = false;
118 nodes_complete.buffer[id] = false;
119 break;
120 default:
121 LOG_ERROR(Service_Audio, "Unknown node SearchState {}", static_cast<u32>(state));
122 break;
123 }
124}
125
126void NodeStates::ResetState() {
127 nodes_found.reset();
128 nodes_complete.reset();
129 std::fill(results.begin(), results.end(), -1);
130 result_pos = 0;
131}
132
133u32 NodeStates::GetNodeCount() const {
134 return node_count;
135}
136
137std::vector<s32> NodeStates::GetSortedResuls() const {
138 return {results.rbegin(), results.rbegin() + result_pos};
139}
140
141} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7#include <vector>
8
9#include "audio_core/renderer/nodes/edge_matrix.h"
10#include "common/alignment.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14/**
15 * Graph utility functions for sorting and getting results from the DAG.
16 */
17class NodeStates {
18 /**
19 * State of a node in the depth first search.
20 */
21 enum class SearchState {
22 Unknown,
23 Found,
24 Complete,
25 };
26
27 /**
28 * Stack used for a depth first search.
29 */
30 struct Stack {
31 /**
32 * Calculate the workbuffer size required for this stack.
33 *
34 * @param count - Maximum number of nodes for the stack.
35 * @return Required buffer size.
36 */
37 static u32 CalcBufferSize(u32 count) {
38 return count * sizeof(u32);
39 }
40
41 /**
42 * Reset the stack back to default.
43 *
44 * @param buffer_ - The new buffer to use.
45 * @param size_ - The size of the new buffer.
46 */
47 void Reset(u32* buffer_, u32 size_) {
48 stack = {buffer_, size_};
49 size = size_;
50 pos = 0;
51 unk_10 = size_;
52 }
53
54 /**
55 * Get the current stack position.
56 *
57 * @return The current stack position.
58 */
59 u32 Count() {
60 return pos;
61 }
62
63 /**
64 * Push a new node to the stack.
65 *
66 * @param data - The node to push.
67 */
68 void push(u32 data) {
69 stack[pos++] = data;
70 }
71
72 /**
73 * Pop a node from the stack.
74 *
75 * @return The node on the top of the stack.
76 */
77 u32 pop() {
78 return stack[--pos];
79 }
80
81 /**
82 * Get the top of the stack without popping.
83 *
84 * @return The node on the top of the stack.
85 */
86 u32 top() {
87 return stack[pos - 1];
88 }
89
90 /// Buffer for the stack
91 std::span<u32> stack{};
92 /// Size of the stack buffer
93 u32 size{};
94 /// Current stack position
95 u32 pos{};
96 /// Unknown
97 u32 unk_10{};
98 };
99
100public:
101 /**
102 * Calculate the workbuffer size required for the node states.
103 *
104 * @param count - The number of nodes.
105 * @return The required workbuffer size.
106 */
107 static u64 GetWorkBufferSize(u32 count) {
108 return (Common::AlignUp(count, 0x40) / sizeof(u64)) * 2 + count * sizeof(BitArray) +
109 count * Stack::CalcBufferSize(count);
110 }
111
112 /**
113 * Initialize the node states.
114 *
115 * @param buffer - The workbuffer to use. Unused.
116 * @param node_buffer_size - The size of the workbuffer. Unused.
117 * @param count - The number of nodes in the graph.
118 */
119 void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count);
120
121 /**
122 * Sort the graph. Only calls DepthFirstSearch.
123 *
124 * @param edge_matrix - The edge matrix used to hold the connections between nodes.
125 * @return True if the sort was successful, otherwise false.
126 */
127 bool Tsort(const EdgeMatrix& edge_matrix);
128
129 /**
130 * Sort the graph via depth first search.
131 *
132 * @param edge_matrix - The edge matrix used to hold the connections between nodes.
133 * @param stack - The stack used for pushing and popping nodes.
134 * @return True if the sort was successful, otherwise false.
135 */
136 bool DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack);
137
138 /**
139 * Get the search state of a given node.
140 *
141 * @param id - The node id to check.
142 * @return The node's search state. See SearchState
143 */
144 SearchState GetState(u32 id) const;
145
146 /**
147 * Push a node id to the results buffer when found in the DFS.
148 *
149 * @param id - The node id to push.
150 */
151 void PushTsortResult(u32 id);
152
153 /**
154 * Set the state of a node.
155 *
156 * @param id - The node id to alter.
157 * @param state - The new search state.
158 */
159 void SetState(u32 id, SearchState state);
160
161 /**
162 * Reset the nodes found, complete and the results.
163 */
164 void ResetState();
165
166 /**
167 * Get the number of nodes in the graph.
168 *
169 * @return The number of nodes.
170 */
171 u32 GetNodeCount() const;
172
173 /**
174 * Get the sorted results from the DFS.
175 *
176 * @return Vector of nodes in reverse order.
177 */
178 std::vector<s32> GetSortedResuls() const;
179
180private:
181 /// Number of nodes in the graph
182 u32 node_count{};
183 /// Position in results buffer
184 u32 result_pos{};
185 /// List of nodes found
186 BitArray nodes_found{};
187 /// List of nodes completed
188 BitArray nodes_complete{};
189 /// List of results from the depth first search
190 std::span<u32> results{};
191 /// Stack used during the depth first search
192 Stack stack{};
193};
194
195} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/command/command_buffer.h"
5#include "audio_core/renderer/command/command_generator.h"
6#include "audio_core/renderer/performance/detail_aspect.h"
7
8namespace AudioCore::AudioRenderer {
9
10DetailAspect::DetailAspect(CommandGenerator& command_generator_,
11 const PerformanceEntryType entry_type, const s32 node_id_,
12 const PerformanceDetailType detail_type)
13 : command_generator{command_generator_}, node_id{node_id_} {
14 auto perf_manager{command_generator.GetPerformanceManager()};
15 if (perf_manager != nullptr && perf_manager->IsInitialized() &&
16 perf_manager->IsDetailTarget(node_id) &&
17 perf_manager->GetNextEntry(performance_entry_address, detail_type, entry_type, node_id)) {
18 command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start,
19 performance_entry_address);
20
21 initialized = true;
22 }
23}
24
25} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/performance/performance_entry_addresses.h"
7#include "audio_core/renderer/performance/performance_manager.h"
8#include "common/common_types.h"
9
10namespace AudioCore::AudioRenderer {
11class CommandGenerator;
12
13/**
14 * Holds detailed information about performance metrics, filled in by the AudioRenderer during
15 * Performance commands.
16 */
17class DetailAspect {
18public:
19 DetailAspect() = default;
20 DetailAspect(CommandGenerator& command_generator, PerformanceEntryType entry_type, s32 node_id,
21 PerformanceDetailType detail_type);
22
23 /// Command generator the command will be generated into
24 CommandGenerator& command_generator;
25 /// Addresses to be filled by the AudioRenderer
26 PerformanceEntryAddresses performance_entry_address{};
27 /// Is this detail aspect initialized?
28 bool initialized{};
29 /// Node id of this aspect
30 s32 node_id;
31};
32
33} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/command/command_buffer.h"
5#include "audio_core/renderer/command/command_generator.h"
6#include "audio_core/renderer/performance/entry_aspect.h"
7
8namespace AudioCore::AudioRenderer {
9
10EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type,
11 const s32 node_id_)
12 : command_generator{command_generator_}, node_id{node_id_} {
13 auto perf_manager{command_generator.GetPerformanceManager()};
14 if (perf_manager != nullptr && perf_manager->IsInitialized() &&
15 perf_manager->GetNextEntry(performance_entry_address, type, node_id)) {
16 command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start,
17 performance_entry_address);
18
19 initialized = true;
20 }
21}
22
23} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/performance/performance_entry_addresses.h"
7#include "audio_core/renderer/performance/performance_manager.h"
8#include "common/common_types.h"
9
10namespace AudioCore::AudioRenderer {
11class CommandGenerator;
12
13/**
14 * Holds entry information about performance metrics, filled in by the AudioRenderer during
15 * Performance commands.
16 */
17class EntryAspect {
18public:
19 EntryAspect() = default;
20 EntryAspect(CommandGenerator& command_generator, PerformanceEntryType type, s32 node_id);
21
22 /// Command generator the command will be generated into
23 CommandGenerator& command_generator;
24 /// Addresses to be filled by the AudioRenderer
25 PerformanceEntryAddresses performance_entry_address{};
26 /// Is this detail aspect initialized?
27 bool initialized{};
28 /// Node id of this aspect
29 s32 node_id;
30};
31
32} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/performance/performance_entry.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10
11enum class PerformanceDetailType : u8 {
12 Invalid,
13 Unk1,
14 Unk2,
15 Unk3,
16 Unk4,
17 Unk5,
18 Unk6,
19 Unk7,
20 Unk8,
21 Unk9,
22 Unk10,
23 Unk11,
24 Unk12,
25 Unk13,
26};
27
28struct PerformanceDetailVersion1 {
29 /* 0x00 */ u32 node_id;
30 /* 0x04 */ u32 start_time;
31 /* 0x08 */ u32 processed_time;
32 /* 0x0C */ PerformanceDetailType detail_type;
33 /* 0x0D */ PerformanceEntryType entry_type;
34};
35static_assert(sizeof(PerformanceDetailVersion1) == 0x10,
36 "PerformanceDetailVersion1 has the worng size!");
37
38struct PerformanceDetailVersion2 {
39 /* 0x00 */ u32 node_id;
40 /* 0x04 */ u32 start_time;
41 /* 0x08 */ u32 processed_time;
42 /* 0x0C */ PerformanceDetailType detail_type;
43 /* 0x0D */ PerformanceEntryType entry_type;
44 /* 0x10 */ u32 unk_10;
45 /* 0x14 */ char unk14[0x4];
46};
47static_assert(sizeof(PerformanceDetailVersion2) == 0x18,
48 "PerformanceDetailVersion2 has the worng size!");
49
50} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7
8namespace AudioCore::AudioRenderer {
9
10enum class PerformanceEntryType : u8 {
11 Invalid,
12 Voice,
13 SubMix,
14 FinalMix,
15 Sink,
16};
17
18struct PerformanceEntryVersion1 {
19 /* 0x00 */ u32 node_id;
20 /* 0x04 */ u32 start_time;
21 /* 0x08 */ u32 processed_time;
22 /* 0x0C */ PerformanceEntryType entry_type;
23};
24static_assert(sizeof(PerformanceEntryVersion1) == 0x10,
25 "PerformanceEntryVersion1 has the worng size!");
26
27struct PerformanceEntryVersion2 {
28 /* 0x00 */ u32 node_id;
29 /* 0x04 */ u32 start_time;
30 /* 0x08 */ u32 processed_time;
31 /* 0x0C */ PerformanceEntryType entry_type;
32 /* 0x0D */ char unk0D[0xB];
33};
34static_assert(sizeof(PerformanceEntryVersion2) == 0x18,
35 "PerformanceEntryVersion2 has the worng size!");
36
37} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/common/common.h"
7
8namespace AudioCore::AudioRenderer {
9
10struct PerformanceEntryAddresses {
11 CpuAddr translated_address;
12 CpuAddr entry_start_time_offset;
13 CpuAddr header_entry_count_offset;
14 CpuAddr entry_processed_time_offset;
15};
16
17} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7
8namespace AudioCore::AudioRenderer {
9
10struct PerformanceFrameHeaderVersion1 {
11 /* 0x00 */ u32 magic; // "PERF"
12 /* 0x04 */ u32 entry_count;
13 /* 0x08 */ u32 detail_count;
14 /* 0x0C */ u32 next_offset;
15 /* 0x10 */ u32 total_processing_time;
16 /* 0x14 */ u32 frame_index;
17};
18static_assert(sizeof(PerformanceFrameHeaderVersion1) == 0x18,
19 "PerformanceFrameHeaderVersion1 has the worng size!");
20
21struct PerformanceFrameHeaderVersion2 {
22 /* 0x00 */ u32 magic; // "PERF"
23 /* 0x04 */ u32 entry_count;
24 /* 0x08 */ u32 detail_count;
25 /* 0x0C */ u32 next_offset;
26 /* 0x10 */ u32 total_processing_time;
27 /* 0x14 */ u32 voices_dropped;
28 /* 0x18 */ u64 start_time;
29 /* 0x20 */ u32 frame_index;
30 /* 0x24 */ bool render_time_exceeded;
31 /* 0x25 */ char unk25[0xB];
32};
33static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30,
34 "PerformanceFrameHeaderVersion2 has the worng size!");
35
36} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/behavior/behavior_info.h"
5#include "audio_core/renderer/memory/memory_pool_info.h"
6#include "audio_core/renderer/performance/performance_manager.h"
7#include "common/common_funcs.h"
8
9namespace AudioCore::AudioRenderer {
10
11void PerformanceManager::CreateImpl(const size_t version) {
12 switch (version) {
13 case 1:
14 impl = std::make_unique<
15 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
16 PerformanceEntryVersion1, PerformanceDetailVersion1>>();
17 break;
18 case 2:
19 impl = std::make_unique<
20 PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
21 PerformanceEntryVersion2, PerformanceDetailVersion2>>();
22 break;
23 default:
24 LOG_WARNING(Service_Audio, "Invalid PerformanceMetricsDataFormat {}, creating version 1",
25 static_cast<u32>(version));
26 impl = std::make_unique<
27 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
28 PerformanceEntryVersion1, PerformanceDetailVersion1>>();
29 }
30}
31
32void PerformanceManager::Initialize(std::span<u8> workbuffer, const u64 workbuffer_size,
33 const AudioRendererParameterInternal& params,
34 const BehaviorInfo& behavior,
35 const MemoryPoolInfo& memory_pool) {
36 CreateImpl(behavior.GetPerformanceMetricsDataFormat());
37 impl->Initialize(workbuffer, workbuffer_size, params, behavior, memory_pool);
38}
39
40bool PerformanceManager::IsInitialized() const {
41 if (impl) {
42 return impl->IsInitialized();
43 }
44 return false;
45}
46
47u32 PerformanceManager::CopyHistories(u8* out_buffer, u64 out_size) {
48 if (impl) {
49 return impl->CopyHistories(out_buffer, out_size);
50 }
51 return 0;
52}
53
54bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
55 const PerformanceSysDetailType sys_detail_type,
56 const s32 node_id) {
57 if (impl) {
58 return impl->GetNextEntry(addresses, unk, sys_detail_type, node_id);
59 }
60 return false;
61}
62
63bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
64 const PerformanceEntryType entry_type, const s32 node_id) {
65 if (impl) {
66 return impl->GetNextEntry(addresses, entry_type, node_id);
67 }
68 return false;
69}
70
71bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
72 const PerformanceDetailType detail_type,
73 const PerformanceEntryType entry_type, const s32 node_id) {
74 if (impl) {
75 return impl->GetNextEntry(addresses, detail_type, entry_type, node_id);
76 }
77 return false;
78}
79
80void PerformanceManager::TapFrame(const bool dsp_behind, const u32 voices_dropped,
81 const u64 rendering_start_tick) {
82 if (impl) {
83 impl->TapFrame(dsp_behind, voices_dropped, rendering_start_tick);
84 }
85}
86
87bool PerformanceManager::IsDetailTarget(const u32 target_node_id) const {
88 if (impl) {
89 return impl->IsDetailTarget(target_node_id);
90 }
91 return false;
92}
93
94void PerformanceManager::SetDetailTarget(const u32 target_node_id) {
95 if (impl) {
96 impl->SetDetailTarget(target_node_id);
97 }
98}
99
100template <>
101void PerformanceManagerImpl<
102 PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
103 PerformanceDetailVersion1>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
104 const AudioRendererParameterInternal& params,
105 const BehaviorInfo& behavior,
106 const MemoryPoolInfo& memory_pool) {
107 workbuffer = workbuffer_;
108 entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
109 max_detail_count = MaxDetailEntries;
110 frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
111 const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
112 max_frames = frame_count - 1;
113 translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);
114
115 // The first frame is the "current" frame we're writing to.
116 auto buffer_offset{workbuffer.data()};
117 frame_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
118 buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
119 entry_buffer = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset), entries_per_frame};
120 buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
121 detail_buffer = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset), max_detail_count};
122
123 // After the current, is a ringbuffer of history frames, the current frame will be copied here
124 // before a new frame is written.
125 frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);
126
127 // If there's room for any history frames.
128 if (frame_count >= 2) {
129 buffer_offset = frame_history.data();
130 frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
131 buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
132 frame_history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset),
133 entries_per_frame};
134 buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
135 frame_history_details = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset),
136 max_detail_count};
137 } else {
138 frame_history_header = {};
139 frame_history_entries = {};
140 frame_history_details = {};
141 }
142
143 target_node_id = 0;
144 version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
145 entry_count = 0;
146 detail_count = 0;
147 frame_header->entry_count = 0;
148 frame_header->detail_count = 0;
149 output_frame_index = 0;
150 last_output_frame_index = 0;
151 is_initialized = true;
152}
153
154template <>
155bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
156 PerformanceEntryVersion1, PerformanceDetailVersion1>::IsInitialized()
157 const {
158 return is_initialized;
159}
160
161template <>
162u32 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
163 PerformanceEntryVersion1,
164 PerformanceDetailVersion1>::CopyHistories(u8* out_buffer, u64 out_size) {
165 if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
166 return 0;
167 }
168
169 // Are there any new frames waiting to be output?
170 if (last_output_frame_index == output_frame_index) {
171 return 0;
172 }
173
174 PerformanceFrameHeaderVersion1* out_header{nullptr};
175 u32 out_history_size{0};
176
177 while (last_output_frame_index != output_frame_index) {
178 PerformanceFrameHeaderVersion1* history_header{nullptr};
179 std::span<PerformanceEntryVersion1> history_entries{};
180 std::span<PerformanceDetailVersion1> history_details{};
181
182 if (max_frames > 0) {
183 auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
184 history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(frame_offset);
185 frame_offset += sizeof(PerformanceFrameHeaderVersion1);
186 history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(frame_offset),
187 history_header->entry_count};
188 frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion1);
189 history_details = {reinterpret_cast<PerformanceDetailVersion1*>(frame_offset),
190 history_header->detail_count};
191 } else {
192 // Original code does not break here, but will crash when trying to dereference the
193 // header in the next if, so let's just skip this frame and continue...
194 // Hopefully this will not happen.
195 LOG_WARNING(Service_Audio,
196 "max_frames should not be 0! Skipping frame to avoid a crash");
197 last_output_frame_index++;
198 continue;
199 }
200
201 if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion1) +
202 history_header->detail_count * sizeof(PerformanceDetailVersion1) +
203 2 * sizeof(PerformanceFrameHeaderVersion1)) {
204 break;
205 }
206
207 u32 out_offset{sizeof(PerformanceFrameHeaderVersion1)};
208 auto out_entries{std::span<PerformanceEntryVersion1>(
209 reinterpret_cast<PerformanceEntryVersion1*>(out_buffer + out_offset),
210 history_header->entry_count)};
211 u32 out_entry_count{0};
212 u32 total_processing_time{0};
213 for (auto& history_entry : history_entries) {
214 if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
215 out_entries[out_entry_count++] = history_entry;
216 total_processing_time += history_entry.processed_time;
217 }
218 }
219
220 out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion1));
221 auto out_details{std::span<PerformanceDetailVersion1>(
222 reinterpret_cast<PerformanceDetailVersion1*>(out_buffer + out_offset),
223 history_header->detail_count)};
224 u32 out_detail_count{0};
225 for (auto& history_detail : history_details) {
226 if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
227 out_details[out_detail_count++] = history_detail;
228 }
229 }
230
231 out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion1));
232 out_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(out_buffer);
233 out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
234 out_header->entry_count = out_entry_count;
235 out_header->detail_count = out_detail_count;
236 out_header->next_offset = out_offset;
237 out_header->total_processing_time = total_processing_time;
238 out_header->frame_index = history_header->frame_index;
239
240 out_history_size += out_offset;
241
242 out_buffer += out_offset;
243 out_size -= out_offset;
244 last_output_frame_index = (last_output_frame_index + 1) % max_frames;
245 }
246
247 // We're out of frames to output, so if there's enough left in the output buffer for another
248 // header, and we output at least 1 frame, set the next header to null.
249 if (out_size > sizeof(PerformanceFrameHeaderVersion1) && out_header != nullptr) {
250 std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion1));
251 }
252
253 return out_history_size;
254}
255
256template <>
257bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
258 PerformanceEntryVersion1, PerformanceDetailVersion1>::
259 GetNextEntry([[maybe_unused]] PerformanceEntryAddresses& addresses, [[maybe_unused]] u32** unk,
260 [[maybe_unused]] PerformanceSysDetailType sys_detail_type,
261 [[maybe_unused]] s32 node_id) {
262 return false;
263}
264
265template <>
266bool PerformanceManagerImpl<
267 PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
268 PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
269 const PerformanceEntryType entry_type,
270 const s32 node_id) {
271 if (!is_initialized) {
272 return false;
273 }
274
275 addresses.translated_address = translated_buffer;
276 addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
277 offsetof(PerformanceFrameHeaderVersion1, entry_count);
278
279 auto entry{&entry_buffer[entry_count++]};
280 addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
281 offsetof(PerformanceEntryVersion1, start_time);
282 addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
283 offsetof(PerformanceEntryVersion1, processed_time);
284
285 std::memset(entry, 0, sizeof(PerformanceEntryVersion1));
286 entry->node_id = node_id;
287 entry->entry_type = entry_type;
288 return true;
289}
290
291template <>
292bool PerformanceManagerImpl<
293 PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
294 PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
295 const PerformanceDetailType detail_type,
296 const PerformanceEntryType entry_type,
297 const s32 node_id) {
298 if (!is_initialized || detail_count > MaxDetailEntries) {
299 return false;
300 }
301
302 auto detail{&detail_buffer[detail_count++]};
303
304 addresses.translated_address = translated_buffer;
305 addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
306 offsetof(PerformanceFrameHeaderVersion1, detail_count);
307 addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
308 offsetof(PerformanceDetailVersion1, start_time);
309 addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
310 offsetof(PerformanceDetailVersion1, processed_time);
311
312 std::memset(detail, 0, sizeof(PerformanceDetailVersion1));
313 detail->node_id = node_id;
314 detail->entry_type = entry_type;
315 detail->detail_type = detail_type;
316 return true;
317}
318
319template <>
320void PerformanceManagerImpl<
321 PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
322 PerformanceDetailVersion1>::TapFrame([[maybe_unused]] bool dsp_behind,
323 [[maybe_unused]] u32 voices_dropped,
324 [[maybe_unused]] u64 rendering_start_tick) {
325 if (!is_initialized) {
326 return;
327 }
328
329 if (max_frames > 0) {
330 if (!frame_history.empty() && !workbuffer.empty()) {
331 auto history_frame = reinterpret_cast<PerformanceFrameHeaderVersion1*>(
332 &frame_history[output_frame_index * frame_size]);
333 std::memcpy(history_frame, workbuffer.data(), frame_size);
334 history_frame->frame_index = history_frame_index++;
335 }
336 output_frame_index = (output_frame_index + 1) % max_frames;
337 }
338
339 entry_count = 0;
340 detail_count = 0;
341 frame_header->entry_count = 0;
342 frame_header->detail_count = 0;
343}
344
345template <>
346bool PerformanceManagerImpl<
347 PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
348 PerformanceDetailVersion1>::IsDetailTarget(const u32 target_node_id_) const {
349 return target_node_id == target_node_id_;
350}
351
352template <>
353void PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
354 PerformanceEntryVersion1,
355 PerformanceDetailVersion1>::SetDetailTarget(const u32 target_node_id_) {
356 target_node_id = target_node_id_;
357}
358
359template <>
360void PerformanceManagerImpl<
361 PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
362 PerformanceDetailVersion2>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
363 const AudioRendererParameterInternal& params,
364 const BehaviorInfo& behavior,
365 const MemoryPoolInfo& memory_pool) {
366 workbuffer = workbuffer_;
367 entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
368 max_detail_count = MaxDetailEntries;
369 frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
370 const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
371 max_frames = frame_count - 1;
372 translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);
373
374 // The first frame is the "current" frame we're writing to.
375 auto buffer_offset{workbuffer.data()};
376 frame_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
377 buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
378 entry_buffer = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset), entries_per_frame};
379 buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
380 detail_buffer = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset), max_detail_count};
381
382 // After the current, is a ringbuffer of history frames, the current frame will be copied here
383 // before a new frame is written.
384 frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);
385
386 // If there's room for any history frames.
387 if (frame_count >= 2) {
388 buffer_offset = frame_history.data();
389 frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
390 buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
391 frame_history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset),
392 entries_per_frame};
393 buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
394 frame_history_details = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset),
395 max_detail_count};
396 } else {
397 frame_history_header = {};
398 frame_history_entries = {};
399 frame_history_details = {};
400 }
401
402 target_node_id = 0;
403 version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
404 entry_count = 0;
405 detail_count = 0;
406 frame_header->entry_count = 0;
407 frame_header->detail_count = 0;
408 output_frame_index = 0;
409 last_output_frame_index = 0;
410 is_initialized = true;
411}
412
413template <>
414bool PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
415 PerformanceEntryVersion2, PerformanceDetailVersion2>::IsInitialized()
416 const {
417 return is_initialized;
418}
419
420template <>
421u32 PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
422 PerformanceEntryVersion2,
423 PerformanceDetailVersion2>::CopyHistories(u8* out_buffer, u64 out_size) {
424 if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
425 return 0;
426 }
427
428 // Are there any new frames waiting to be output?
429 if (last_output_frame_index == output_frame_index) {
430 return 0;
431 }
432
433 PerformanceFrameHeaderVersion2* out_header{nullptr};
434 u32 out_history_size{0};
435
436 while (last_output_frame_index != output_frame_index) {
437 PerformanceFrameHeaderVersion2* history_header{nullptr};
438 std::span<PerformanceEntryVersion2> history_entries{};
439 std::span<PerformanceDetailVersion2> history_details{};
440
441 if (max_frames > 0) {
442 auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
443 history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(frame_offset);
444 frame_offset += sizeof(PerformanceFrameHeaderVersion2);
445 history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(frame_offset),
446 history_header->entry_count};
447 frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion2);
448 history_details = {reinterpret_cast<PerformanceDetailVersion2*>(frame_offset),
449 history_header->detail_count};
450 } else {
451 // Original code does not break here, but will crash when trying to dereference the
452 // header in the next if, so let's just skip this frame and continue...
453 // Hopefully this will not happen.
454 LOG_WARNING(Service_Audio,
455 "max_frames should not be 0! Skipping frame to avoid a crash");
456 last_output_frame_index++;
457 continue;
458 }
459
460 if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion2) +
461 history_header->detail_count * sizeof(PerformanceDetailVersion2) +
462 2 * sizeof(PerformanceFrameHeaderVersion2)) {
463 break;
464 }
465
466 u32 out_offset{sizeof(PerformanceFrameHeaderVersion2)};
467 auto out_entries{std::span<PerformanceEntryVersion2>(
468 reinterpret_cast<PerformanceEntryVersion2*>(out_buffer + out_offset),
469 history_header->entry_count)};
470 u32 out_entry_count{0};
471 u32 total_processing_time{0};
472 for (auto& history_entry : history_entries) {
473 if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
474 out_entries[out_entry_count++] = history_entry;
475 total_processing_time += history_entry.processed_time;
476 }
477 }
478
479 out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion2));
480 auto out_details{std::span<PerformanceDetailVersion2>(
481 reinterpret_cast<PerformanceDetailVersion2*>(out_buffer + out_offset),
482 history_header->detail_count)};
483 u32 out_detail_count{0};
484 for (auto& history_detail : history_details) {
485 if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
486 out_details[out_detail_count++] = history_detail;
487 }
488 }
489
490 out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion2));
491 out_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(out_buffer);
492 out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
493 out_header->entry_count = out_entry_count;
494 out_header->detail_count = out_detail_count;
495 out_header->next_offset = out_offset;
496 out_header->total_processing_time = total_processing_time;
497 out_header->voices_dropped = history_header->voices_dropped;
498 out_header->start_time = history_header->start_time;
499 out_header->frame_index = history_header->frame_index;
500 out_header->render_time_exceeded = history_header->render_time_exceeded;
501
502 out_history_size += out_offset;
503
504 out_buffer += out_offset;
505 out_size -= out_offset;
506 last_output_frame_index = (last_output_frame_index + 1) % max_frames;
507 }
508
509 // We're out of frames to output, so if there's enough left in the output buffer for another
510 // header, and we output at least 1 frame, set the next header to null.
511 if (out_size > sizeof(PerformanceFrameHeaderVersion2) && out_header != nullptr) {
512 std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion2));
513 }
514
515 return out_history_size;
516}
517
518template <>
519bool PerformanceManagerImpl<
520 PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
521 PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
522 const PerformanceSysDetailType sys_detail_type,
523 const s32 node_id) {
524 if (!is_initialized || detail_count > MaxDetailEntries) {
525 return false;
526 }
527
528 auto detail{&detail_buffer[detail_count++]};
529
530 addresses.translated_address = translated_buffer;
531 addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
532 offsetof(PerformanceFrameHeaderVersion2, detail_count);
533 addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
534 offsetof(PerformanceDetailVersion2, start_time);
535 addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
536 offsetof(PerformanceDetailVersion2, processed_time);
537
538 std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
539 detail->node_id = node_id;
540 detail->detail_type = static_cast<PerformanceDetailType>(sys_detail_type);
541
542 if (unk) {
543 *unk = &detail->unk_10;
544 }
545 return true;
546}
547
548template <>
549bool PerformanceManagerImpl<
550 PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
551 PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
552 const PerformanceEntryType entry_type,
553 const s32 node_id) {
554 if (!is_initialized) {
555 return false;
556 }
557
558 auto entry{&entry_buffer[entry_count++]};
559
560 addresses.translated_address = translated_buffer;
561 addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
562 offsetof(PerformanceFrameHeaderVersion2, entry_count);
563 addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
564 offsetof(PerformanceEntryVersion2, start_time);
565 addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
566 offsetof(PerformanceEntryVersion2, processed_time);
567
568 std::memset(entry, 0, sizeof(PerformanceEntryVersion2));
569 entry->node_id = node_id;
570 entry->entry_type = entry_type;
571 return true;
572}
573
574template <>
575bool PerformanceManagerImpl<
576 PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
577 PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
578 const PerformanceDetailType detail_type,
579 const PerformanceEntryType entry_type,
580 const s32 node_id) {
581 if (!is_initialized || detail_count > MaxDetailEntries) {
582 return false;
583 }
584
585 auto detail{&detail_buffer[detail_count++]};
586
587 addresses.translated_address = translated_buffer;
588 addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
589 offsetof(PerformanceFrameHeaderVersion2, detail_count);
590 addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
591 offsetof(PerformanceDetailVersion2, start_time);
592 addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
593 offsetof(PerformanceDetailVersion2, processed_time);
594
595 std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
596 detail->node_id = node_id;
597 detail->entry_type = entry_type;
598 detail->detail_type = detail_type;
599 return true;
600}
601
602template <>
603void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
604 PerformanceEntryVersion2,
605 PerformanceDetailVersion2>::TapFrame(const bool dsp_behind,
606 const u32 voices_dropped,
607 const u64 rendering_start_tick) {
608 if (!is_initialized) {
609 return;
610 }
611
612 if (max_frames > 0) {
613 if (!frame_history.empty() && !workbuffer.empty()) {
614 auto history_frame{reinterpret_cast<PerformanceFrameHeaderVersion2*>(
615 &frame_history[output_frame_index * frame_size])};
616 std::memcpy(history_frame, workbuffer.data(), frame_size);
617 history_frame->render_time_exceeded = dsp_behind;
618 history_frame->voices_dropped = voices_dropped;
619 history_frame->start_time = rendering_start_tick;
620 history_frame->frame_index = history_frame_index++;
621 }
622 output_frame_index = (output_frame_index + 1) % max_frames;
623 }
624
625 entry_count = 0;
626 detail_count = 0;
627 frame_header->entry_count = 0;
628 frame_header->detail_count = 0;
629}
630
631template <>
632bool PerformanceManagerImpl<
633 PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
634 PerformanceDetailVersion2>::IsDetailTarget(const u32 target_node_id_) const {
635 return target_node_id == target_node_id_;
636}
637
638template <>
639void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
640 PerformanceEntryVersion2,
641 PerformanceDetailVersion2>::SetDetailTarget(const u32 target_node_id_) {
642 target_node_id = target_node_id_;
643}
644
645} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <chrono>
7#include <memory>
8#include <span>
9
10#include "audio_core/common/audio_renderer_parameter.h"
11#include "audio_core/renderer/performance/performance_detail.h"
12#include "audio_core/renderer/performance/performance_entry.h"
13#include "audio_core/renderer/performance/performance_entry_addresses.h"
14#include "audio_core/renderer/performance/performance_frame_header.h"
15#include "common/common_types.h"
16
17namespace AudioCore::AudioRenderer {
18class BehaviorInfo;
19class MemoryPoolInfo;
20
21enum class PerformanceVersion {
22 Version1,
23 Version2,
24};
25
26enum class PerformanceSysDetailType {
27 PcmInt16 = 15,
28 PcmFloat = 16,
29 Adpcm = 17,
30 LightLimiter = 37,
31};
32
33enum class PerformanceState {
34 Invalid,
35 Start,
36 Stop,
37};
38
39/**
40 * Manages performance information.
41 *
42 * The performance buffer is split into frames, each comprised of:
43 * Frame header - Information about the number of entries/details and some others
44 * Entries - Created when starting to generate types of commands, such as voice
45 * commands, mix commands, sink commands etc. Details - Created for specific commands
46 * within each group. Up to MaxDetailEntries per frame.
47 *
48 * A current frame is written to by the AudioRenderer, and before it processes the next command
49 * list, the current frame is copied to a ringbuffer of history frames. These frames are then
50 * output back to the game if it supplies a performance buffer to RequestUpdate.
51 *
52 * Two versions currently exist, version 2 adds a few extra fields to the header, and a new
53 * SysDetail type which is seemingly unused.
54 */
55class PerformanceManager {
56public:
57 static constexpr size_t MaxDetailEntries = 100;
58
59 struct InParameter {
60 /* 0x00 */ s32 target_node_id;
61 /* 0x04 */ char unk04[0xC];
62 };
63 static_assert(sizeof(InParameter) == 0x10,
64 "PerformanceManager::InParameter has the wrong size!");
65
66 struct OutStatus {
67 /* 0x00 */ s32 history_size;
68 /* 0x04 */ char unk04[0xC];
69 };
70 static_assert(sizeof(OutStatus) == 0x10, "PerformanceManager::OutStatus has the wrong size!");
71
72 /**
73 * Calculate the required size for the performance workbuffer.
74 *
75 * @param behavior - Check which version is supported.
76 * @param params - Input parameters.
77 * @return Required workbuffer size.
78 */
79 static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame(
80 const BehaviorInfo& behavior, const AudioRendererParameterInternal& params) {
81 u64 entry_count{params.voices + params.effects + params.sub_mixes + params.sinks + 1};
82 switch (behavior.GetPerformanceMetricsDataFormat()) {
83 case 1:
84 return sizeof(PerformanceFrameHeaderVersion1) +
85 PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) +
86 entry_count * sizeof(PerformanceEntryVersion1);
87 case 2:
88 return sizeof(PerformanceFrameHeaderVersion2) +
89 PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion2) +
90 entry_count * sizeof(PerformanceEntryVersion2);
91 }
92
93 LOG_WARNING(Service_Audio, "Invalid PerformanceMetrics version, assuming version 1");
94 return sizeof(PerformanceFrameHeaderVersion1) +
95 PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) +
96 entry_count * sizeof(PerformanceEntryVersion1);
97 }
98
99 virtual ~PerformanceManager() = default;
100
101 /**
102 * Initialize the performance manager.
103 *
104 * @param workbuffer - Workbuffer to use for performance frames.
105 * @param workbuffer_size - Size of the workbuffer.
106 * @param params - Input parameters.
107 * @param behavior - Behaviour to check version and data format.
108 * @param memory_pool - Used to translate the workbuffer address for the DSP.
109 */
110 virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
111 const AudioRendererParameterInternal& params,
112 const BehaviorInfo& behavior, const MemoryPoolInfo& memory_pool);
113
114 /**
115 * Check if the manager is initialized.
116 *
117 * @return True if initialized, otherwise false.
118 */
119 virtual bool IsInitialized() const;
120
121 /**
122 * Copy the waiting performance frames to the output buffer.
123 *
124 * @param out_buffer - Output buffer to store performance frames.
125 * @param out_size - Size of the output buffer.
126 * @return Size in bytes that were written to the buffer.
127 */
128 virtual u32 CopyHistories(u8* out_buffer, u64 out_size);
129
130 /**
131 * Setup a new sys detail in the current frame, filling in addresses with offsets to the
132 * current workbuffer, to be written by the AudioRenderer. Note: This version is
133 * unused/incomplete.
134 *
135 * @param addresses - Filled with pointers to the new entry, which should be passed to
136 * the AudioRenderer with Performance commands to be written.
137 * @param unk - Unknown.
138 * @param sys_detail_type - Sys detail type.
139 * @param node_id - Node id for this entry.
140 * @return True if a new entry was created and the offsets are valid, otherwise false.
141 */
142 virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
143 PerformanceSysDetailType sys_detail_type, s32 node_id);
144
145 /**
146 * Setup a new entry in the current frame, filling in addresses with offsets to the current
147 * workbuffer, to be written by the AudioRenderer.
148 *
149 * @param addresses - Filled with pointers to the new entry, which should be passed to
150 * the AudioRenderer with Performance commands to be written.
151 * @param entry_type - The type of this entry. See PerformanceEntryType
152 * @param node_id - Node id for this entry.
153 * @return True if a new entry was created and the offsets are valid, otherwise false.
154 */
155 virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type,
156 s32 node_id);
157
158 /**
159 * Setup a new detail in the current frame, filling in addresses with offsets to the current
160 * workbuffer, to be written by the AudioRenderer.
161 *
162 * @param addresses - Filled with pointers to the new detail, which should be passed
163 * to the AudioRenderer with Performance commands to be written.
164 * @param entry_type - The type of this detail. See PerformanceEntryType
165 * @param node_id - Node id for this detail.
166 * @return True if a new detail was created and the offsets are valid, otherwise false.
167 */
168 virtual bool GetNextEntry(PerformanceEntryAddresses& addresses,
169 PerformanceDetailType detail_type, PerformanceEntryType entry_type,
170 s32 node_id);
171
172 /**
173 * Save the current frame to the ring buffer.
174 *
175 * @param dsp_behind - Did the AudioRenderer fall behind and not
176 * finish processing the command list?
177 * @param voices_dropped - The number of voices that were dropped.
178 * @param rendering_start_tick - The tick rendering started.
179 */
180 virtual void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick);
181
182 /**
183 * Check if the node id is a detail type.
184 *
185 * @return True if the node is a detail type, otherwise false.
186 */
187 virtual bool IsDetailTarget(u32 target_node_id) const;
188
189 /**
190 * Set the given node to be a detail type.
191 *
192 * @param target_node_id - Node to set.
193 */
194 virtual void SetDetailTarget(u32 target_node_id);
195
196private:
197 /**
198 * Create the performance manager.
199 *
200 * @param version - Performance version to create.
201 */
202 void CreateImpl(size_t version);
203
204 std::unique_ptr<PerformanceManager>
205 /// Impl for the performance manager, may be version 1 or 2.
206 impl;
207};
208
209template <PerformanceVersion Version, typename FrameHeaderVersion, typename EntryVersion,
210 typename DetailVersion>
211class PerformanceManagerImpl : public PerformanceManager {
212public:
213 void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
214 const AudioRendererParameterInternal& params, const BehaviorInfo& behavior,
215 const MemoryPoolInfo& memory_pool) override;
216 bool IsInitialized() const override;
217 u32 CopyHistories(u8* out_buffer, u64 out_size) override;
218 bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
219 PerformanceSysDetailType sys_detail_type, s32 node_id) override;
220 bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type,
221 s32 node_id) override;
222 bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceDetailType detail_type,
223 PerformanceEntryType entry_type, s32 node_id) override;
224 void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick) override;
225 bool IsDetailTarget(u32 target_node_id) const override;
226 void SetDetailTarget(u32 target_node_id) override;
227
228private:
229 /// Workbuffer used to store the current performance frame
230 std::span<u8> workbuffer{};
231 /// DSP address of the workbuffer, used by the AudioRenderer
232 CpuAddr translated_buffer{};
233 /// Current frame index
234 u32 history_frame_index{};
235 /// Current frame header
236 FrameHeaderVersion* frame_header{};
237 /// Current frame entry buffer
238 std::span<EntryVersion> entry_buffer{};
239 /// Current frame detail buffer
240 std::span<DetailVersion> detail_buffer{};
241 /// Current frame entry count
242 u32 entry_count{};
243 /// Current frame detail count
244 u32 detail_count{};
245 /// Ringbuffer of previous frames
246 std::span<u8> frame_history{};
247 /// Current history frame header
248 FrameHeaderVersion* frame_history_header{};
249 /// Current history entry buffer
250 std::span<EntryVersion> frame_history_entries{};
251 /// Current history detail buffer
252 std::span<DetailVersion> frame_history_details{};
253 /// Current history ringbuffer write index
254 u32 output_frame_index{};
255 /// Last history frame index that was written back to the game
256 u32 last_output_frame_index{};
257 /// Maximum number of history frames in the ringbuffer
258 u32 max_frames{};
259 /// Number of entries per frame
260 u32 entries_per_frame{};
261 /// Maximum number of details per frame
262 u32 max_detail_count{};
263 /// Frame size in bytes
264 u64 frame_size{};
265 /// Is the performance manager initialized?
266 bool is_initialized{};
267 /// Target node id
268 u32 target_node_id{};
269 /// Performance version in use
270 PerformanceVersion version{};
271};
272
273} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/memory/pool_mapper.h"
5#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
6#include "audio_core/renderer/upsampler/upsampler_manager.h"
7
8namespace AudioCore::AudioRenderer {
9
10CircularBufferSinkInfo::CircularBufferSinkInfo() {
11 state.fill(0);
12 parameter.fill(0);
13 type = Type::CircularBufferSink;
14
15 auto state_{reinterpret_cast<CircularBufferState*>(state.data())};
16 state_->address_info.Setup(0, 0);
17}
18
19void CircularBufferSinkInfo::CleanUp() {
20 auto state_{reinterpret_cast<DeviceState*>(state.data())};
21
22 if (state_->upsampler_info) {
23 state_->upsampler_info->manager->Free(state_->upsampler_info);
24 state_->upsampler_info = nullptr;
25 }
26
27 parameter.fill(0);
28 type = Type::Invalid;
29}
30
31void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
32 const InParameter& in_params, const PoolMapper& pool_mapper) {
33 const auto buffer_params{
34 reinterpret_cast<const CircularBufferInParameter*>(&in_params.circular_buffer)};
35 auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
36 auto current_state{reinterpret_cast<CircularBufferState*>(state.data())};
37
38 if (in_use == buffer_params->in_use && !buffer_unmapped) {
39 error_info.error_code = ResultSuccess;
40 error_info.address = CpuAddr(0);
41 out_status.writeOffset = current_state->last_pos2;
42 return;
43 }
44
45 node_id = in_params.node_id;
46 in_use = in_params.in_use;
47
48 if (in_use) {
49 buffer_unmapped =
50 !pool_mapper.TryAttachBuffer(error_info, current_state->address_info,
51 buffer_params->cpu_address, buffer_params->size);
52 *current_params = *buffer_params;
53 } else {
54 *current_params = *buffer_params;
55 }
56 out_status.writeOffset = current_state->last_pos2;
57}
58
59void CircularBufferSinkInfo::UpdateForCommandGeneration() {
60 if (in_use) {
61 auto params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
62 auto state_{reinterpret_cast<CircularBufferState*>(state.data())};
63
64 const auto pos{state_->current_pos};
65 state_->last_pos2 = state_->last_pos;
66 state_->last_pos = pos;
67
68 state_->current_pos += static_cast<s32>(params->input_count * params->sample_count *
69 GetSampleFormatByteSize(SampleFormat::PcmInt16));
70 if (params->size > 0) {
71 state_->current_pos %= params->size;
72 }
73 }
74}
75
76} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/sink/sink_info_base.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Info for a circular buffer sink.
12 */
13class CircularBufferSinkInfo : public SinkInfoBase {
14public:
15 CircularBufferSinkInfo();
16
17 /**
18 * Clean up for info, resetting it to a default state.
19 */
20 void CleanUp() override;
21
22 /**
23 * Update the info according to parameters, and write the current state to out_status.
24 *
25 * @param error_info - Output error code.
26 * @param out_status - Output status.
27 * @param in_params - Input parameters.
28 * @param pool_mapper - Used to map the circular buffer.
29 */
30 void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
31 const InParameter& in_params, const PoolMapper& pool_mapper) override;
32
33 /**
34 * Update the circular buffer on command generation, incrementing its current offsets.
35 */
36 void UpdateForCommandGeneration() override;
37};
38static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase),
39 "CircularBufferSinkInfo is too large!");
40
41} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/sink/device_sink_info.h"
5#include "audio_core/renderer/upsampler/upsampler_manager.h"
6
7namespace AudioCore::AudioRenderer {
8
9DeviceSinkInfo::DeviceSinkInfo() {
10 state.fill(0);
11 parameter.fill(0);
12 type = Type::DeviceSink;
13}
14
15void DeviceSinkInfo::CleanUp() {
16 auto state_{reinterpret_cast<DeviceState*>(state.data())};
17
18 if (state_->upsampler_info) {
19 state_->upsampler_info->manager->Free(state_->upsampler_info);
20 state_->upsampler_info = nullptr;
21 }
22
23 parameter.fill(0);
24 type = Type::Invalid;
25}
26
27void DeviceSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
28 const InParameter& in_params,
29 [[maybe_unused]] const PoolMapper& pool_mapper) {
30
31 const auto device_params{reinterpret_cast<const DeviceInParameter*>(&in_params.device)};
32 auto current_params{reinterpret_cast<DeviceInParameter*>(parameter.data())};
33
34 if (in_use == in_params.in_use) {
35 current_params->downmix_enabled = device_params->downmix_enabled;
36 current_params->downmix_coeff = device_params->downmix_coeff;
37 } else {
38 type = in_params.type;
39 in_use = in_params.in_use;
40 node_id = in_params.node_id;
41 *current_params = *device_params;
42 }
43
44 auto current_state{reinterpret_cast<DeviceState*>(state.data())};
45
46 for (size_t i = 0; i < current_state->downmix_coeff.size(); i++) {
47 current_state->downmix_coeff[i] = current_params->downmix_coeff[i];
48 }
49
50 std::memset(&out_status, 0, sizeof(OutStatus));
51 error_info.error_code = ResultSuccess;
52 error_info.address = CpuAddr(0);
53}
54
55void DeviceSinkInfo::UpdateForCommandGeneration() {}
56
57} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/sink/sink_info_base.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Info for a device sink.
12 */
13class DeviceSinkInfo : public SinkInfoBase {
14public:
15 DeviceSinkInfo();
16
17 /**
18 * Clean up for info, resetting it to a default state.
19 */
20 void CleanUp() override;
21
22 /**
23 * Update the info according to parameters, and write the current state to out_status.
24 *
25 * @param error_info - Output error code.
26 * @param out_status - Output status.
27 * @param in_params - Input parameters.
28 * @param pool_mapper - Unused.
29 */
30 void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
31 const InParameter& in_params, const PoolMapper& pool_mapper) override;
32
33 /**
34 * Update the device sink on command generation, unused.
35 */
36 void UpdateForCommandGeneration() override;
37};
38static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!");
39
40} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/sink/sink_context.h"
5
6namespace AudioCore::AudioRenderer {
7
8void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) {
9 sink_infos = sink_infos_;
10 sink_count = sink_count_;
11}
12
13SinkInfoBase* SinkContext::GetInfo(const u32 index) {
14 return &sink_infos[index];
15}
16
17u32 SinkContext::GetCount() const {
18 return sink_count;
19}
20
21} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/sink/sink_info_base.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12/**
13 * Manages output sinks.
14 */
15class SinkContext {
16public:
17 /**
18 * Initialize the sink context.
19 *
20 * @param sink_infos - Workbuffer for the sinks.
21 * @param sink_count - Number of sinks in the buffer.
22 */
23 void Initialize(std::span<SinkInfoBase> sink_infos, u32 sink_count);
24
25 /**
26 * Get a given index's info.
27 *
28 * @param index - Sink index to get.
29 * @return The sink info base for the given index.
30 */
31 SinkInfoBase* GetInfo(u32 index);
32
33 /**
34 * Get the current number of sinks.
35 *
36 * @return The number of sinks.
37 */
38 u32 GetCount() const;
39
40private:
41 /// Buffer of sink infos
42 std::span<SinkInfoBase> sink_infos{};
43 /// Number of sinks in the buffer
44 u32 sink_count{};
45};
46
47} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/memory/pool_mapper.h"
5#include "audio_core/renderer/sink/sink_info_base.h"
6
7namespace AudioCore::AudioRenderer {
8
9void SinkInfoBase::CleanUp() {
10 type = Type::Invalid;
11}
12
13void SinkInfoBase::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
14 [[maybe_unused]] const InParameter& in_params,
15 [[maybe_unused]] const PoolMapper& pool_mapper) {
16 std::memset(&out_status, 0, sizeof(OutStatus));
17 error_info.error_code = ResultSuccess;
18 error_info.address = CpuAddr(0);
19}
20
21void SinkInfoBase::UpdateForCommandGeneration() {}
22
23SinkInfoBase::DeviceState* SinkInfoBase::GetDeviceState() {
24 return reinterpret_cast<DeviceState*>(state.data());
25}
26
27SinkInfoBase::Type SinkInfoBase::GetType() const {
28 return type;
29}
30
31bool SinkInfoBase::IsUsed() const {
32 return in_use;
33}
34
35bool SinkInfoBase::ShouldSkip() const {
36 return buffer_unmapped;
37}
38
39u32 SinkInfoBase::GetNodeId() const {
40 return node_id;
41}
42
43u8* SinkInfoBase::GetState() {
44 return state.data();
45}
46
47u8* SinkInfoBase::GetParameter() {
48 return parameter.data();
49}
50
51} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/behavior/behavior_info.h"
10#include "audio_core/renderer/memory/address_info.h"
11#include "common/common_types.h"
12#include "common/fixed_point.h"
13
14namespace AudioCore::AudioRenderer {
15struct UpsamplerInfo;
16class PoolMapper;
17
18/**
19 * Base for the circular buffer and device sinks, holding their states for the AudioRenderer and
20 * their parametetrs for generating sink commands.
21 */
22class SinkInfoBase {
23public:
24 enum class Type : u8 {
25 Invalid,
26 DeviceSink,
27 CircularBufferSink,
28 };
29
30 struct DeviceInParameter {
31 /* 0x000 */ char name[0x100];
32 /* 0x100 */ u32 input_count;
33 /* 0x104 */ std::array<s8, MaxChannels> inputs;
34 /* 0x10A */ char unk10A[0x1];
35 /* 0x10B */ bool downmix_enabled;
36 /* 0x10C */ std::array<f32, 4> downmix_coeff;
37 };
38 static_assert(sizeof(DeviceInParameter) == 0x11C, "DeviceInParameter has the wrong size!");
39
40 struct DeviceState {
41 /* 0x00 */ UpsamplerInfo* upsampler_info;
42 /* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff;
43 /* 0x18 */ char unk18[0x18];
44 };
45 static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!");
46
47 struct CircularBufferInParameter {
48 /* 0x00 */ u64 cpu_address;
49 /* 0x08 */ u32 size;
50 /* 0x0C */ u32 input_count;
51 /* 0x10 */ u32 sample_count;
52 /* 0x14 */ u32 previous_pos;
53 /* 0x18 */ SampleFormat format;
54 /* 0x1C */ std::array<s8, MaxChannels> inputs;
55 /* 0x22 */ bool in_use;
56 /* 0x23 */ char unk23[0x5];
57 };
58 static_assert(sizeof(CircularBufferInParameter) == 0x28,
59 "CircularBufferInParameter has the wrong size!");
60
61 struct CircularBufferState {
62 /* 0x00 */ u32 last_pos2;
63 /* 0x04 */ s32 current_pos;
64 /* 0x08 */ u32 last_pos;
65 /* 0x0C */ char unk0C[0x4];
66 /* 0x10 */ AddressInfo address_info;
67 };
68 static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
69
70 struct InParameter {
71 /* 0x000 */ Type type;
72 /* 0x001 */ bool in_use;
73 /* 0x004 */ u32 node_id;
74 /* 0x008 */ char unk08[0x18];
75 union {
76 /* 0x020 */ DeviceInParameter device;
77 /* 0x020 */ CircularBufferInParameter circular_buffer;
78 };
79 };
80 static_assert(sizeof(InParameter) == 0x140, "SinkInfoBase::InParameter has the wrong size!");
81
82 struct OutStatus {
83 /* 0x00 */ u32 writeOffset;
84 /* 0x04 */ char unk04[0x1C];
85 }; // size == 0x20
86 static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
87
88 virtual ~SinkInfoBase() = default;
89
90 /**
91 * Clean up for info, resetting it to a default state.
92 */
93 virtual void CleanUp();
94
95 /**
96 * Update the info according to parameters, and write the current state to out_status.
97 *
98 * @param error_info - Output error code.
99 * @param out_status - Output status.
100 * @param in_params - Input parameters.
101 * @param pool_mapper - Used to map the circular buffer.
102 */
103 virtual void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
104 [[maybe_unused]] const InParameter& in_params,
105 [[maybe_unused]] const PoolMapper& pool_mapper);
106
107 /**
108 * Update the circular buffer on command generation, incrementing its current offsets.
109 */
110 virtual void UpdateForCommandGeneration();
111
112 /**
113 * Get the state as a device sink.
114 *
115 * @return Device state.
116 */
117 DeviceState* GetDeviceState();
118
119 /**
120 * Get the type of this sink.
121 *
122 * @return Either Device, Circular, or Invalid.
123 */
124 Type GetType() const;
125
126 /**
127 * Check if this sink is in use.
128 *
129 * @return True if used, otherwise false.
130 */
131 bool IsUsed() const;
132
133 /**
134 * Check if this sink should be skipped for updates.
135 *
136 * @return True if it should be skipped, otherwise false.
137 */
138 bool ShouldSkip() const;
139
140 /**
141 * Get the node if of this sink.
142 *
143 * @return Node id for this sink.
144 */
145 u32 GetNodeId() const;
146
147 /**
148 * Get the state of this sink.
149 *
150 * @return Pointer to the state, must be cast to the correct type.
151 */
152 u8* GetState();
153
154 /**
155 * Get the parameters of this sink.
156 *
157 * @return Pointer to the parameters, must be cast to the correct type.
158 */
159 u8* GetParameter();
160
161protected:
162 /// Type of this sink
163 Type type{Type::Invalid};
164 /// Is this sink in use?
165 bool in_use{};
166 /// Is this sink's buffer unmapped? Circular only
167 bool buffer_unmapped{};
168 /// Node id for this sink
169 u32 node_id{};
170 /// State buffer for this sink
171 std::array<u8, std::max(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
172 /// Parameter buffer for this sink
173 std::array<u8, std::max(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))>
174 parameter{};
175};
176
177} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/common/audio_renderer_parameter.h"
5#include "audio_core/common/workbuffer_allocator.h"
6#include "audio_core/renderer/behavior/behavior_info.h"
7#include "audio_core/renderer/splitter/splitter_context.h"
8#include "common/alignment.h"
9
10namespace AudioCore::AudioRenderer {
11
12SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id,
13 const s32 destination_id) {
14 return splitter_infos[splitter_id].GetData(destination_id);
15}
16
17SplitterInfo& SplitterContext::GetInfo(const s32 splitter_id) {
18 return splitter_infos[splitter_id];
19}
20
21u32 SplitterContext::GetDataCount() const {
22 return destinations_count;
23}
24
25u32 SplitterContext::GetInfoCount() const {
26 return info_count;
27}
28
29SplitterDestinationData& SplitterContext::GetData(const u32 index) {
30 return splitter_destinations[index];
31}
32
33void SplitterContext::Setup(std::span<SplitterInfo> splitter_infos_, const u32 splitter_info_count_,
34 SplitterDestinationData* splitter_destinations_,
35 const u32 destination_count_, const bool splitter_bug_fixed_) {
36 splitter_infos = splitter_infos_;
37 info_count = splitter_info_count_;
38 splitter_destinations = splitter_destinations_;
39 destinations_count = destination_count_;
40 splitter_bug_fixed = splitter_bug_fixed_;
41}
42
43bool SplitterContext::UsingSplitter() const {
44 return splitter_infos.size() > 0 && info_count > 0 && splitter_destinations != nullptr &&
45 destinations_count > 0;
46}
47
48void SplitterContext::ClearAllNewConnectionFlag() {
49 for (s32 i = 0; i < info_count; i++) {
50 splitter_infos[i].SetNewConnectionFlag();
51 }
52}
53
54bool SplitterContext::Initialize(const BehaviorInfo& behavior,
55 const AudioRendererParameterInternal& params,
56 WorkbufferAllocator& allocator) {
57 if (behavior.IsSplitterSupported() && params.splitter_infos > 0 &&
58 params.splitter_destinations > 0) {
59 splitter_infos = allocator.Allocate<SplitterInfo>(params.splitter_infos, 0x10);
60
61 for (u32 i = 0; i < params.splitter_infos; i++) {
62 std::construct_at<SplitterInfo>(&splitter_infos[i], static_cast<s32>(i));
63 }
64
65 if (splitter_infos.size() == 0) {
66 splitter_infos = {};
67 return false;
68 }
69
70 splitter_destinations =
71 allocator.Allocate<SplitterDestinationData>(params.splitter_destinations, 0x10).data();
72
73 for (s32 i = 0; i < params.splitter_destinations; i++) {
74 std::construct_at<SplitterDestinationData>(&splitter_destinations[i], i);
75 }
76
77 if (params.splitter_destinations <= 0) {
78 splitter_infos = {};
79 splitter_destinations = nullptr;
80 return false;
81 }
82
83 Setup(splitter_infos, params.splitter_infos, splitter_destinations,
84 params.splitter_destinations, behavior.IsSplitterBugFixed());
85 }
86 return true;
87}
88
89bool SplitterContext::Update(const u8* input, u32& consumed_size) {
90 auto in_params{reinterpret_cast<const InParameterHeader*>(input)};
91
92 if (destinations_count == 0 || info_count == 0) {
93 consumed_size = 0;
94 return true;
95 }
96
97 if (in_params->magic != GetSplitterInParamHeaderMagic()) {
98 consumed_size = 0;
99 return false;
100 }
101
102 for (auto& splitter_info : splitter_infos) {
103 splitter_info.ClearNewConnectionFlag();
104 }
105
106 u32 offset{sizeof(InParameterHeader)};
107 offset = UpdateInfo(input, offset, in_params->info_count);
108 offset = UpdateData(input, offset, in_params->destination_count);
109
110 consumed_size = Common::AlignUp(offset, 0x10);
111 return true;
112}
113
114u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_count) {
115 for (u32 i = 0; i < splitter_count; i++) {
116 auto info_header{reinterpret_cast<const SplitterInfo::InParameter*>(input + offset)};
117
118 if (info_header->magic != GetSplitterInfoMagic()) {
119 continue;
120 }
121
122 if (info_header->id < 0 || info_header->id > info_count) {
123 break;
124 }
125
126 auto& info{splitter_infos[info_header->id]};
127 RecomposeDestination(info, info_header);
128
129 offset += info.Update(info_header);
130 }
131
132 return offset;
133}
134
135u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) {
136 for (u32 i = 0; i < count; i++) {
137 auto data_header{
138 reinterpret_cast<const SplitterDestinationData::InParameter*>(input + offset)};
139
140 if (data_header->magic != GetSplitterSendDataMagic()) {
141 continue;
142 }
143
144 if (data_header->id < 0 || data_header->id > destinations_count) {
145 continue;
146 }
147
148 splitter_destinations[data_header->id].Update(*data_header);
149 offset += sizeof(SplitterDestinationData::InParameter);
150 }
151
152 return offset;
153}
154
155void SplitterContext::UpdateInternalState() {
156 for (s32 i = 0; i < info_count; i++) {
157 splitter_infos[i].UpdateInternalState();
158 }
159}
160
161void SplitterContext::RecomposeDestination(SplitterInfo& out_info,
162 const SplitterInfo::InParameter* info_header) {
163 auto destination{out_info.GetData(0)};
164 while (destination != nullptr) {
165 auto dest{destination->GetNext()};
166 destination->SetNext(nullptr);
167 destination = dest;
168 }
169 out_info.SetDestinations(nullptr);
170
171 auto dest_count{info_header->destination_count};
172 if (!splitter_bug_fixed) {
173 dest_count = std::min(dest_count, GetDestCountPerInfoForCompat());
174 }
175
176 if (dest_count == 0) {
177 return;
178 }
179
180 std::span<const u32> destination_ids{reinterpret_cast<const u32*>(&info_header[1]), dest_count};
181
182 auto head{&splitter_destinations[destination_ids[0]]};
183 auto current_destination{head};
184 for (u32 i = 1; i < dest_count; i++) {
185 auto next_destination{&splitter_destinations[destination_ids[i]]};
186 current_destination->SetNext(next_destination);
187 current_destination = next_destination;
188 }
189
190 out_info.SetDestinations(head);
191 out_info.SetDestinationCount(dest_count);
192}
193
194u32 SplitterContext::GetDestCountPerInfoForCompat() const {
195 if (info_count <= 0) {
196 return 0;
197 }
198 return static_cast<u32>(destinations_count / info_count);
199}
200
201u64 SplitterContext::CalcWorkBufferSize(const BehaviorInfo& behavior,
202 const AudioRendererParameterInternal& params) {
203 u64 size{0};
204 if (!behavior.IsSplitterSupported()) {
205 return size;
206 }
207
208 size += params.splitter_destinations * sizeof(SplitterDestinationData) +
209 params.splitter_infos * sizeof(SplitterInfo);
210
211 if (behavior.IsSplitterBugFixed()) {
212 size += Common::AlignUp(params.splitter_destinations * sizeof(u32), 0x10);
213 }
214 return size;
215}
216
217} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/splitter/splitter_destinations_data.h"
9#include "audio_core/renderer/splitter/splitter_info.h"
10#include "common/common_types.h"
11
12namespace AudioCore {
13struct AudioRendererParameterInternal;
14class WorkbufferAllocator;
15
16namespace AudioRenderer {
17class BehaviorInfo;
18
19/**
20 * The splitter allows much more control over how sound is mixed together.
21 * Previously, one mix can only connect to one other, and you may need
22 * more mixes (and duplicate processing) to achieve the same result.
23 * With the splitter, many-to-one and one-to-many mixing is possible.
24 * This was added in revision 2.
25 * Had a bug with incorrect numbers of destinations, fixed in revision 5.
26 */
27class SplitterContext {
28 struct InParameterHeader {
29 /* 0x00 */ u32 magic; // 'SNDH'
30 /* 0x04 */ s32 info_count;
31 /* 0x08 */ s32 destination_count;
32 /* 0x0C */ char unk0C[0x14];
33 };
34 static_assert(sizeof(InParameterHeader) == 0x20,
35 "SplitterContext::InParameterHeader has the wrong size!");
36
37public:
38 /**
39 * Get a destination mix from the given splitter and destination index.
40 *
41 * @param splitter_id - Splitter index to get from.
42 * @param destination_id - Destination index within the splitter.
43 * @return Pointer to the found destination. May be nullptr.
44 */
45 SplitterDestinationData* GetDesintationData(s32 splitter_id, s32 destination_id);
46
47 /**
48 * Get a splitter from the given index.
49 *
50 * @param index - Index of the desired splitter.
51 * @return Splitter requested.
52 */
53 SplitterInfo& GetInfo(s32 index);
54
55 /**
56 * Get the total number of splitter destinations.
57 *
58 * @return Number of destiantions.
59 */
60 u32 GetDataCount() const;
61
62 /**
63 * Get the total number of splitters.
64 *
65 * @return Number of splitters.
66 */
67 u32 GetInfoCount() const;
68
69 /**
70 * Get a specific global destination.
71 *
72 * @param index - Index of the desired destination.
73 * @return The requested destination.
74 */
75 SplitterDestinationData& GetData(u32 index);
76
77 /**
78 * Check if the splitter is in use.
79 *
80 * @return True if any splitter or destination is in use, otherwise false.
81 */
82 bool UsingSplitter() const;
83
84 /**
85 * Mark all splitters as having new connections.
86 */
87 void ClearAllNewConnectionFlag();
88
89 /**
90 * Initialize the context.
91 *
92 * @param behavior - Used to check for splitter support.
93 * @param params - Input parameters.
94 * @param allocator - Allocator used to allocate workbuffer memory.
95 */
96 bool Initialize(const BehaviorInfo& behavior, const AudioRendererParameterInternal& params,
97 WorkbufferAllocator& allocator);
98
99 /**
100 * Update the context.
101 *
102 * @param input - Input buffer with the new info,
103 * expected to point to a InParameterHeader.
104 * @param consumed_size - Output with the number of bytes consumed from input.
105 */
106 bool Update(const u8* input, u32& consumed_size);
107
108 /**
109 * Update the splitters.
110 *
111 * @param input - Input buffer with the new info.
112 * @param offset - Current offset within the input buffer,
113 * input + offset should point to a SplitterInfo::InParameter.
114 * @param splitter_count - Number of splitters in the input buffer.
115 * @return Number of bytes consumed in input.
116 */
117 u32 UpdateInfo(const u8* input, u32 offset, u32 splitter_count);
118
119 /**
120 * Update the splitters.
121 *
122 * @param input - Input buffer with the new info.
123 * @param offset - Current offset within the input buffer,
124 * input + offset should point to a
125 * SplitterDestinationData::InParameter.
126 * @param destination_count - Number of destinations in the input buffer.
127 * @return Number of bytes consumed in input.
128 */
129 u32 UpdateData(const u8* input, u32 offset, u32 destination_count);
130
131 /**
132 * Update the state of all destinations in all splitters.
133 */
134 void UpdateInternalState();
135
136 /**
137 * Replace the given splitter's destinations with new ones.
138 *
139 * @param out_info - Splitter to recompose.
140 * @param info_header - Input parameters containing new destination ids.
141 */
142 void RecomposeDestination(SplitterInfo& out_info, const SplitterInfo::InParameter* info_header);
143
144 /**
145 * Old calculation for destinations, this is the thing the splitter bug fixes.
146 * Left for compatibility, and now min'd with the actual count to not bug.
147 *
148 * @return Number of splitter destinations.
149 */
150 u32 GetDestCountPerInfoForCompat() const;
151
152 /**
153 * Calculate the size of the required workbuffer for splitters and destinations.
154 *
155 * @param behavior - Used to check splitter features.
156 * @param params - Input parameters with splitter/destination counts.
157 * @return Required buffer size.
158 */
159 static u64 CalcWorkBufferSize(const BehaviorInfo& behavior,
160 const AudioRendererParameterInternal& params);
161
162private:
163 /**
164 * Setup the context.
165 *
166 * @param splitter_infos - Workbuffer for splitters.
167 * @param splitter_info_count - Number of splitters in the workbuffer.
168 * @param splitter_destinations - Workbuffer for splitter destinations.
169 * @param destination_count - Number of destinations in the workbuffer.
170 * @param splitter_bug_fixed - Is the splitter bug fixed?
171 */
172 void Setup(std::span<SplitterInfo> splitter_infos, u32 splitter_info_count,
173 SplitterDestinationData* splitter_destinations, u32 destination_count,
174 bool splitter_bug_fixed);
175
176 /// Workbuffer for splitters
177 std::span<SplitterInfo> splitter_infos{};
178 /// Number of splitters in buffer
179 s32 info_count{};
180 /// Workbuffer for destinations
181 SplitterDestinationData* splitter_destinations{};
182 /// Number of destinations in buffer
183 s32 destinations_count{};
184 /// Is the splitter bug fixed?
185 bool splitter_bug_fixed{};
186};
187
188} // namespace AudioRenderer
189} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/splitter/splitter_destinations_data.h"
5
6namespace AudioCore::AudioRenderer {
7
8SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {}
9
10void SplitterDestinationData::ClearMixVolume() {
11 mix_volumes.fill(0.0f);
12 prev_mix_volumes.fill(0.0f);
13}
14
15s32 SplitterDestinationData::GetId() const {
16 return id;
17}
18
19bool SplitterDestinationData::IsConfigured() const {
20 return in_use && destination_id != UnusedMixId;
21}
22
23s32 SplitterDestinationData::GetMixId() const {
24 return destination_id;
25}
26
27f32 SplitterDestinationData::GetMixVolume(const u32 index) const {
28 if (index >= mix_volumes.size()) {
29 LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolume Invalid index {}", index);
30 return 0.0f;
31 }
32 return mix_volumes[index];
33}
34
35std::span<f32> SplitterDestinationData::GetMixVolume() {
36 return mix_volumes;
37}
38
39f32 SplitterDestinationData::GetMixVolumePrev(const u32 index) const {
40 if (index >= prev_mix_volumes.size()) {
41 LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolumePrev Invalid index {}",
42 index);
43 return 0.0f;
44 }
45 return prev_mix_volumes[index];
46}
47
48std::span<f32> SplitterDestinationData::GetMixVolumePrev() {
49 return prev_mix_volumes;
50}
51
52void SplitterDestinationData::Update(const InParameter& params) {
53 if (params.id != id || params.magic != GetSplitterSendDataMagic()) {
54 return;
55 }
56
57 destination_id = params.mix_id;
58 mix_volumes = params.mix_volumes;
59
60 if (!in_use && params.in_use) {
61 prev_mix_volumes = mix_volumes;
62 need_update = false;
63 }
64
65 in_use = params.in_use;
66}
67
68void SplitterDestinationData::MarkAsNeedToUpdateInternalState() {
69 need_update = true;
70}
71
72void SplitterDestinationData::UpdateInternalState() {
73 if (in_use && need_update) {
74 prev_mix_volumes = mix_volumes;
75 }
76 need_update = false;
77}
78
79SplitterDestinationData* SplitterDestinationData::GetNext() const {
80 return next;
81}
82
83void SplitterDestinationData::SetNext(SplitterDestinationData* next_) {
84 next = next_;
85}
86
87} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8
9#include "audio_core/common/common.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Represents a mixing node, can be connected to a previous and next destination forming a chain
15 * that a certain mix buffer will pass through to output.
16 */
17class SplitterDestinationData {
18public:
19 struct InParameter {
20 /* 0x00 */ u32 magic; // 'SNDD'
21 /* 0x04 */ s32 id;
22 /* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes;
23 /* 0x68 */ u32 mix_id;
24 /* 0x6C */ bool in_use;
25 };
26 static_assert(sizeof(InParameter) == 0x70,
27 "SplitterDestinationData::InParameter has the wrong size!");
28
29 SplitterDestinationData(s32 id);
30
31 /**
32 * Reset the mix volumes for this destination.
33 */
34 void ClearMixVolume();
35
36 /**
37 * Get the id of this destination.
38 *
39 * @return Id for this destination.
40 */
41 s32 GetId() const;
42
43 /**
44 * Check if this destination is correctly configured.
45 *
46 * @return True if configured, otherwise false.
47 */
48 bool IsConfigured() const;
49
50 /**
51 * Get the mix id for this destination.
52 *
53 * @return Mix id for this destination.
54 */
55 s32 GetMixId() const;
56
57 /**
58 * Get the current mix volume of a given index in this destination.
59 *
60 * @param index - Mix buffer index to get the volume for.
61 * @return Current volume of the specified mix.
62 */
63 f32 GetMixVolume(u32 index) const;
64
65 /**
66 * Get the current mix volumes for all mix buffers in this destination.
67 *
68 * @return Span of current mix buffer volumes.
69 */
70 std::span<f32> GetMixVolume();
71
72 /**
73 * Get the previous mix volume of a given index in this destination.
74 *
75 * @param index - Mix buffer index to get the volume for.
76 * @return Previous volume of the specified mix.
77 */
78 f32 GetMixVolumePrev(u32 index) const;
79
80 /**
81 * Get the previous mix volumes for all mix buffers in this destination.
82 *
83 * @return Span of previous mix buffer volumes.
84 */
85 std::span<f32> GetMixVolumePrev();
86
87 /**
88 * Update this destination.
89 *
90 * @param params - Inpout parameters to update the destination.
91 */
92 void Update(const InParameter& params);
93
94 /**
95 * Mark this destination as needing its volumes updated.
96 */
97 void MarkAsNeedToUpdateInternalState();
98
99 /**
100 * Copy current volumes to previous if an update is required.
101 */
102 void UpdateInternalState();
103
104 /**
105 * Get the next destination in the mix chain.
106 *
107 * @return The next splitter destination, may be nullptr if this is the last in the chain.
108 */
109 SplitterDestinationData* GetNext() const;
110
111 /**
112 * Set the next destination in the mix chain.
113 *
114 * @param next - Destination this one is to be connected to.
115 */
116 void SetNext(SplitterDestinationData* next);
117
118private:
119 /// Id of this destination
120 const s32 id;
121 /// Mix id this destination represents
122 s32 destination_id{UnusedMixId};
123 /// Current mix volumes
124 std::array<f32, MaxMixBuffers> mix_volumes{0.0f};
125 /// Previous mix volumes
126 std::array<f32, MaxMixBuffers> prev_mix_volumes{0.0f};
127 /// Next destination in the mix chain
128 SplitterDestinationData* next{};
129 /// Is this destiantion in use?
130 bool in_use{};
131 /// Does this destiantion need its volumes updated?
132 bool need_update{};
133};
134
135} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/splitter/splitter_info.h"
5
6namespace AudioCore::AudioRenderer {
7
8SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {}
9
10void SplitterInfo::InitializeInfos(SplitterInfo* splitters, const u32 count) {
11 if (splitters == nullptr) {
12 return;
13 }
14
15 for (u32 i = 0; i < count; i++) {
16 auto& splitter{splitters[i]};
17 splitter.destinations = nullptr;
18 splitter.destination_count = 0;
19 splitter.has_new_connection = true;
20 }
21}
22
23u32 SplitterInfo::Update(const InParameter* params) {
24 if (params->id != id) {
25 return 0;
26 }
27 sample_rate = params->sample_rate;
28 has_new_connection = true;
29 return static_cast<u32>((sizeof(InParameter) + 3 * sizeof(s32)) +
30 params->destination_count * sizeof(s32));
31}
32
33SplitterDestinationData* SplitterInfo::GetData(const u32 destination_id) {
34 auto out_destination{destinations};
35 u32 i{0};
36 while (i < destination_id) {
37 if (out_destination == nullptr) {
38 break;
39 }
40 out_destination = out_destination->GetNext();
41 i++;
42 }
43
44 return out_destination;
45}
46
47u32 SplitterInfo::GetDestinationCount() const {
48 return destination_count;
49}
50
51void SplitterInfo::SetDestinationCount(const u32 count) {
52 destination_count = count;
53}
54
55bool SplitterInfo::HasNewConnection() const {
56 return has_new_connection;
57}
58
59void SplitterInfo::ClearNewConnectionFlag() {
60 has_new_connection = false;
61}
62
63void SplitterInfo::SetNewConnectionFlag() {
64 has_new_connection = true;
65}
66
67void SplitterInfo::UpdateInternalState() {
68 auto destination{destinations};
69 while (destination != nullptr) {
70 destination->UpdateInternalState();
71 destination = destination->GetNext();
72 }
73}
74
75void SplitterInfo::SetDestinations(SplitterDestinationData* destinations_) {
76 destinations = destinations_;
77}
78
79} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/splitter/splitter_destinations_data.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Represents a splitter, wraps multiple output destinations to split an input mix into.
12 */
13class SplitterInfo {
14public:
15 struct InParameter {
16 /* 0x00 */ u32 magic; // 'SNDI'
17 /* 0x04 */ s32 id;
18 /* 0x08 */ u32 sample_rate;
19 /* 0x0C */ u32 destination_count;
20 };
21 static_assert(sizeof(InParameter) == 0x10, "SplitterInfo::InParameter has the wrong size!");
22
23 explicit SplitterInfo(s32 id);
24
25 /**
26 * Initialize the given splitters.
27 *
28 * @param splitters - Splitters to initialize.
29 * @param count - Number of splitters given.
30 */
31 static void InitializeInfos(SplitterInfo* splitters, u32 count);
32
33 /**
34 * Update this splitter.
35 *
36 * @param params - Input parameters to update with.
37 * @return The size in bytes of this splitter.
38 */
39 u32 Update(const InParameter* params);
40
41 /**
42 * Get a destination in this splitter.
43 *
44 * @param id - Destination id to get.
45 * @return Pointer to the destination, may be nullptr.
46 */
47 SplitterDestinationData* GetData(u32 id);
48
49 /**
50 * Get the number of destinations in this splitter.
51 *
52 * @return The number of destiantions.
53 */
54 u32 GetDestinationCount() const;
55
56 /**
57 * Set the number of destinations in this splitter.
58 *
59 * @param count - The new number of destiantions.
60 */
61 void SetDestinationCount(u32 count);
62
63 /**
64 * Check if the splitter has a new connection.
65 *
66 * @return True if there is a new connection, otherwise false.
67 */
68 bool HasNewConnection() const;
69
70 /**
71 * Reset the new connection flag.
72 */
73 void ClearNewConnectionFlag();
74
75 /**
76 * Mark as having a new connection.
77 */
78 void SetNewConnectionFlag();
79
80 /**
81 * Update the state of all destinations.
82 */
83 void UpdateInternalState();
84
85 /**
86 * Set this splitter's destinations.
87 *
88 * @param destinations - The new destination list for this splitter.
89 */
90 void SetDestinations(SplitterDestinationData* destinations);
91
92private:
93 /// Id of this splitter
94 s32 id;
95 /// Sample rate of this splitter
96 u32 sample_rate{};
97 /// Number of destinations in this splitter
98 u32 destination_count{};
99 /// Does this splitter have a new connection?
100 bool has_new_connection{true};
101 /// Pointer to the destinations of this splitter
102 SplitterDestinationData* destinations{};
103 /// Number of channels this splitter manages
104 u32 channel_count{};
105};
106
107} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <chrono>
5#include <span>
6
7#include "audio_core/audio_core.h"
8#include "audio_core/common/audio_renderer_parameter.h"
9#include "audio_core/common/common.h"
10#include "audio_core/common/feature_support.h"
11#include "audio_core/common/workbuffer_allocator.h"
12#include "audio_core/renderer/adsp/adsp.h"
13#include "audio_core/renderer/behavior/info_updater.h"
14#include "audio_core/renderer/command/command_buffer.h"
15#include "audio_core/renderer/command/command_generator.h"
16#include "audio_core/renderer/command/command_list_header.h"
17#include "audio_core/renderer/effect/effect_info_base.h"
18#include "audio_core/renderer/effect/effect_result_state.h"
19#include "audio_core/renderer/memory/memory_pool_info.h"
20#include "audio_core/renderer/memory/pool_mapper.h"
21#include "audio_core/renderer/mix/mix_info.h"
22#include "audio_core/renderer/nodes/edge_matrix.h"
23#include "audio_core/renderer/nodes/node_states.h"
24#include "audio_core/renderer/sink/sink_info_base.h"
25#include "audio_core/renderer/system.h"
26#include "audio_core/renderer/upsampler/upsampler_info.h"
27#include "audio_core/renderer/voice/voice_channel_resource.h"
28#include "audio_core/renderer/voice/voice_info.h"
29#include "audio_core/renderer/voice/voice_state.h"
30#include "common/alignment.h"
31#include "core/core.h"
32#include "core/core_timing.h"
33#include "core/hle/kernel/k_event.h"
34#include "core/hle/kernel/k_transfer_memory.h"
35#include "core/memory.h"
36
37namespace AudioCore::AudioRenderer {
38
39u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
40 BehaviorInfo behavior;
41 behavior.SetUserLibRevision(params.revision);
42
43 u64 size{0};
44
45 size += Common::AlignUp(params.mixes * sizeof(s32), 0x40);
46 size += params.sub_mixes * MaxEffects * sizeof(s32);
47 size += (params.sub_mixes + 1) * sizeof(MixInfo);
48 size += params.voices * (sizeof(VoiceInfo) + sizeof(VoiceChannelResource) + sizeof(VoiceState));
49 size += Common::AlignUp((params.sub_mixes + 1) * sizeof(MixInfo*), 0x10);
50 size += Common::AlignUp(params.voices * sizeof(VoiceInfo*), 0x10);
51 size += Common::AlignUp(((params.sinks + params.sub_mixes) * TargetSampleCount * sizeof(s32) +
52 params.sample_count * sizeof(s32)) *
53 (params.mixes + MaxChannels),
54 0x40);
55
56 if (behavior.IsSplitterSupported()) {
57 const auto node_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)};
58 const auto edge_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)};
59 size += Common::AlignUp(node_size + edge_size, 0x10);
60 }
61
62 size += SplitterContext::CalcWorkBufferSize(behavior, params);
63 size += (params.effects + params.voices * MaxWaveBuffers) * sizeof(MemoryPoolInfo);
64
65 if (behavior.IsEffectInfoVersion2Supported()) {
66 size += params.effects * sizeof(EffectResultState);
67 }
68 size += 0x50;
69
70 size = Common::AlignUp(size, 0x40);
71
72 size += (params.sinks + params.sub_mixes) * sizeof(UpsamplerInfo);
73 size += params.effects * sizeof(EffectInfoBase);
74 size += Common::AlignUp(params.voices * sizeof(VoiceState), 0x40);
75 size += params.sinks * sizeof(SinkInfoBase);
76
77 if (behavior.IsEffectInfoVersion2Supported()) {
78 size += params.effects * sizeof(EffectResultState);
79 }
80
81 if (params.perf_frames > 0) {
82 auto perf_size{PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame(
83 behavior, params)};
84 size += Common::AlignUp(perf_size * (params.perf_frames + 1) + 0xC0, 0x100);
85 }
86
87 if (behavior.IsVariadicCommandBufferSizeSupported()) {
88 size += CommandGenerator::CalculateCommandBufferSize(behavior, params) + (0x40 - 1) * 2;
89 } else {
90 size += 0x18000 + (0x40 - 1) * 2;
91 }
92
93 size = Common::AlignUp(size, 0x1000);
94 return size;
95}
96
97System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
98 : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {}
99
100Result System::Initialize(const AudioRendererParameterInternal& params,
101 Kernel::KTransferMemory* transfer_memory, const u64 transfer_memory_size,
102 const u32 process_handle_, const u64 applet_resource_user_id_,
103 const s32 session_id_) {
104 if (!CheckValidRevision(params.revision)) {
105 return Service::Audio::ERR_INVALID_REVISION;
106 }
107
108 if (GetWorkBufferSize(params) > transfer_memory_size) {
109 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
110 }
111
112 if (process_handle_ == 0) {
113 return Service::Audio::ERR_INVALID_PROCESS_HANDLE;
114 }
115
116 behavior.SetUserLibRevision(params.revision);
117
118 process_handle = process_handle_;
119 applet_resource_user_id = applet_resource_user_id_;
120 session_id = session_id_;
121
122 sample_rate = params.sample_rate;
123 sample_count = params.sample_count;
124 mix_buffer_count = static_cast<s16>(params.mixes);
125 voice_channels = MaxChannels;
126 upsampler_count = params.sinks + params.sub_mixes;
127 memory_pool_count = params.effects + params.voices * MaxWaveBuffers;
128 render_device = params.rendering_device;
129 execution_mode = params.execution_mode;
130
131 core.Memory().ZeroBlock(*core.Kernel().CurrentProcess(), transfer_memory->GetSourceAddress(),
132 transfer_memory_size);
133
134 // Note: We're not actually using the transfer memory because it's a pain to code for.
135 // Allocate the memory normally instead and hope the game doesn't try to read anything back
136 workbuffer = std::make_unique<u8[]>(transfer_memory_size);
137 workbuffer_size = transfer_memory_size;
138
139 PoolMapper pool_mapper(process_handle, false);
140 pool_mapper.InitializeSystemPool(memory_pool_info, workbuffer.get(), workbuffer_size);
141
142 WorkbufferAllocator allocator({workbuffer.get(), workbuffer_size}, workbuffer_size);
143
144 samples_workbuffer =
145 allocator.Allocate<s32>((voice_channels + mix_buffer_count) * sample_count, 0x10);
146 if (samples_workbuffer.empty()) {
147 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
148 }
149
150 auto upsampler_workbuffer{allocator.Allocate<s32>(
151 (voice_channels + mix_buffer_count) * TargetSampleCount * upsampler_count, 0x10)};
152 if (upsampler_workbuffer.empty()) {
153 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
154 }
155
156 depop_buffer =
157 allocator.Allocate<s32>(Common::AlignUp(static_cast<u32>(mix_buffer_count), 0x40), 0x40);
158 if (depop_buffer.empty()) {
159 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
160 }
161
162 // invalidate samples_workbuffer DSP cache
163
164 auto voice_infos{allocator.Allocate<VoiceInfo>(params.voices, 0x10)};
165 for (auto& voice_info : voice_infos) {
166 std::construct_at<VoiceInfo>(&voice_info);
167 }
168
169 if (voice_infos.empty()) {
170 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
171 }
172
173 auto sorted_voice_infos{allocator.Allocate<VoiceInfo*>(params.voices, 0x10)};
174 if (sorted_voice_infos.empty()) {
175 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
176 }
177
178 std::memset(sorted_voice_infos.data(), 0, sorted_voice_infos.size_bytes());
179
180 auto voice_channel_resources{allocator.Allocate<VoiceChannelResource>(params.voices, 0x10)};
181 u32 i{0};
182 for (auto& voice_channel_resource : voice_channel_resources) {
183 std::construct_at<VoiceChannelResource>(&voice_channel_resource, i++);
184 }
185
186 if (voice_channel_resources.empty()) {
187 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
188 }
189
190 auto voice_cpu_states{allocator.Allocate<VoiceState>(params.voices, 0x10)};
191 if (voice_cpu_states.empty()) {
192 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
193 }
194
195 for (auto& voice_state : voice_cpu_states) {
196 voice_state = {};
197 }
198
199 auto mix_infos{allocator.Allocate<MixInfo>(params.sub_mixes + 1, 0x10)};
200
201 if (mix_infos.empty()) {
202 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
203 }
204
205 u32 effect_process_order_count{0};
206 std::span<s32> effect_process_order_buffer{};
207
208 if (params.effects > 0) {
209 effect_process_order_count = params.effects * (params.sub_mixes + 1);
210 effect_process_order_buffer = allocator.Allocate<s32>(effect_process_order_count, 0x10);
211 if (effect_process_order_buffer.empty()) {
212 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
213 }
214 }
215
216 i = 0;
217 for (auto& mix_info : mix_infos) {
218 std::construct_at<MixInfo>(
219 &mix_info, effect_process_order_buffer.subspan(i * params.effects, params.effects),
220 params.effects, this->behavior);
221 i++;
222 }
223
224 auto sorted_mix_infos{allocator.Allocate<MixInfo*>(params.sub_mixes + 1, 0x10)};
225 if (sorted_mix_infos.empty()) {
226 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
227 }
228
229 std::memset(sorted_mix_infos.data(), 0, sorted_mix_infos.size_bytes());
230
231 if (behavior.IsSplitterSupported()) {
232 u64 node_state_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)};
233 u64 edge_matrix_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)};
234
235 auto node_states_workbuffer{allocator.Allocate<u8>(node_state_size, 1)};
236 auto edge_matrix_workbuffer{allocator.Allocate<u8>(edge_matrix_size, 1)};
237
238 if (node_states_workbuffer.empty() || edge_matrix_workbuffer.size() == 0) {
239 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
240 }
241
242 mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1,
243 effect_process_order_buffer, effect_process_order_count,
244 node_states_workbuffer, node_state_size, edge_matrix_workbuffer,
245 edge_matrix_size);
246 } else {
247 mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1,
248 effect_process_order_buffer, effect_process_order_count, {}, 0, {},
249 0);
250 }
251
252 upsampler_manager = allocator.Allocate<UpsamplerManager>(1, 0x10).data();
253 if (upsampler_manager == nullptr) {
254 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
255 }
256
257 memory_pool_workbuffer = allocator.Allocate<MemoryPoolInfo>(memory_pool_count, 0x10);
258 for (auto& memory_pool : memory_pool_workbuffer) {
259 std::construct_at<MemoryPoolInfo>(&memory_pool, MemoryPoolInfo::Location::DSP);
260 }
261
262 if (memory_pool_workbuffer.empty() && memory_pool_count > 0) {
263 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
264 }
265
266 if (!splitter_context.Initialize(behavior, params, allocator)) {
267 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
268 }
269
270 std::span<EffectResultState> effect_result_states_cpu{};
271 if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) {
272 effect_result_states_cpu = allocator.Allocate<EffectResultState>(params.effects, 0x10);
273 if (effect_result_states_cpu.empty()) {
274 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
275 }
276 std::memset(effect_result_states_cpu.data(), 0, effect_result_states_cpu.size_bytes());
277 }
278
279 allocator.Align(0x40);
280
281 unk_2B0 = allocator.GetSize() - allocator.GetCurrentOffset();
282 unk_2A8 = {&workbuffer[allocator.GetCurrentOffset()], unk_2B0};
283
284 upsampler_infos = allocator.Allocate<UpsamplerInfo>(upsampler_count, 0x40);
285 for (auto& upsampler_info : upsampler_infos) {
286 std::construct_at<UpsamplerInfo>(&upsampler_info);
287 }
288
289 std::construct_at<UpsamplerManager>(upsampler_manager, upsampler_count, upsampler_infos,
290 upsampler_workbuffer);
291
292 if (upsampler_infos.empty()) {
293 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
294 }
295
296 auto effect_infos{allocator.Allocate<EffectInfoBase>(params.effects, 0x40)};
297 for (auto& effect_info : effect_infos) {
298 std::construct_at<EffectInfoBase>(&effect_info);
299 }
300
301 if (effect_infos.empty() && params.effects > 0) {
302 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
303 }
304
305 std::span<EffectResultState> effect_result_states_dsp{};
306 if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) {
307 effect_result_states_dsp = allocator.Allocate<EffectResultState>(params.effects, 0x40);
308 if (effect_result_states_dsp.empty()) {
309 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
310 }
311 std::memset(effect_result_states_dsp.data(), 0, effect_result_states_dsp.size_bytes());
312 }
313
314 effect_context.Initialize(effect_infos, params.effects, effect_result_states_cpu,
315 effect_result_states_dsp, effect_result_states_dsp.size());
316
317 auto sinks{allocator.Allocate<SinkInfoBase>(params.sinks, 0x10)};
318 for (auto& sink : sinks) {
319 std::construct_at<SinkInfoBase>(&sink);
320 }
321
322 if (sinks.empty()) {
323 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
324 }
325
326 sink_context.Initialize(sinks, params.sinks);
327
328 auto voice_dsp_states{allocator.Allocate<VoiceState>(params.voices, 0x40)};
329 if (voice_dsp_states.empty()) {
330 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
331 }
332
333 for (auto& voice_state : voice_dsp_states) {
334 voice_state = {};
335 }
336
337 voice_context.Initialize(sorted_voice_infos, voice_infos, voice_channel_resources,
338 voice_cpu_states, voice_dsp_states, params.voices);
339
340 if (params.perf_frames > 0) {
341 const auto perf_workbuffer_size{
342 PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior,
343 params) *
344 (params.perf_frames + 1) +
345 0xC};
346 performance_workbuffer = allocator.Allocate<u8>(perf_workbuffer_size, 0x40);
347 if (performance_workbuffer.empty()) {
348 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
349 }
350 std::memset(performance_workbuffer.data(), 0, performance_workbuffer.size_bytes());
351 performance_manager.Initialize(performance_workbuffer, performance_workbuffer.size_bytes(),
352 params, behavior, memory_pool_info);
353 }
354
355 render_time_limit_percent = 100;
356 drop_voice = params.voice_drop_enabled && params.execution_mode == ExecutionMode::Auto;
357
358 allocator.Align(0x40);
359 command_workbuffer_size = allocator.GetRemainingSize();
360 command_workbuffer = allocator.Allocate<u8>(command_workbuffer_size, 0x40);
361 if (command_workbuffer.empty()) {
362 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
363 }
364
365 command_buffer_size = 0;
366 reset_command_buffers = true;
367
368 // nn::audio::dsp::FlushDataCache(transferMemory, transferMemorySize);
369
370 if (behavior.IsCommandProcessingTimeEstimatorVersion5Supported()) {
371 command_processing_time_estimator =
372 std::make_unique<CommandProcessingTimeEstimatorVersion5>(sample_count,
373 mix_buffer_count);
374 } else if (behavior.IsCommandProcessingTimeEstimatorVersion4Supported()) {
375 command_processing_time_estimator =
376 std::make_unique<CommandProcessingTimeEstimatorVersion4>(sample_count,
377 mix_buffer_count);
378 } else if (behavior.IsCommandProcessingTimeEstimatorVersion3Supported()) {
379 command_processing_time_estimator =
380 std::make_unique<CommandProcessingTimeEstimatorVersion3>(sample_count,
381 mix_buffer_count);
382 } else if (behavior.IsCommandProcessingTimeEstimatorVersion2Supported()) {
383 command_processing_time_estimator =
384 std::make_unique<CommandProcessingTimeEstimatorVersion2>(sample_count,
385 mix_buffer_count);
386 } else {
387 command_processing_time_estimator =
388 std::make_unique<CommandProcessingTimeEstimatorVersion1>(sample_count,
389 mix_buffer_count);
390 }
391
392 initialized = true;
393 return ResultSuccess;
394}
395
396void System::Finalize() {
397 if (!initialized) {
398 return;
399 }
400
401 if (active) {
402 Stop();
403 }
404
405 applet_resource_user_id = 0;
406
407 PoolMapper pool_mapper(process_handle, false);
408 pool_mapper.Unmap(memory_pool_info);
409
410 if (process_handle) {
411 pool_mapper.ClearUseState(memory_pool_workbuffer, memory_pool_count);
412 for (auto& memory_pool : memory_pool_workbuffer) {
413 if (memory_pool.IsMapped()) {
414 pool_mapper.Unmap(memory_pool);
415 }
416 }
417
418 // dsp::ProcessCleanup
419 // close handle
420 }
421 initialized = false;
422}
423
424void System::Start() {
425 std::scoped_lock l{lock};
426 frames_elapsed = 0;
427 state = State::Started;
428 active = true;
429}
430
431void System::Stop() {
432 {
433 std::scoped_lock l{lock};
434 state = State::Stopped;
435 active = false;
436 }
437
438 if (execution_mode == ExecutionMode::Auto) {
439 // Should wait for the system to terminate here, but core timing (should have) already
440 // stopped, so this isn't needed. Find a way to make this definite.
441
442 // terminate_event.Wait();
443 }
444}
445
446Result System::Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) {
447 std::scoped_lock l{lock};
448
449 const auto start_time{core.CoreTiming().GetClockTicks()};
450
451 InfoUpdater info_updater(input, output, process_handle, behavior);
452
453 auto result{info_updater.UpdateBehaviorInfo(behavior)};
454 if (result.IsError()) {
455 LOG_ERROR(Service_Audio, "Failed to update BehaviorInfo!");
456 return result;
457 }
458
459 result = info_updater.UpdateMemoryPools(memory_pool_workbuffer, memory_pool_count);
460 if (result.IsError()) {
461 LOG_ERROR(Service_Audio, "Failed to update MemoryPools!");
462 return result;
463 }
464
465 result = info_updater.UpdateVoiceChannelResources(voice_context);
466 if (result.IsError()) {
467 LOG_ERROR(Service_Audio, "Failed to update VoiceChannelResources!");
468 return result;
469 }
470
471 result = info_updater.UpdateVoices(voice_context, memory_pool_workbuffer, memory_pool_count);
472 if (result.IsError()) {
473 LOG_ERROR(Service_Audio, "Failed to update Voices!");
474 return result;
475 }
476
477 result = info_updater.UpdateEffects(effect_context, active, memory_pool_workbuffer,
478 memory_pool_count);
479 if (result.IsError()) {
480 LOG_ERROR(Service_Audio, "Failed to update Effects!");
481 return result;
482 }
483
484 if (behavior.IsSplitterSupported()) {
485 result = info_updater.UpdateSplitterInfo(splitter_context);
486 if (result.IsError()) {
487 LOG_ERROR(Service_Audio, "Failed to update SplitterInfo!");
488 return result;
489 }
490 }
491
492 result =
493 info_updater.UpdateMixes(mix_context, mix_buffer_count, effect_context, splitter_context);
494 if (result.IsError()) {
495 LOG_ERROR(Service_Audio, "Failed to update Mixes!");
496 return result;
497 }
498
499 result = info_updater.UpdateSinks(sink_context, memory_pool_workbuffer, memory_pool_count);
500 if (result.IsError()) {
501 LOG_ERROR(Service_Audio, "Failed to update Sinks!");
502 return result;
503 }
504
505 PerformanceManager* perf_manager{nullptr};
506 if (performance_manager.IsInitialized()) {
507 perf_manager = &performance_manager;
508 }
509
510 result =
511 info_updater.UpdatePerformanceBuffer(performance, performance.size_bytes(), perf_manager);
512 if (result.IsError()) {
513 LOG_ERROR(Service_Audio, "Failed to update PerformanceBuffer!");
514 return result;
515 }
516
517 result = info_updater.UpdateErrorInfo(behavior);
518 if (result.IsError()) {
519 LOG_ERROR(Service_Audio, "Failed to update ErrorInfo!");
520 return result;
521 }
522
523 if (behavior.IsElapsedFrameCountSupported()) {
524 result = info_updater.UpdateRendererInfo(frames_elapsed);
525 if (result.IsError()) {
526 LOG_ERROR(Service_Audio, "Failed to update RendererInfo!");
527 return result;
528 }
529 }
530
531 result = info_updater.CheckConsumedSize();
532 if (result.IsError()) {
533 LOG_ERROR(Service_Audio, "Invalid consume size!");
534 return result;
535 }
536
537 adsp_rendered_event->GetWritableEvent().Clear();
538 num_times_updated++;
539
540 const auto end_time{core.CoreTiming().GetClockTicks()};
541 ticks_spent_updating += end_time - start_time;
542
543 return ResultSuccess;
544}
545
546u32 System::GetRenderingTimeLimit() const {
547 return render_time_limit_percent;
548}
549
550void System::SetRenderingTimeLimit(const u32 limit) {
551 render_time_limit_percent = limit;
552}
553
554u32 System::GetSessionId() const {
555 return session_id;
556}
557
558u32 System::GetSampleRate() const {
559 return sample_rate;
560}
561
562u32 System::GetSampleCount() const {
563 return sample_count;
564}
565
566u32 System::GetMixBufferCount() const {
567 return mix_buffer_count;
568}
569
570ExecutionMode System::GetExecutionMode() const {
571 return execution_mode;
572}
573
574u32 System::GetRenderingDevice() const {
575 return render_device;
576}
577
578bool System::IsActive() const {
579 return active;
580}
581
582void System::SendCommandToDsp() {
583 std::scoped_lock l{lock};
584
585 if (initialized) {
586 if (active) {
587 terminate_event.Reset();
588 const auto remaining_command_count{adsp.GetRemainCommandCount(session_id)};
589 u64 command_size{0};
590
591 if (remaining_command_count) {
592 adsp_behind = true;
593 command_size = command_buffer_size;
594 } else {
595 command_size = GenerateCommand(command_workbuffer, command_workbuffer_size);
596 }
597
598 auto translated_addr{
599 memory_pool_info.Translate(CpuAddr(command_workbuffer.data()), command_size)};
600
601 auto time_limit_percent{70.0f};
602 if (behavior.IsAudioRendererProcessingTimeLimit80PercentSupported()) {
603 time_limit_percent = 80.0f;
604 } else if (behavior.IsAudioRendererProcessingTimeLimit75PercentSupported()) {
605 time_limit_percent = 75.0f;
606 } else {
607 // result ignored and 70 is used anyway
608 behavior.IsAudioRendererProcessingTimeLimit70PercentSupported();
609 time_limit_percent = 70.0f;
610 }
611
612 ADSP::CommandBuffer command_buffer{
613 .buffer{translated_addr},
614 .size{command_size},
615 .time_limit{
616 static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
617 (static_cast<f32>(render_time_limit_percent) / 100.0f))},
618 .remaining_command_count{remaining_command_count},
619 .reset_buffers{reset_command_buffers},
620 .applet_resource_user_id{applet_resource_user_id},
621 .render_time_taken{adsp.GetRenderTimeTaken(session_id)},
622 };
623
624 adsp.SendCommandBuffer(session_id, command_buffer);
625 reset_command_buffers = false;
626 command_buffer_size = command_size;
627 if (remaining_command_count == 0) {
628 adsp_rendered_event->GetWritableEvent().Signal();
629 }
630 } else {
631 adsp.ClearRemainCount(session_id);
632 terminate_event.Set();
633 }
634 }
635}
636
637u64 System::GenerateCommand(std::span<u8> in_command_buffer,
638 [[maybe_unused]] const u64 command_buffer_size_) {
639 PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count);
640 const auto start_time{core.CoreTiming().GetClockTicks()};
641
642 auto command_list_header{reinterpret_cast<CommandListHeader*>(in_command_buffer.data())};
643
644 command_list_header->buffer_count = static_cast<s16>(voice_channels + mix_buffer_count);
645 command_list_header->sample_count = sample_count;
646 command_list_header->sample_rate = sample_rate;
647 command_list_header->samples_buffer = samples_workbuffer;
648
649 const auto performance_initialized{performance_manager.IsInitialized()};
650 if (performance_initialized) {
651 performance_manager.TapFrame(adsp_behind, num_voices_dropped, render_start_tick);
652 adsp_behind = false;
653 num_voices_dropped = 0;
654 render_start_tick = 0;
655 }
656
657 s8 channel_count{2};
658 if (execution_mode == ExecutionMode::Auto) {
659 const auto& sink{core.AudioCore().GetOutputSink()};
660 channel_count = static_cast<s8>(sink.GetDeviceChannels());
661 }
662
663 AudioRendererSystemContext render_context{
664 .session_id{session_id},
665 .channels{channel_count},
666 .mix_buffer_count{mix_buffer_count},
667 .behavior{&behavior},
668 .depop_buffer{depop_buffer},
669 .upsampler_manager{upsampler_manager},
670 .memory_pool_info{&memory_pool_info},
671 };
672
673 CommandBuffer command_buffer{
674 .command_list{in_command_buffer},
675 .sample_count{sample_count},
676 .sample_rate{sample_rate},
677 .size{sizeof(CommandListHeader)},
678 .count{0},
679 .estimated_process_time{0},
680 .memory_pool{&memory_pool_info},
681 .time_estimator{command_processing_time_estimator.get()},
682 .behavior{&behavior},
683 };
684
685 PerformanceManager* perf_manager{nullptr};
686 if (performance_initialized) {
687 perf_manager = &performance_manager;
688 }
689
690 CommandGenerator command_generator{command_buffer, *command_list_header, render_context,
691 voice_context, mix_context, effect_context,
692 sink_context, splitter_context, perf_manager};
693
694 voice_context.SortInfo();
695
696 const auto start_estimated_time{command_buffer.estimated_process_time};
697
698 command_generator.GenerateVoiceCommands();
699 command_generator.GenerateSubMixCommands();
700 command_generator.GenerateFinalMixCommands();
701 command_generator.GenerateSinkCommands();
702
703 if (drop_voice) {
704 f32 time_limit_percent{70.0f};
705 if (render_context.behavior->IsAudioRendererProcessingTimeLimit80PercentSupported()) {
706 time_limit_percent = 80.0f;
707 } else if (render_context.behavior
708 ->IsAudioRendererProcessingTimeLimit75PercentSupported()) {
709 time_limit_percent = 75.0f;
710 } else {
711 // result is ignored
712 render_context.behavior->IsAudioRendererProcessingTimeLimit70PercentSupported();
713 time_limit_percent = 70.0f;
714 }
715 const auto time_limit{static_cast<u32>(
716 static_cast<f32>(start_estimated_time - command_buffer.estimated_process_time) +
717 (((time_limit_percent / 100.0f) * 2'880'000.0) *
718 (static_cast<f32>(render_time_limit_percent) / 100.0f)))};
719 num_voices_dropped = DropVoices(command_buffer, start_estimated_time, time_limit);
720 }
721
722 command_list_header->buffer_size = command_buffer.size;
723 command_list_header->command_count = command_buffer.count;
724
725 voice_context.UpdateStateByDspShared();
726
727 if (render_context.behavior->IsEffectInfoVersion2Supported()) {
728 effect_context.UpdateStateByDspShared();
729 }
730
731 const auto end_time{core.CoreTiming().GetClockTicks()};
732 total_ticks_elapsed += end_time - start_time;
733 num_command_lists_generated++;
734 render_start_tick = adsp.GetRenderingStartTick(session_id);
735 frames_elapsed++;
736
737 return command_buffer.size;
738}
739
740u32 System::DropVoices(CommandBuffer& command_buffer, const u32 estimated_process_time,
741 const u32 time_limit) {
742 u32 i{0};
743 auto command_list{command_buffer.command_list.data() + sizeof(CommandListHeader)};
744 ICommand* cmd{};
745
746 for (; i < command_buffer.count; i++) {
747 cmd = reinterpret_cast<ICommand*>(command_list);
748 if (cmd->type != CommandId::Performance &&
749 cmd->type != CommandId::DataSourcePcmInt16Version1 &&
750 cmd->type != CommandId::DataSourcePcmInt16Version2 &&
751 cmd->type != CommandId::DataSourcePcmFloatVersion1 &&
752 cmd->type != CommandId::DataSourcePcmFloatVersion2 &&
753 cmd->type != CommandId::DataSourceAdpcmVersion1 &&
754 cmd->type != CommandId::DataSourceAdpcmVersion2) {
755 break;
756 }
757 command_list += cmd->size;
758 }
759
760 if (cmd == nullptr || command_buffer.count == 0 || i >= command_buffer.count) {
761 return 0;
762 }
763
764 auto voices_dropped{0};
765 while (i < command_buffer.count) {
766 const auto node_id{cmd->node_id};
767 const auto node_id_type{cmd->node_id >> 28};
768 const auto node_id_base{cmd->node_id & 0xFFF};
769
770 if (estimated_process_time <= time_limit) {
771 break;
772 }
773
774 if (node_id_type != 1) {
775 break;
776 }
777
778 auto& voice_info{voice_context.GetInfo(node_id_base)};
779 if (voice_info.priority == HighestVoicePriority) {
780 break;
781 }
782
783 voices_dropped++;
784 voice_info.voice_dropped = true;
785
786 if (i < command_buffer.count) {
787 while (cmd->node_id == node_id) {
788 if (cmd->type == CommandId::DepopPrepare) {
789 cmd->enabled = true;
790 } else if (cmd->type == CommandId::Performance || !cmd->enabled) {
791 cmd->enabled = false;
792 }
793 i++;
794 command_list += cmd->size;
795 cmd = reinterpret_cast<ICommand*>(command_list);
796 }
797 }
798 }
799 return voices_dropped;
800}
801
802} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <mutex>
8#include <span>
9
10#include "audio_core/renderer/behavior/behavior_info.h"
11#include "audio_core/renderer/command/command_processing_time_estimator.h"
12#include "audio_core/renderer/effect/effect_context.h"
13#include "audio_core/renderer/memory/memory_pool_info.h"
14#include "audio_core/renderer/mix/mix_context.h"
15#include "audio_core/renderer/performance/performance_manager.h"
16#include "audio_core/renderer/sink/sink_context.h"
17#include "audio_core/renderer/splitter/splitter_context.h"
18#include "audio_core/renderer/upsampler/upsampler_manager.h"
19#include "audio_core/renderer/voice/voice_context.h"
20#include "common/thread.h"
21#include "core/hle/service/audio/errors.h"
22
23namespace Core {
24namespace Memory {
25class Memory;
26}
27class System;
28} // namespace Core
29
30namespace Kernel {
31class KEvent;
32class KTransferMemory;
33} // namespace Kernel
34
35namespace AudioCore {
36struct AudioRendererParameterInternal;
37
38namespace AudioRenderer {
39class CommandBuffer;
40namespace ADSP {
41class ADSP;
42}
43
44/**
45 * Audio Renderer System, the main worker for audio rendering.
46 */
47class System {
48 enum class State {
49 Started = 0,
50 Stopped = 2,
51 };
52
53public:
54 explicit System(Core::System& core, Kernel::KEvent* adsp_rendered_event);
55
56 /**
57 * Calculate the total size required for all audio render workbuffers.
58 *
59 * @param params - Input parameters with the numbers of voices/mixes/sinks/etc.
60 * @return Size (in bytes) required for the audio renderer.
61 */
62 static u64 GetWorkBufferSize(const AudioRendererParameterInternal& params);
63
64 /**
65 * Initialize the renderer system.
66 * Allocates workbuffers and initializes everything to a default state, ready to receive a
67 * RequestUpdate.
68 *
69 * @param params - Input parameters to initialize the system with.
70 * @param transfer_memory - Game-supplied memory for all workbuffers. Unused.
71 * @param transfer_memory_size - Size of the transfer memory. Unused.
72 * @param process_handle - Process handle, also used for memory. Unused.
73 * @param applet_resource_user_id - Applet id for this renderer. Unused.
74 * @param session_id - Session id of this renderer.
75 * @return Result code.
76 */
77 Result Initialize(const AudioRendererParameterInternal& params,
78 Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
79 u32 process_handle, u64 applet_resource_user_id, s32 session_id);
80
81 /**
82 * Finalize the system.
83 */
84 void Finalize();
85
86 /**
87 * Start the system.
88 */
89 void Start();
90
91 /**
92 * Stop the system.
93 */
94 void Stop();
95
96 /**
97 * Update the system.
98 *
99 * @param input - Inout buffer containing the update data.
100 * @param performance - Optional buffer for writing back performance metrics.
101 * @param output - Output information from rendering.
102 * @return Result code.
103 */
104 Result Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output);
105
106 /**
107 * Get the time limit (percent) for rendering
108 *
109 * @return Time limit as a percent.
110 */
111 u32 GetRenderingTimeLimit() const;
112
113 /**
114 * Set the time limit (percent) for rendering
115 *
116 * @param limit - New time limit.
117 */
118 void SetRenderingTimeLimit(u32 limit);
119
120 /**
121 * Get the session id for this system.
122 *
123 * @return Session id of this system.
124 */
125 u32 GetSessionId() const;
126
127 /**
128 * Get the sample rate of this system.
129 *
130 * @return Sample rate of this system.
131 */
132 u32 GetSampleRate() const;
133
134 /**
135 * Get the sample count of this system.
136 *
137 * @return Sample count of this system.
138 */
139 u32 GetSampleCount() const;
140
141 /**
142 * Get the number of mix buffers for this system.
143 *
144 * @return Number of mix buffers in the system.
145 */
146 u32 GetMixBufferCount() const;
147
148 /**
149 * Get the execution mode of this system.
150 * Note: Only Auto is implemented.
151 *
152 * @return Execution mode for this system.
153 */
154 ExecutionMode GetExecutionMode() const;
155
156 /**
157 * Get the rendering deivce for this system.
158 * This is unused.
159 *
160 * @return Rendering device for this system.
161 */
162 u32 GetRenderingDevice() const;
163
164 /**
165 * Check if this system is currently active.
166 *
167 * @return True if active, otherwise false.
168 */
169 bool IsActive() const;
170
171 /**
172 * Prepare and generate a list of commands for the AudioRenderer based on current state,
173 * signalling the buffer event when all processed.
174 */
175 void SendCommandToDsp();
176
177 /**
178 * Generate a list of commands for the AudioRenderer based on current state.
179 *
180 * @param command_buffer - Buffer for commands to be written to.
181 * @param command_buffer_size - Size of the command_buffer.
182 *
183 * @return Number of bytes written.
184 */
185 u64 GenerateCommand(std::span<u8> command_buffer, u64 command_buffer_size);
186
187 /**
188 * Try to drop some voices if the AudioRenderer fell behind.
189 *
190 * @param command_buffer - Command buffer to drop voices from.
191 * @param estimated_process_time - Current estimated processing time of all commands.
192 * @param time_limit - Time limit for rendering, voices are dropped if estimated
193 * exceeds this.
194 *
195 * @return Number of voices dropped.
196 */
197 u32 DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time, u32 time_limit);
198
199private:
200 /// Core system
201 Core::System& core;
202 /// Reference to the ADSP for communication
203 ADSP::ADSP& adsp;
204 /// Is this system initialized?
205 bool initialized{};
206 /// Is this system currently active?
207 std::atomic<bool> active{};
208 /// State of the system
209 State state{State::Stopped};
210 /// Sample rate for the system
211 u32 sample_rate{};
212 /// Sample count of the system
213 u32 sample_count{};
214 /// Number of mix buffers in use by the system
215 s16 mix_buffer_count{};
216 /// Workbuffer for mix buffers, used by the AudioRenderer
217 std::span<s32> samples_workbuffer{};
218 /// Depop samples for depopping commands
219 std::span<s32> depop_buffer{};
220 /// Number of memory pools in the buffer
221 u32 memory_pool_count{};
222 /// Workbuffer for memory pools
223 std::span<MemoryPoolInfo> memory_pool_workbuffer{};
224 /// System memory pool info
225 MemoryPoolInfo memory_pool_info{};
226 /// Workbuffer that commands will be generated into
227 std::span<u8> command_workbuffer{};
228 /// Size of command workbuffer
229 u64 command_workbuffer_size{};
230 /// Numebr of commands in the workbuffer
231 u64 command_buffer_size{};
232 /// Manager for upsamplers
233 UpsamplerManager* upsampler_manager{};
234 /// Upsampler workbuffer
235 std::span<UpsamplerInfo> upsampler_infos{};
236 /// Number of upsamplers in the workbuffer
237 u32 upsampler_count{};
238 /// Holds and controls all voices
239 VoiceContext voice_context{};
240 /// Holds and controls all mixes
241 MixContext mix_context{};
242 /// Holds and controls all effects
243 EffectContext effect_context{};
244 /// Holds and controls all sinks
245 SinkContext sink_context{};
246 /// Holds and controls all splitters
247 SplitterContext splitter_context{};
248 /// Estimates the time taken for each command
249 std::unique_ptr<ICommandProcessingTimeEstimator> command_processing_time_estimator{};
250 /// Session id of this system
251 s32 session_id{};
252 /// Number of channels in use by voices
253 s32 voice_channels{};
254 /// Event to be called when the AudioRenderer processes a command list
255 Kernel::KEvent* adsp_rendered_event{};
256 /// Event signalled on system terminate
257 Common::Event terminate_event{};
258 /// Does what locks do
259 std::mutex lock{};
260 /// Handle for the process for this system, unused
261 u32 process_handle{};
262 /// Applet resource id for this system, unused
263 u64 applet_resource_user_id{};
264 /// Controls performance input and output
265 PerformanceManager performance_manager{};
266 /// Workbuffer for performance metrics
267 std::span<u8> performance_workbuffer{};
268 /// Main workbuffer, from which all other workbuffers here allocate into
269 std::unique_ptr<u8[]> workbuffer{};
270 /// Size of the main workbuffer
271 u64 workbuffer_size{};
272 /// Unknown buffer/marker
273 std::span<u8> unk_2A8{};
274 /// Size of the above unknown buffer/marker
275 u64 unk_2B0{};
276 /// Rendering time limit (percent)
277 u32 render_time_limit_percent{};
278 /// Should any voices be dropped?
279 bool drop_voice{};
280 /// Should the backend stream have its buffers flushed?
281 bool reset_command_buffers{};
282 /// Execution mode of this system, only Auto is supported
283 ExecutionMode execution_mode{ExecutionMode::Auto};
284 /// Render device, unused
285 u32 render_device{};
286 /// Behaviour to check which features are supported by the user revision
287 BehaviorInfo behavior{};
288 /// Total ticks the audio system has been running
289 u64 total_ticks_elapsed{};
290 /// Ticks the system has spent in updates
291 u64 ticks_spent_updating{};
292 /// Number of times a command list was generated
293 u64 num_command_lists_generated{};
294 /// Number of times the system has updated
295 u64 num_times_updated{};
296 /// Number of frames generated, written back to the game
297 std::atomic<u64> frames_elapsed{};
298 /// Is the AudioRenderer running too slow?
299 bool adsp_behind{};
300 /// Number of voices dropped
301 u32 num_voices_dropped{};
302 /// Tick that rendering started
303 u64 render_start_tick{};
304};
305
306} // namespace AudioRenderer
307} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <chrono>
5
6#include "audio_core/audio_core.h"
7#include "audio_core/renderer/adsp/adsp.h"
8#include "audio_core/renderer/system_manager.h"
9#include "common/microprofile.h"
10#include "common/thread.h"
11#include "core/core.h"
12#include "core/core_timing.h"
13
14MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
15 MP_RGB(60, 19, 97));
16
17namespace AudioCore::AudioRenderer {
18constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL};
19constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL};
20
21SystemManager::SystemManager(Core::System& core_)
22 : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
23 thread_event{Core::Timing::CreateEvent(
24 "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
25 return ThreadFunc2(time);
26 })} {
27 core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); });
28}
29
30SystemManager::~SystemManager() {
31 Stop();
32}
33
34bool SystemManager::InitializeUnsafe() {
35 if (!active) {
36 if (adsp.Start()) {
37 active = true;
38 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
39 core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0),
40 BaseRenderTime - RenderTimeOffset, thread_event);
41 }
42 }
43
44 return adsp.GetState() == ADSP::State::Started;
45}
46
47void SystemManager::Stop() {
48 if (!active) {
49 return;
50 }
51 core.CoreTiming().UnscheduleEvent(thread_event, {});
52 active = false;
53 update.store(true);
54 update.notify_all();
55 thread.join();
56 adsp.Stop();
57}
58
59bool SystemManager::Add(System& system_) {
60 std::scoped_lock l2{mutex2};
61
62 if (systems.size() + 1 > MaxRendererSessions) {
63 LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!");
64 return false;
65 }
66
67 {
68 std::scoped_lock l{mutex1};
69 if (systems.empty()) {
70 if (!InitializeUnsafe()) {
71 LOG_ERROR(Service_Audio, "Failed to start the AudioRenderer SystemManager");
72 return false;
73 }
74 }
75 }
76
77 systems.push_back(&system_);
78 return true;
79}
80
81bool SystemManager::Remove(System& system_) {
82 std::scoped_lock l2{mutex2};
83
84 {
85 std::scoped_lock l{mutex1};
86 if (systems.remove(&system_) == 0) {
87 LOG_ERROR(Service_Audio,
88 "Failed to remove a render system, it was not found in the list!");
89 return false;
90 }
91 }
92
93 if (systems.empty()) {
94 Stop();
95 }
96 return true;
97}
98
99void SystemManager::ThreadFunc() {
100 constexpr char name[]{"yuzu:AudioRenderSystemManager"};
101 MicroProfileOnThreadCreate(name);
102 Common::SetCurrentThreadName(name);
103 Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
104 while (active) {
105 {
106 std::scoped_lock l{mutex1};
107
108 MICROPROFILE_SCOPE(Audio_RenderSystemManager);
109
110 for (auto system : systems) {
111 system->SendCommandToDsp();
112 }
113 }
114
115 adsp.Signal();
116 adsp.Wait();
117
118 update.wait(false);
119 update.store(false);
120 }
121}
122
123std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
124 std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt};
125 const auto queue_size{core.AudioCore().GetStreamQueue()};
126 switch (state) {
127 case StreamState::Filling:
128 if (queue_size >= 5) {
129 new_schedule_time = BaseRenderTime;
130 state = StreamState::Steady;
131 }
132 break;
133 case StreamState::Steady:
134 if (queue_size <= 2) {
135 new_schedule_time = BaseRenderTime - RenderTimeOffset;
136 state = StreamState::Filling;
137 } else if (queue_size > 5) {
138 new_schedule_time = BaseRenderTime + RenderTimeOffset;
139 state = StreamState::Draining;
140 }
141 break;
142 case StreamState::Draining:
143 if (queue_size <= 5) {
144 new_schedule_time = BaseRenderTime;
145 state = StreamState::Steady;
146 }
147 break;
148 }
149
150 update.store(true);
151 update.notify_all();
152 return new_schedule_time;
153}
154
155void SystemManager::PauseCallback(bool paused) {
156 if (paused && core.IsPoweredOn() && core.IsShuttingDown()) {
157 update.store(true);
158 update.notify_all();
159 }
160}
161
162} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <list>
7#include <memory>
8#include <mutex>
9#include <optional>
10#include <thread>
11
12#include "audio_core/renderer/system.h"
13
14namespace Core {
15namespace Timing {
16struct EventType;
17}
18class System;
19} // namespace Core
20
21namespace AudioCore::AudioRenderer {
22namespace ADSP {
23class ADSP;
24class AudioRenderer_Mailbox;
25} // namespace ADSP
26
27/**
28 * Manages all audio renderers, responsible for triggering command list generation and signalling
29 * the ADSP.
30 */
31class SystemManager {
32public:
33 explicit SystemManager(Core::System& core);
34 ~SystemManager();
35
36 /**
37 * Initialize the system manager, called when any system is registered.
38 *
39 * @return True if sucessfully initialized, otherwise false.
40 */
41 bool InitializeUnsafe();
42
43 /**
44 * Stop the system manager.
45 */
46 void Stop();
47
48 /**
49 * Add an audio render system to the manager.
50 * The manager does not own the system, so do not free it without calling Remove.
51 *
52 * @param system - The system to add.
53 * @return True if succesfully added, otherwise false.
54 */
55 bool Add(System& system);
56
57 /**
58 * Remove an audio render system from the manager.
59 *
60 * @param system - The system to remove.
61 * @return True if succesfully removed, otherwise false.
62 */
63 bool Remove(System& system);
64
65private:
66 /**
67 * Main thread responsible for command generation.
68 */
69 void ThreadFunc();
70
71 /**
72 * Signalling core timing thread to run ThreadFunc.
73 */
74 std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
75
76 /**
77 * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc.
78 *
79 * @param paused - Are we pausing or resuming?
80 */
81 void PauseCallback(bool paused);
82
83 enum class StreamState {
84 Filling,
85 Steady,
86 Draining,
87 };
88
89 /// Core system
90 Core::System& core;
91 /// List of pointers to managed systems
92 std::list<System*> systems{};
93 /// Main worker thread for generating command lists
94 std::jthread thread;
95 /// Mutex for the systems
96 std::mutex mutex1{};
97 /// Mutex for adding/removing systems
98 std::mutex mutex2{};
99 /// Is the system manager thread active?
100 std::atomic<bool> active{};
101 /// Reference to the ADSP for communication
102 ADSP::ADSP& adsp;
103 /// AudioRenderer mailbox for communication
104 ADSP::AudioRenderer_Mailbox* mailbox{};
105 /// Core timing event to signal main thread
106 std::shared_ptr<Core::Timing::EventType> thread_event;
107 /// Atomic for main thread to wait on
108 std::atomic<bool> update{};
109 /// Current state of the streams
110 StreamState state{StreamState::Filling};
111};
112
113} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/upsampler/upsampler_info.h"
5
6namespace 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/upsampler/upsampler_state.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13class UpsamplerManager;
14
15/**
16 * Manages information needed to upsample a mix buffer.
17 */
18struct UpsamplerInfo {
19 /// States used by the AudioRenderer across calls.
20 std::array<UpsamplerState, MaxChannels> states{};
21 /// Pointer to the manager
22 UpsamplerManager* manager{};
23 /// Pointer to the samples to be upsampled
24 CpuAddr samples_pos{};
25 /// Target number of samples to upsample to
26 u32 sample_count{};
27 /// Number of channels to upsample
28 u32 input_count{};
29 /// Is this upsampler enabled?
30 bool enabled{};
31 /// Mix buffer indexes to be upsampled
32 std::array<s16, MaxChannels> inputs{};
33};
34
35} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/upsampler/upsampler_manager.h"
5
6namespace AudioCore::AudioRenderer {
7
8UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_,
9 std::span<s32> workbuffer_)
10 : count{count_}, upsampler_infos{infos_}, workbuffer{workbuffer_} {}
11
12UpsamplerInfo* UpsamplerManager::Allocate() {
13 std::scoped_lock l{lock};
14
15 if (count == 0) {
16 return nullptr;
17 }
18
19 u32 free_index{0};
20 for (auto& upsampler : upsampler_infos) {
21 if (!upsampler.enabled) {
22 break;
23 }
24 free_index++;
25 }
26
27 if (free_index >= count) {
28 return nullptr;
29 }
30
31 auto& upsampler{upsampler_infos[free_index]};
32 upsampler.manager = this;
33 upsampler.sample_count = TargetSampleCount;
34 upsampler.samples_pos = CpuAddr(&workbuffer[upsampler.sample_count * MaxChannels]);
35 upsampler.enabled = true;
36 return &upsampler;
37}
38
39void UpsamplerManager::Free(UpsamplerInfo* info) {
40 std::scoped_lock l{lock};
41 info->enabled = false;
42}
43
44} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <mutex>
7#include <span>
8
9#include "audio_core/renderer/upsampler/upsampler_info.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Manages and has utility functions for upsampler infos.
15 */
16class UpsamplerManager {
17public:
18 UpsamplerManager(u32 count, std::span<UpsamplerInfo> infos, std::span<s32> workbuffer);
19
20 /**
21 * Allocate a new UpsamplerInfo.
22 *
23 * @return The allocated upsampler, may be nullptr if alloc failed.
24 */
25 UpsamplerInfo* Allocate();
26
27 /**
28 * Free the given upsampler.
29 *
30 * @param The upsampler to be freed.
31 */
32 void Free(UpsamplerInfo* info);
33
34private:
35 /// Maximum number of upsamplers in the buffer
36 const u32 count;
37 /// Upsamplers buffer
38 std::span<UpsamplerInfo> upsampler_infos;
39 /// Workbuffer for upsampling samples
40 std::span<s32> workbuffer;
41 /// Lock for allocate/free
42 std::mutex lock{};
43};
44
45} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "common/common_types.h"
9#include "common/fixed_point.h"
10
11namespace AudioCore::AudioRenderer {
12/**
13 * Upsampling state used by the AudioRenderer across calls.
14 */
15struct UpsamplerState {
16 static constexpr u16 HistorySize = 20;
17
18 /// Source data to target data ratio. E.g 48'000/32'000 = 1.5
19 Common::FixedPoint<16, 16> ratio;
20 /// Sample history
21 std::array<Common::FixedPoint<24, 8>, HistorySize> history;
22 /// Size of the sinc coefficient window
23 u16 window_size;
24 /// Read index for the history
25 u16 history_output_index;
26 /// Write index for the history
27 u16 history_input_index;
28 /// Start offset within the history, fixed to 0
29 u16 history_start_index;
30 /// Ebd offset within the history, fixed to HistorySize
31 u16 history_end_index;
32 /// Is this state initialized?
33 bool initialized;
34 /// Index of the current sample.
35 /// E.g 16K -> 48K has a ratio of 3, so this will be 0-2.
36 /// See the Upsample command in the AudioRenderer for more information.
37 u8 sample_index;
38};
39
40} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12/**
13 * Represents one channel for mixing a voice.
14 */
15class VoiceChannelResource {
16public:
17 struct InParameter {
18 /* 0x00 */ u32 id;
19 /* 0x04 */ std::array<f32, MaxMixBuffers> mix_volumes;
20 /* 0x64 */ bool in_use;
21 /* 0x65 */ char unk65[0xB];
22 };
23 static_assert(sizeof(InParameter) == 0x70,
24 "VoiceChannelResource::InParameter has the wrong size!");
25
26 explicit VoiceChannelResource(u32 id_) : id{id_} {}
27
28 /// Current volume for each mix buffer
29 std::array<f32, MaxMixBuffers> mix_volumes{};
30 /// Previous volume for each mix buffer
31 std::array<f32, MaxMixBuffers> prev_mix_volumes{};
32 /// Id of this resource
33 const u32 id;
34 /// Is this resource in use?
35 bool in_use{};
36};
37
38} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <ranges>
5
6#include "audio_core/renderer/voice/voice_context.h"
7
8namespace AudioCore::AudioRenderer {
9
10VoiceState& VoiceContext::GetDspSharedState(const u32 index) {
11 if (index >= dsp_states.size()) {
12 LOG_ERROR(Service_Audio, "Invalid voice dsp state index {:04X}", index);
13 }
14 return dsp_states[index];
15}
16
17VoiceChannelResource& VoiceContext::GetChannelResource(const u32 index) {
18 if (index >= channel_resources.size()) {
19 LOG_ERROR(Service_Audio, "Invalid voice channel resource index {:04X}", index);
20 }
21 return channel_resources[index];
22}
23
24void VoiceContext::Initialize(std::span<VoiceInfo*> sorted_voice_infos_,
25 std::span<VoiceInfo> voice_infos_,
26 std::span<VoiceChannelResource> voice_channel_resources_,
27 std::span<VoiceState> cpu_states_, std::span<VoiceState> dsp_states_,
28 const u32 voice_count_) {
29 sorted_voice_info = sorted_voice_infos_;
30 voices = voice_infos_;
31 channel_resources = voice_channel_resources_;
32 cpu_states = cpu_states_;
33 dsp_states = dsp_states_;
34 voice_count = voice_count_;
35 active_count = 0;
36}
37
38VoiceInfo* VoiceContext::GetSortedInfo(const u32 index) {
39 if (index >= sorted_voice_info.size()) {
40 LOG_ERROR(Service_Audio, "Invalid voice sorted info index {:04X}", index);
41 }
42 return sorted_voice_info[index];
43}
44
45VoiceInfo& VoiceContext::GetInfo(const u32 index) {
46 if (index >= voices.size()) {
47 LOG_ERROR(Service_Audio, "Invalid voice info index {:04X}", index);
48 }
49 return voices[index];
50}
51
52VoiceState& VoiceContext::GetState(const u32 index) {
53 if (index >= cpu_states.size()) {
54 LOG_ERROR(Service_Audio, "Invalid voice cpu state index {:04X}", index);
55 }
56 return cpu_states[index];
57}
58
59u32 VoiceContext::GetCount() const {
60 return voice_count;
61}
62
63u32 VoiceContext::GetActiveCount() const {
64 return active_count;
65}
66
67void VoiceContext::SetActiveCount(const u32 active_count_) {
68 active_count = active_count_;
69}
70
71void VoiceContext::SortInfo() {
72 for (u32 i = 0; i < voice_count; i++) {
73 sorted_voice_info[i] = &voices[i];
74 }
75
76 std::ranges::sort(sorted_voice_info, [](const VoiceInfo* a, const VoiceInfo* b) {
77 return a->priority != b->priority ? a->priority < b->priority
78 : a->sort_order < b->sort_order;
79 });
80}
81
82void VoiceContext::UpdateStateByDspShared() {
83 std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState));
84}
85
86} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/voice/voice_channel_resource.h"
9#include "audio_core/renderer/voice/voice_info.h"
10#include "audio_core/renderer/voice/voice_state.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14/**
15 * Contains all voices, with utility functions for managing them.
16 */
17class VoiceContext {
18public:
19 /**
20 * Get the AudioRenderer state for a given index
21 *
22 * @param index - State index to get.
23 * @return The requested voice state.
24 */
25 VoiceState& GetDspSharedState(u32 index);
26
27 /**
28 * Get the channel resource for a given index
29 *
30 * @param index - Resource index to get.
31 * @return The requested voice resource.
32 */
33 VoiceChannelResource& GetChannelResource(u32 index);
34
35 /**
36 * Initialize the voice context.
37 *
38 * @param sorted_voice_infos - Workbuffer for the sorted voices.
39 * @param voice_infos - Workbuffer for the voices.
40 * @param voice_channel_resources - Workbuffer for the voice channel resources.
41 * @param cpu_states - Workbuffer for the host-side voice states.
42 * @param dsp_states - Workbuffer for the AudioRenderer-side voice states.
43 * @param voice_count - The number of voices in each workbuffer.
44 */
45 void Initialize(std::span<VoiceInfo*> sorted_voice_infos, std::span<VoiceInfo> voice_infos,
46 std::span<VoiceChannelResource> voice_channel_resources,
47 std::span<VoiceState> cpu_states, std::span<VoiceState> dsp_states,
48 u32 voice_count);
49
50 /**
51 * Get a sorted voice with the given index.
52 *
53 * @param index - The sorted voice index to get.
54 * @return The sorted voice.
55 */
56 VoiceInfo* GetSortedInfo(u32 index);
57
58 /**
59 * Get a voice with the given index.
60 *
61 * @param index - The voice index to get.
62 * @return The voice.
63 */
64 VoiceInfo& GetInfo(u32 index);
65
66 /**
67 * Get a host voice state with the given index.
68 *
69 * @param index - The host voice state index to get.
70 * @return The voice state.
71 */
72 VoiceState& GetState(u32 index);
73
74 /**
75 * Get the maximum number of voices.
76 * Not all voices in the buffers may be in use, see GetActiveCount.
77 *
78 * @return The maximum number of voices.
79 */
80 u32 GetCount() const;
81
82 /**
83 * Get the number of active voices.
84 * Can be less than or equal to the maximum number of voices.
85 *
86 * @return The number of active voices.
87 */
88 u32 GetActiveCount() const;
89
90 /**
91 * Set the number of active voices.
92 * Can be less than or equal to the maximum number of voices.
93 *
94 * @param active_count - The new number of active voices.
95 */
96 void SetActiveCount(u32 active_count);
97
98 /**
99 * Sort all voices. Results are available via GetSortedInfo.
100 * Voices are sorted descendingly, according to priority, and then sort order.
101 */
102 void SortInfo();
103
104 /**
105 * Update all voice states, copying AudioRenderer-side states to host-side states.
106 */
107 void UpdateStateByDspShared();
108
109private:
110 /// Sorted voices
111 std::span<VoiceInfo*> sorted_voice_info{};
112 /// Voices
113 std::span<VoiceInfo> voices{};
114 /// Channel resources
115 std::span<VoiceChannelResource> channel_resources{};
116 /// Host-side voice states
117 std::span<VoiceState> cpu_states{};
118 /// AudioRenderer-side voice states
119 std::span<VoiceState> dsp_states{};
120 /// Maximum number of voices
121 u32 voice_count{};
122 /// Number of active voices
123 u32 active_count{};
124};
125
126} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/memory/pool_mapper.h"
5#include "audio_core/renderer/voice/voice_context.h"
6#include "audio_core/renderer/voice/voice_info.h"
7#include "audio_core/renderer/voice/voice_state.h"
8
9namespace AudioCore::AudioRenderer {
10
11VoiceInfo::VoiceInfo() {
12 Initialize();
13}
14
15void VoiceInfo::Initialize() {
16 in_use = false;
17 is_new = false;
18 id = 0;
19 node_id = 0;
20 current_play_state = ServerPlayState::Stopped;
21 src_quality = SrcQuality::Medium;
22 priority = LowestVoicePriority;
23 sample_format = SampleFormat::Invalid;
24 sample_rate = 0;
25 channel_count = 0;
26 wave_buffer_count = 0;
27 wave_buffer_index = 0;
28 pitch = 0.0f;
29 volume = 0.0f;
30 prev_volume = 0.0f;
31 mix_id = UnusedMixId;
32 splitter_id = UnusedSplitterId;
33 biquads = {};
34 biquad_initialized = {};
35 voice_dropped = false;
36 data_unmapped = false;
37 buffer_unmapped = false;
38 flush_buffer_count = 0;
39
40 data_address.Setup(0, 0);
41 for (auto& wavebuffer : wavebuffers) {
42 wavebuffer.Initialize();
43 }
44}
45
46bool VoiceInfo::ShouldUpdateParameters(const InParameter& params) const {
47 return data_address.GetCpuAddr() != params.src_data_address ||
48 data_address.GetSize() != params.src_data_size || data_unmapped;
49}
50
51void VoiceInfo::UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
52 const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
53 in_use = params.in_use;
54 id = params.id;
55 node_id = params.node_id;
56 UpdatePlayState(params.play_state);
57 UpdateSrcQuality(params.src_quality);
58 priority = params.priority;
59 sort_order = params.sort_order;
60 sample_rate = params.sample_rate;
61 sample_format = params.sample_format;
62 channel_count = static_cast<s8>(params.channel_count);
63 pitch = params.pitch;
64 volume = params.volume;
65 biquads = params.biquads;
66 wave_buffer_count = params.wave_buffer_count;
67 wave_buffer_index = params.wave_buffer_index;
68
69 if (behavior.IsFlushVoiceWaveBuffersSupported()) {
70 flush_buffer_count += params.flush_buffer_count;
71 }
72
73 mix_id = params.mix_id;
74
75 if (behavior.IsSplitterSupported()) {
76 splitter_id = params.splitter_id;
77 } else {
78 splitter_id = UnusedSplitterId;
79 }
80
81 channel_resource_ids = params.channel_resource_ids;
82
83 flags &= u16(~0b11);
84 if (behavior.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
85 flags |= u16(params.flags.IsVoicePlayedSampleCountResetAtLoopPointSupported);
86 }
87
88 if (behavior.IsVoicePitchAndSrcSkippedSupported()) {
89 flags |= u16(params.flags.IsVoicePitchAndSrcSkippedSupported);
90 }
91
92 if (params.clear_voice_drop) {
93 voice_dropped = false;
94 }
95
96 if (ShouldUpdateParameters(params)) {
97 data_unmapped = !pool_mapper.TryAttachBuffer(error_info, data_address,
98 params.src_data_address, params.src_data_size);
99 } else {
100 error_info.error_code = ResultSuccess;
101 error_info.address = CpuAddr(0);
102 }
103}
104
105void VoiceInfo::UpdatePlayState(const PlayState state) {
106 last_play_state = current_play_state;
107
108 switch (state) {
109 case PlayState::Started:
110 current_play_state = ServerPlayState::Started;
111 break;
112 case PlayState::Stopped:
113 if (current_play_state != ServerPlayState::Stopped) {
114 current_play_state = ServerPlayState::RequestStop;
115 }
116 break;
117 case PlayState::Paused:
118 current_play_state = ServerPlayState::Paused;
119 break;
120 default:
121 LOG_ERROR(Service_Audio, "Invalid input play state {}", static_cast<u32>(state));
122 break;
123 }
124}
125
126void VoiceInfo::UpdateSrcQuality(const SrcQuality quality) {
127 switch (quality) {
128 case SrcQuality::Medium:
129 src_quality = quality;
130 break;
131 case SrcQuality::High:
132 src_quality = quality;
133 break;
134 case SrcQuality::Low:
135 src_quality = quality;
136 break;
137 default:
138 LOG_ERROR(Service_Audio, "Invalid input src quality {}", static_cast<u32>(quality));
139 break;
140 }
141}
142
143void VoiceInfo::UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
144 [[maybe_unused]] u32 error_count, const InParameter& params,
145 std::span<VoiceState*> voice_states,
146 const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
147 if (params.is_new) {
148 for (size_t i = 0; i < wavebuffers.size(); i++) {
149 wavebuffers[i].Initialize();
150 }
151
152 for (s8 channel = 0; channel < static_cast<s8>(params.channel_count); channel++) {
153 voice_states[channel]->wave_buffer_valid.fill(false);
154 }
155 }
156
157 for (u32 i = 0; i < MaxWaveBuffers; i++) {
158 UpdateWaveBuffer(error_infos[i], wavebuffers[i], params.wave_buffer_internal[i],
159 params.sample_format, voice_states[0]->wave_buffer_valid[i], pool_mapper,
160 behavior);
161 }
162}
163
164void VoiceInfo::UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info,
165 WaveBuffer& wave_buffer,
166 const WaveBufferInternal& wave_buffer_internal,
167 const SampleFormat sample_format_, const bool valid,
168 const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
169 if (!valid && wave_buffer.sent_to_DSP && wave_buffer.buffer_address.GetCpuAddr() != 0) {
170 pool_mapper.ForceUnmapPointer(wave_buffer.buffer_address);
171 wave_buffer.buffer_address.Setup(0, 0);
172 }
173
174 if (!ShouldUpdateWaveBuffer(wave_buffer_internal)) {
175 return;
176 }
177
178 switch (sample_format_) {
179 case SampleFormat::PcmInt16: {
180 constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmInt16)};
181 if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
182 wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
183 LOG_ERROR(Service_Audio, "Invalid PCM16 start/end wavebuffer sizes!");
184 error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
185 error_info[0].address = wave_buffer_internal.address;
186 return;
187 }
188 } break;
189
190 case SampleFormat::PcmFloat: {
191 constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmFloat)};
192 if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
193 wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
194 LOG_ERROR(Service_Audio, "Invalid PCMFloat start/end wavebuffer sizes!");
195 error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
196 error_info[0].address = wave_buffer_internal.address;
197 return;
198 }
199 } break;
200
201 case SampleFormat::Adpcm: {
202 const auto start_frame{wave_buffer_internal.start_offset / 14};
203 auto start_extra{wave_buffer_internal.start_offset % 14 == 0
204 ? 0
205 : (wave_buffer_internal.start_offset % 14) / 2 + 1 +
206 ((wave_buffer_internal.start_offset % 14) % 2)};
207 const auto start{start_frame * 8 + start_extra};
208
209 const auto end_frame{wave_buffer_internal.end_offset / 14};
210 const auto end_extra{wave_buffer_internal.end_offset % 14 == 0
211 ? 0
212 : (wave_buffer_internal.end_offset % 14) / 2 + 1 +
213 ((wave_buffer_internal.end_offset % 14) % 2)};
214 const auto end{end_frame * 8 + end_extra};
215
216 if (start > static_cast<s64>(wave_buffer_internal.size) ||
217 end > static_cast<s64>(wave_buffer_internal.size)) {
218 LOG_ERROR(Service_Audio, "Invalid ADPCM start/end wavebuffer sizes!");
219 error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
220 error_info[0].address = wave_buffer_internal.address;
221 return;
222 }
223 } break;
224
225 default:
226 break;
227 }
228
229 if (wave_buffer_internal.start_offset < 0 || wave_buffer_internal.end_offset < 0) {
230 LOG_ERROR(Service_Audio, "Invalid input start/end wavebuffer sizes!");
231 error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
232 error_info[0].address = wave_buffer_internal.address;
233 return;
234 }
235
236 wave_buffer.start_offset = wave_buffer_internal.start_offset;
237 wave_buffer.end_offset = wave_buffer_internal.end_offset;
238 wave_buffer.loop = wave_buffer_internal.loop;
239 wave_buffer.stream_ended = wave_buffer_internal.stream_ended;
240 wave_buffer.sent_to_DSP = false;
241 wave_buffer.loop_start_offset = wave_buffer_internal.loop_start;
242 wave_buffer.loop_end_offset = wave_buffer_internal.loop_end;
243 wave_buffer.loop_count = wave_buffer_internal.loop_count;
244
245 buffer_unmapped =
246 !pool_mapper.TryAttachBuffer(error_info[0], wave_buffer.buffer_address,
247 wave_buffer_internal.address, wave_buffer_internal.size);
248
249 if (sample_format_ == SampleFormat::Adpcm && behavior.IsAdpcmLoopContextBugFixed() &&
250 wave_buffer_internal.context_address != 0) {
251 buffer_unmapped = !pool_mapper.TryAttachBuffer(error_info[1], wave_buffer.context_address,
252 wave_buffer_internal.context_address,
253 wave_buffer_internal.context_size) ||
254 data_unmapped;
255 } else {
256 wave_buffer.context_address.Setup(0, 0);
257 }
258}
259
260bool VoiceInfo::ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const {
261 return !wave_buffer_internal.sent_to_DSP || buffer_unmapped;
262}
263
264void VoiceInfo::WriteOutStatus(OutStatus& out_status, const InParameter& params,
265 std::span<VoiceState*> voice_states) {
266 if (params.is_new) {
267 is_new = true;
268 }
269
270 if (params.is_new || is_new) {
271 out_status.played_sample_count = 0;
272 out_status.wave_buffers_consumed = 0;
273 out_status.voice_dropped = false;
274 } else {
275 out_status.played_sample_count = voice_states[0]->played_sample_count;
276 out_status.wave_buffers_consumed = voice_states[0]->wave_buffers_consumed;
277 out_status.voice_dropped = voice_dropped;
278 }
279}
280
281bool VoiceInfo::ShouldSkip() const {
282 return !in_use || wave_buffer_count == 0 || data_unmapped || buffer_unmapped || voice_dropped;
283}
284
285bool VoiceInfo::HasAnyConnection() const {
286 return mix_id != UnusedMixId || splitter_id != UnusedSplitterId;
287}
288
289void VoiceInfo::FlushWaveBuffers(const u32 flush_count, std::span<VoiceState*> voice_states,
290 const s8 channel_count_) {
291 auto wave_index{wave_buffer_index};
292
293 for (size_t i = 0; i < flush_count; i++) {
294 wavebuffers[wave_index].sent_to_DSP = true;
295
296 for (s8 j = 0; j < channel_count_; j++) {
297 auto voice_state{voice_states[j]};
298 if (voice_state->wave_buffer_index == wave_index) {
299 voice_state->wave_buffer_index =
300 (voice_state->wave_buffer_index + 1) % MaxWaveBuffers;
301 voice_state->wave_buffers_consumed++;
302 }
303 voice_state->wave_buffer_valid[wave_index] = false;
304 }
305
306 wave_index = (wave_index + 1) % MaxWaveBuffers;
307 }
308}
309
310bool VoiceInfo::UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states) {
311 if (flush_buffer_count > 0) {
312 FlushWaveBuffers(flush_buffer_count, voice_states, channel_count);
313 flush_buffer_count = 0;
314 }
315
316 switch (current_play_state) {
317 case ServerPlayState::Started:
318 for (u32 i = 0; i < MaxWaveBuffers; i++) {
319 if (!wavebuffers[i].sent_to_DSP) {
320 for (s8 channel = 0; channel < channel_count; channel++) {
321 voice_states[channel]->wave_buffer_valid[i] = true;
322 }
323 wavebuffers[i].sent_to_DSP = true;
324 }
325 }
326
327 was_playing = false;
328
329 for (u32 i = 0; i < MaxWaveBuffers; i++) {
330 if (voice_states[0]->wave_buffer_valid[i]) {
331 return true;
332 }
333 }
334 break;
335
336 case ServerPlayState::Stopped:
337 case ServerPlayState::Paused:
338 for (auto& wavebuffer : wavebuffers) {
339 if (!wavebuffer.sent_to_DSP) {
340 wavebuffer.buffer_address.GetReference(true);
341 wavebuffer.context_address.GetReference(true);
342 }
343 }
344
345 if (sample_format == SampleFormat::Adpcm && data_address.GetCpuAddr() != 0) {
346 data_address.GetReference(true);
347 }
348
349 was_playing = last_play_state == ServerPlayState::Started;
350 break;
351
352 case ServerPlayState::RequestStop:
353 for (u32 i = 0; i < MaxWaveBuffers; i++) {
354 wavebuffers[i].sent_to_DSP = true;
355
356 for (s8 channel = 0; channel < channel_count; channel++) {
357 if (voice_states[channel]->wave_buffer_valid[i]) {
358 voice_states[channel]->wave_buffer_index =
359 (voice_states[channel]->wave_buffer_index + 1) % MaxWaveBuffers;
360 voice_states[channel]->wave_buffers_consumed++;
361 }
362 voice_states[channel]->wave_buffer_valid[i] = false;
363 }
364 }
365
366 for (s8 channel = 0; channel < channel_count; channel++) {
367 voice_states[channel]->offset = 0;
368 voice_states[channel]->played_sample_count = 0;
369 voice_states[channel]->adpcm_context = {};
370 voice_states[channel]->sample_history.fill(0);
371 voice_states[channel]->fraction = 0;
372 }
373
374 current_play_state = ServerPlayState::Stopped;
375 was_playing = last_play_state == ServerPlayState::Started;
376 break;
377 }
378
379 return was_playing;
380}
381
382bool VoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
383 std::array<VoiceState*, MaxChannels> voice_states{};
384
385 if (is_new) {
386 ResetResources(voice_context);
387 prev_volume = volume;
388 is_new = false;
389 }
390
391 for (s8 channel = 0; channel < channel_count; channel++) {
392 voice_states[channel] = &voice_context.GetDspSharedState(channel_resource_ids[channel]);
393 }
394
395 return UpdateParametersForCommandGeneration(voice_states);
396}
397
398void VoiceInfo::ResetResources(VoiceContext& voice_context) const {
399 for (s8 channel = 0; channel < channel_count; channel++) {
400 auto& state{voice_context.GetDspSharedState(channel_resource_ids[channel])};
401 state = {};
402
403 auto& channel_resource{voice_context.GetChannelResource(channel_resource_ids[channel])};
404 channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
405 }
406}
407
408} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <bitset>
8
9#include "audio_core/common/common.h"
10#include "audio_core/common/wave_buffer.h"
11#include "audio_core/renderer/behavior/behavior_info.h"
12#include "audio_core/renderer/memory/address_info.h"
13#include "common/common_types.h"
14
15namespace AudioCore::AudioRenderer {
16class PoolMapper;
17class VoiceContext;
18struct VoiceState;
19
20/**
21 * Represents one voice. Voices are essentially noises, and they can be further mixed and have
22 * effects applied to them, but voices are the basis of all sounds.
23 */
24class VoiceInfo {
25public:
26 enum class ServerPlayState {
27 Started,
28 Stopped,
29 RequestStop,
30 Paused,
31 };
32
33 struct Flags {
34 u8 IsVoicePlayedSampleCountResetAtLoopPointSupported : 1;
35 u8 IsVoicePitchAndSrcSkippedSupported : 1;
36 };
37
38 /**
39 * A wavebuffer contains information on the data source buffers.
40 */
41 struct WaveBuffer {
42 void Copy(WaveBufferVersion1& other) {
43 other.buffer = buffer_address.GetReference(true);
44 other.buffer_size = buffer_address.GetSize();
45 other.start_offset = start_offset;
46 other.end_offset = end_offset;
47 other.loop = loop;
48 other.stream_ended = stream_ended;
49
50 if (context_address.GetCpuAddr()) {
51 other.context = context_address.GetReference(true);
52 other.context_size = context_address.GetSize();
53 } else {
54 other.context = CpuAddr(0);
55 other.context_size = 0;
56 }
57 }
58
59 void Copy(WaveBufferVersion2& other) {
60 other.buffer = buffer_address.GetReference(true);
61 other.buffer_size = buffer_address.GetSize();
62 other.start_offset = start_offset;
63 other.end_offset = end_offset;
64 other.loop_start_offset = loop_start_offset;
65 other.loop_end_offset = loop_end_offset;
66 other.loop = loop;
67 other.loop_count = loop_count;
68 other.stream_ended = stream_ended;
69
70 if (context_address.GetCpuAddr()) {
71 other.context = context_address.GetReference(true);
72 other.context_size = context_address.GetSize();
73 } else {
74 other.context = CpuAddr(0);
75 other.context_size = 0;
76 }
77 }
78
79 void Initialize() {
80 buffer_address.Setup(0, 0);
81 context_address.Setup(0, 0);
82 start_offset = 0;
83 end_offset = 0;
84 loop = false;
85 stream_ended = false;
86 sent_to_DSP = true;
87 loop_start_offset = 0;
88 loop_end_offset = 0;
89 loop_count = 0;
90 }
91 /// Game memory address of the wavebuffer data
92 AddressInfo buffer_address{0, 0};
93 /// Context for decoding, used for ADPCM
94 AddressInfo context_address{0, 0};
95 /// Starting offset for the wavebuffer
96 u32 start_offset{};
97 /// Ending offset the wavebuffer
98 u32 end_offset{};
99 /// Should this wavebuffer loop?
100 bool loop{};
101 /// Has this wavebuffer ended?
102 bool stream_ended{};
103 /// Has this wavebuffer been sent to the AudioRenderer?
104 bool sent_to_DSP{true};
105 /// Starting offset when looping, can differ from start_offset
106 u32 loop_start_offset{};
107 /// Ending offset when looping, can differ from end_offset
108 u32 loop_end_offset{};
109 /// Number of times to loop this wavebuffer
110 s32 loop_count{};
111 };
112
113 struct WaveBufferInternal {
114 /* 0x00 */ CpuAddr address;
115 /* 0x08 */ u64 size;
116 /* 0x10 */ s32 start_offset;
117 /* 0x14 */ s32 end_offset;
118 /* 0x18 */ bool loop;
119 /* 0x19 */ bool stream_ended;
120 /* 0x1A */ bool sent_to_DSP;
121 /* 0x1C */ s32 loop_count;
122 /* 0x20 */ CpuAddr context_address;
123 /* 0x28 */ u64 context_size;
124 /* 0x30 */ u32 loop_start;
125 /* 0x34 */ u32 loop_end;
126 };
127 static_assert(sizeof(WaveBufferInternal) == 0x38,
128 "VoiceInfo::WaveBufferInternal has the wrong size!");
129
130 struct BiquadFilterParameter {
131 /* 0x00 */ bool enabled;
132 /* 0x02 */ std::array<s16, 3> b;
133 /* 0x08 */ std::array<s16, 2> a;
134 };
135 static_assert(sizeof(BiquadFilterParameter) == 0xC,
136 "VoiceInfo::BiquadFilterParameter has the wrong size!");
137
138 struct InParameter {
139 /* 0x000 */ u32 id;
140 /* 0x004 */ u32 node_id;
141 /* 0x008 */ bool is_new;
142 /* 0x009 */ bool in_use;
143 /* 0x00A */ PlayState play_state;
144 /* 0x00B */ SampleFormat sample_format;
145 /* 0x00C */ u32 sample_rate;
146 /* 0x010 */ s32 priority;
147 /* 0x014 */ s32 sort_order;
148 /* 0x018 */ u32 channel_count;
149 /* 0x01C */ f32 pitch;
150 /* 0x020 */ f32 volume;
151 /* 0x024 */ std::array<BiquadFilterParameter, MaxBiquadFilters> biquads;
152 /* 0x03C */ u32 wave_buffer_count;
153 /* 0x040 */ u16 wave_buffer_index;
154 /* 0x042 */ char unk042[0x6];
155 /* 0x048 */ CpuAddr src_data_address;
156 /* 0x050 */ u64 src_data_size;
157 /* 0x058 */ u32 mix_id;
158 /* 0x05C */ u32 splitter_id;
159 /* 0x060 */ std::array<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal;
160 /* 0x140 */ std::array<u32, MaxChannels> channel_resource_ids;
161 /* 0x158 */ bool clear_voice_drop;
162 /* 0x159 */ u8 flush_buffer_count;
163 /* 0x15A */ char unk15A[0x2];
164 /* 0x15C */ Flags flags;
165 /* 0x15D */ char unk15D[0x1];
166 /* 0x15E */ SrcQuality src_quality;
167 /* 0x15F */ char unk15F[0x11];
168 };
169 static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!");
170
171 struct OutStatus {
172 /* 0x00 */ u64 played_sample_count;
173 /* 0x08 */ u32 wave_buffers_consumed;
174 /* 0x0C */ bool voice_dropped;
175 };
176 static_assert(sizeof(OutStatus) == 0x10, "OutStatus::InParameter has the wrong size!");
177
178 VoiceInfo();
179
180 /**
181 * Initialize this voice.
182 */
183 void Initialize();
184
185 /**
186 * Does this voice ned an update?
187 *
188 * @param params - Input parametetrs to check matching.
189 * @return True if this voice needs an update, otherwise false.
190 */
191 bool ShouldUpdateParameters(const InParameter& params) const;
192
193 /**
194 * Update the parameters of this voice.
195 *
196 * @param error_info - Output error code.
197 * @param params - Input parametters to udpate from.
198 * @param pool_mapper - Used to map buffers.
199 * @param behavior - behavior to check supported features.
200 */
201 void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
202 const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
203
204 /**
205 * Update the current play state.
206 *
207 * @param state - New play state for this voice.
208 */
209 void UpdatePlayState(PlayState state);
210
211 /**
212 * Update the current sample rate conversion quality.
213 *
214 * @param quality - New quality.
215 */
216 void UpdateSrcQuality(SrcQuality quality);
217
218 /**
219 * Update all wavebuffers.
220 *
221 * @param error_infos - Output 2D array of errors, 2 per wavebuffer.
222 * @param error_count - Number of errors provided. Unused.
223 * @param params - Input parametters to be used for the update.
224 * @param voice_states - The voice states for each channel in this voice to be updated.
225 * @param pool_mapper - Used to map the wavebuffers.
226 * @param behavior - Used to check for supported features.
227 */
228 void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
229 u32 error_count, const InParameter& params,
230 std::span<VoiceState*> voice_states, const PoolMapper& pool_mapper,
231 const BehaviorInfo& behavior);
232
233 /**
234 * Update a wavebuffer.
235 *
236 * @param error_infos - Output array of errors.
237 * @param wave_buffer - The wavebuffer to be updated.
238 * @param wave_buffer_internal - Input parametters to be used for the update.
239 * @param sample_format - Sample format of the wavebuffer.
240 * @param valid - Is this wavebuffer valid?
241 * @param pool_mapper - Used to map the wavebuffers.
242 * @param behavior - Used to check for supported features.
243 */
244 void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
245 const WaveBufferInternal& wave_buffer_internal,
246 SampleFormat sample_format, bool valid, const PoolMapper& pool_mapper,
247 const BehaviorInfo& behavior);
248
249 /**
250 * Check if the input wavebuffer needs an update.
251 *
252 * @param wave_buffer_internal - Input wavebuffer parameters to check.
253 * @return True if the given wavebuffer needs an update, otherwise false.
254 */
255 bool ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const;
256
257 /**
258 * Write the number of played samples, number of consumed wavebuffers and if this voice was
259 * dropped, to the given out_status.
260 *
261 * @param out_status - Output status to be written to.
262 * @param in_params - Input parameters to check if the wavebuffer is new.
263 * @param voice_states - Current host voice states for this voice, source of the output.
264 */
265 void WriteOutStatus(OutStatus& out_status, const InParameter& in_params,
266 std::span<VoiceState*> voice_states);
267
268 /**
269 * Check if this voice should be skipped for command generation.
270 * Checks various things such as usage state, whether data is mapped etc.
271 *
272 * @return True if this voice should not be generated, otherwise false.
273 */
274 bool ShouldSkip() const;
275
276 /**
277 * Check if this voice has any mixing connections.
278 *
279 * @return True if this voice participes in mixing, otherwise false.
280 */
281 bool HasAnyConnection() const;
282
283 /**
284 * Flush flush_count wavebuffers, marking them as consumed.
285 *
286 * @param flush_count - Number of wavebuffers to flush.
287 * @param voice_states - Voice states for these wavebuffers.
288 * @param channel_count - Number of active channels.
289 */
290 void FlushWaveBuffers(u32 flush_count, std::span<VoiceState*> voice_states, s8 channel_count);
291
292 /**
293 * Update this voice's parameters on command generation,
294 * updating voice states and flushing if needed.
295 *
296 * @param voice_states - Voice states for these wavebuffers.
297 * @return True if this voice should be generated, otherwise false.
298 */
299 bool UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states);
300
301 /**
302 * Update this voice on command generation.
303 *
304 * @param voice_states - Voice states for these wavebuffers.
305 * @return True if this voice should be generated, otherwise false.
306 */
307 bool UpdateForCommandGeneration(VoiceContext& voice_context);
308
309 /**
310 * Reset the AudioRenderer-side voice states, and the channel resources for this voice.
311 *
312 * @param voice_context - Context from which to get the resources.
313 */
314 void ResetResources(VoiceContext& voice_context) const;
315
316 /// Is this voice in use?
317 bool in_use{};
318 /// Is this voice new?
319 bool is_new{};
320 /// Was this voice last playing? Used for depopping
321 bool was_playing{};
322 /// Sample format of the wavebuffers in this voice
323 SampleFormat sample_format{};
324 /// Sample rate of the wavebuffers in this voice
325 u32 sample_rate{};
326 /// Number of channels in this voice
327 s8 channel_count{};
328 /// Id of this voice
329 u32 id{};
330 /// Node id of this voice
331 u32 node_id{};
332 /// Mix id this voice is mixed to
333 u32 mix_id{};
334 /// Play state of this voice
335 ServerPlayState current_play_state{ServerPlayState::Stopped};
336 /// Last play state of this voice
337 ServerPlayState last_play_state{ServerPlayState::Started};
338 /// Priority of this voice, lower is higher
339 s32 priority{};
340 /// Sort order of this voice, used when same priority
341 s32 sort_order{};
342 /// Pitch of this voice (for sample rate conversion)
343 f32 pitch{};
344 /// Current volume of this voice
345 f32 volume{};
346 /// Previous volume of this voice
347 f32 prev_volume{};
348 /// Biquad filters for generating filter commands on this voice
349 std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{};
350 /// Number of active wavebuffers
351 u32 wave_buffer_count{};
352 /// Current playing wavebuffer index
353 u16 wave_buffer_index{};
354 /// Flags controlling decode behavior
355 u16 flags{};
356 /// Game memory for ADPCM coefficients
357 AddressInfo data_address{0, 0};
358 /// Wavebuffers
359 std::array<WaveBuffer, MaxWaveBuffers> wavebuffers{};
360 /// Channel resources for this voice
361 std::array<u32, MaxChannels> channel_resource_ids{};
362 /// Splitter id this voice is connected with
363 s32 splitter_id{UnusedSplitterId};
364 /// Sample rate conversion quality
365 SrcQuality src_quality{SrcQuality::Medium};
366 /// Was this voice dropped due to limited time?
367 bool voice_dropped{};
368 /// Is this voice's coefficient (data_address) unmapped?
369 bool data_unmapped{};
370 /// Is this voice's buffers (wavebuffer data and ADPCM context) unmapped?
371 bool buffer_unmapped{};
372 /// Initialisation state of the biquads
373 std::array<bool, MaxBiquadFilters> biquad_initialized{};
374 /// Number of wavebuffers to flush
375 u8 flush_buffer_count{};
376};
377
378} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10#include "common/fixed_point.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer,
15 * host-side is updated on the next iteration.
16 */
17struct VoiceState {
18 /**
19 * State of the voice's biquad filter.
20 */
21 struct BiquadFilterState {
22 Common::FixedPoint<50, 14> s0;
23 Common::FixedPoint<50, 14> s1;
24 Common::FixedPoint<50, 14> s2;
25 Common::FixedPoint<50, 14> s3;
26 };
27
28 /**
29 * Context for ADPCM decoding.
30 */
31 struct AdpcmContext {
32 u16 header;
33 s16 yn0;
34 s16 yn1;
35 };
36
37 /// Number of samples played
38 u64 played_sample_count;
39 /// Current offset from the starting offset
40 u32 offset;
41 /// Currently active wavebuffer index
42 u32 wave_buffer_index;
43 /// Array of which wavebuffers are currently valid
44
45 std::array<bool, MaxWaveBuffers> wave_buffer_valid;
46 /// Number of wavebuffers consumed, given back to the game
47 u32 wave_buffers_consumed;
48 /// History of samples, used for rate conversion
49
50 std::array<s16, MaxWaveBuffers * 2> sample_history;
51 /// Current read fraction, used for resampling
52 Common::FixedPoint<49, 15> fraction;
53 /// Current adpcm context
54 AdpcmContext adpcm_context;
55 /// Current biquad states, used when filtering
56
57 std::array<std::array<BiquadFilterState, MaxBiquadFilters>, MaxBiquadFilters> biquad_states;
58 /// Previous samples
59 std::array<s32, MaxMixBuffers> previous_samples;
60 /// Unused
61 u32 external_context_size;
62 /// Unused
63 bool external_context_enabled;
64 /// Was this voice dropped?
65 bool voice_dropped;
66 /// Number of times the wavebuffer has looped
67 s32 loop_count;
68};
69
70} // namespace AudioCore::AudioRenderer