diff options
| author | 2022-07-16 23:48:45 +0100 | |
|---|---|---|
| committer | 2022-07-22 01:11:32 +0100 | |
| commit | 458da8a94877677f086f06cdeecf959ec4283a33 (patch) | |
| tree | 583166d77602ad90a0d552f37de8729ad80fd6c1 /src/audio_core/renderer | |
| parent | Merge pull request #8598 from Link4565/recv-dontwait (diff) | |
| download | yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.gz yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.xz yuzu-458da8a94877677f086f06cdeecf959ec4283a33.zip | |
Project Andio
Diffstat (limited to 'src/audio_core/renderer')
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 | |||
| 13 | namespace AudioCore::AudioRenderer::ADSP { | ||
| 14 | |||
| 15 | ADSP::ADSP(Core::System& system_, Sink::Sink& sink_) | ||
| 16 | : system{system_}, memory{system.Memory()}, sink{sink_} {} | ||
| 17 | |||
| 18 | ADSP::~ADSP() { | ||
| 19 | ClearCommandBuffers(); | ||
| 20 | } | ||
| 21 | |||
| 22 | State ADSP::GetState() const { | ||
| 23 | if (running) { | ||
| 24 | return State::Started; | ||
| 25 | } | ||
| 26 | return State::Stopped; | ||
| 27 | } | ||
| 28 | |||
| 29 | AudioRenderer_Mailbox* ADSP::GetRenderMailbox() { | ||
| 30 | return &render_mailbox; | ||
| 31 | } | ||
| 32 | |||
| 33 | void ADSP::ClearRemainCount(const u32 session_id) { | ||
| 34 | render_mailbox.ClearRemainCount(session_id); | ||
| 35 | } | ||
| 36 | |||
| 37 | u64 ADSP::GetSignalledTick() const { | ||
| 38 | return render_mailbox.GetSignalledTick(); | ||
| 39 | } | ||
| 40 | |||
| 41 | u64 ADSP::GetTimeTaken() const { | ||
| 42 | return render_mailbox.GetRenderTimeTaken(); | ||
| 43 | } | ||
| 44 | |||
| 45 | u64 ADSP::GetRenderTimeTaken(const u32 session_id) { | ||
| 46 | return render_mailbox.GetCommandBuffer(session_id).render_time_taken; | ||
| 47 | } | ||
| 48 | |||
| 49 | u32 ADSP::GetRemainCommandCount(const u32 session_id) const { | ||
| 50 | return render_mailbox.GetRemainCommandCount(session_id); | ||
| 51 | } | ||
| 52 | |||
| 53 | void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) { | ||
| 54 | render_mailbox.SetCommandBuffer(session_id, command_buffer); | ||
| 55 | } | ||
| 56 | |||
| 57 | u64 ADSP::GetRenderingStartTick(const u32 session_id) { | ||
| 58 | return render_mailbox.GetSignalledTick() + | ||
| 59 | render_mailbox.GetCommandBuffer(session_id).render_time_taken; | ||
| 60 | } | ||
| 61 | |||
| 62 | bool 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 | |||
| 80 | void 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 | |||
| 96 | void 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 | |||
| 102 | void 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 | |||
| 114 | void 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 | |||
| 12 | namespace Core { | ||
| 13 | namespace Memory { | ||
| 14 | class Memory; | ||
| 15 | } | ||
| 16 | class System; | ||
| 17 | } // namespace Core | ||
| 18 | |||
| 19 | namespace AudioCore { | ||
| 20 | namespace Sink { | ||
| 21 | class Sink; | ||
| 22 | } | ||
| 23 | |||
| 24 | namespace AudioRenderer::ADSP { | ||
| 25 | struct CommandBuffer; | ||
| 26 | |||
| 27 | enum 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 | */ | ||
| 52 | class ADSP { | ||
| 53 | public: | ||
| 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 | |||
| 153 | private: | ||
| 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 | |||
| 18 | MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97)); | ||
| 19 | |||
| 20 | namespace AudioCore::AudioRenderer::ADSP { | ||
| 21 | |||
| 22 | void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) { | ||
| 23 | adsp_messages.enqueue(message_); | ||
| 24 | adsp_event.Set(); | ||
| 25 | } | ||
| 26 | |||
| 27 | RenderMessage 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 | |||
| 36 | void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) { | ||
| 37 | host_messages.enqueue(message_); | ||
| 38 | host_event.Set(); | ||
| 39 | } | ||
| 40 | |||
| 41 | RenderMessage 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 | |||
| 50 | CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) { | ||
| 51 | return command_buffers[session_id]; | ||
| 52 | } | ||
| 53 | |||
| 54 | void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) { | ||
| 55 | command_buffers[session_id] = buffer; | ||
| 56 | } | ||
| 57 | |||
| 58 | u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const { | ||
| 59 | return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken; | ||
| 60 | } | ||
| 61 | |||
| 62 | u64 AudioRenderer_Mailbox::GetSignalledTick() const { | ||
| 63 | return signalled_tick; | ||
| 64 | } | ||
| 65 | |||
| 66 | void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) { | ||
| 67 | signalled_tick = tick; | ||
| 68 | } | ||
| 69 | |||
| 70 | void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) { | ||
| 71 | command_buffers[session_id].remaining_command_count = 0; | ||
| 72 | } | ||
| 73 | |||
| 74 | u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const { | ||
| 75 | return command_buffers[session_id].remaining_command_count; | ||
| 76 | } | ||
| 77 | |||
| 78 | void 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 | |||
| 87 | AudioRenderer::AudioRenderer(Core::System& system_) | ||
| 88 | : system{system_}, sink{system.AudioCore().GetOutputSink()} { | ||
| 89 | CreateSinkStreams(); | ||
| 90 | } | ||
| 91 | |||
| 92 | AudioRenderer::~AudioRenderer() { | ||
| 93 | Stop(); | ||
| 94 | for (auto& stream : streams) { | ||
| 95 | if (stream) { | ||
| 96 | sink.CloseStream(stream); | ||
| 97 | } | ||
| 98 | stream = nullptr; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | void 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 | |||
| 115 | void 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 | |||
| 127 | void 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 | |||
| 136 | void 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 | |||
| 16 | namespace Core { | ||
| 17 | namespace Timing { | ||
| 18 | struct EventType; | ||
| 19 | } | ||
| 20 | class System; | ||
| 21 | } // namespace Core | ||
| 22 | |||
| 23 | namespace AudioCore { | ||
| 24 | namespace Sink { | ||
| 25 | class Sink; | ||
| 26 | } | ||
| 27 | |||
| 28 | namespace AudioRenderer::ADSP { | ||
| 29 | |||
| 30 | enum 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 | */ | ||
| 50 | class AudioRenderer_Mailbox { | ||
| 51 | public: | ||
| 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 | |||
| 137 | private: | ||
| 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 | */ | ||
| 158 | class AudioRenderer { | ||
| 159 | public: | ||
| 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 | |||
| 175 | private: | ||
| 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 | |||
| 9 | namespace AudioCore::AudioRenderer::ADSP { | ||
| 10 | |||
| 11 | struct 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 | |||
| 15 | namespace AudioCore::AudioRenderer::ADSP { | ||
| 16 | |||
| 17 | void 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 | |||
| 33 | void CommandListProcessor::SetProcessTimeMax(const u64 time) { | ||
| 34 | max_process_time = time; | ||
| 35 | } | ||
| 36 | |||
| 37 | u32 CommandListProcessor::GetRemainingCommandCount() const { | ||
| 38 | return command_count - processed_command_count; | ||
| 39 | } | ||
| 40 | |||
| 41 | void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) { | ||
| 42 | commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader)); | ||
| 43 | commands_buffer_size = size; | ||
| 44 | } | ||
| 45 | |||
| 46 | Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { | ||
| 47 | return stream; | ||
| 48 | } | ||
| 49 | |||
| 50 | u64 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 | |||
| 11 | namespace Core { | ||
| 12 | namespace Memory { | ||
| 13 | class Memory; | ||
| 14 | } | ||
| 15 | class System; | ||
| 16 | } // namespace Core | ||
| 17 | |||
| 18 | namespace AudioCore { | ||
| 19 | namespace Sink { | ||
| 20 | class SinkStream; | ||
| 21 | } | ||
| 22 | |||
| 23 | namespace AudioRenderer { | ||
| 24 | struct CommandListHeader; | ||
| 25 | |||
| 26 | namespace ADSP { | ||
| 27 | |||
| 28 | /** | ||
| 29 | * A processor for command lists given to the AudioRenderer. | ||
| 30 | */ | ||
| 31 | class CommandListProcessor { | ||
| 32 | public: | ||
| 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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | AudioDevice::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 | |||
| 17 | u32 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 | |||
| 34 | u32 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 | |||
| 44 | void AudioDevice::SetDeviceVolumes(const f32 volume) { | ||
| 45 | output_sink.SetDeviceVolume(volume); | ||
| 46 | } | ||
| 47 | |||
| 48 | f32 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 | |||
| 10 | namespace Core { | ||
| 11 | class System; | ||
| 12 | } | ||
| 13 | |||
| 14 | namespace AudioCore { | ||
| 15 | namespace Sink { | ||
| 16 | class Sink; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace AudioRenderer { | ||
| 20 | /** | ||
| 21 | * An interface to an output audio device available to the Switch. | ||
| 22 | */ | ||
| 23 | class AudioDevice { | ||
| 24 | public: | ||
| 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 | |||
| 78 | private: | ||
| 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event) | ||
| 15 | : core{system_}, manager{manager_}, system{system_, rendered_event} {} | ||
| 16 | |||
| 17 | Result 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 | |||
| 37 | void 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 | |||
| 50 | System& Renderer::GetSystem() { | ||
| 51 | return system; | ||
| 52 | } | ||
| 53 | |||
| 54 | void Renderer::Start() { | ||
| 55 | system.Start(); | ||
| 56 | } | ||
| 57 | |||
| 58 | void Renderer::Stop() { | ||
| 59 | system.Stop(); | ||
| 60 | } | ||
| 61 | |||
| 62 | Result 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 | |||
| 11 | namespace Core { | ||
| 12 | class System; | ||
| 13 | } | ||
| 14 | |||
| 15 | namespace Kernel { | ||
| 16 | class KTransferMemory; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace AudioCore { | ||
| 20 | struct AudioRendererParameterInternal; | ||
| 21 | |||
| 22 | namespace AudioRenderer { | ||
| 23 | class Manager; | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls. | ||
| 27 | */ | ||
| 28 | class Renderer { | ||
| 29 | public: | ||
| 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 | |||
| 83 | private: | ||
| 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 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {} | ||
| 10 | |||
| 11 | u32 BehaviorInfo::GetProcessRevisionNum() const { | ||
| 12 | return process_revision; | ||
| 13 | } | ||
| 14 | |||
| 15 | u32 BehaviorInfo::GetProcessRevision() const { | ||
| 16 | return Common::MakeMagic('R', 'E', 'V', | ||
| 17 | static_cast<char>(static_cast<u8>('0') + process_revision)); | ||
| 18 | } | ||
| 19 | |||
| 20 | u32 BehaviorInfo::GetUserRevisionNum() const { | ||
| 21 | return user_revision; | ||
| 22 | } | ||
| 23 | |||
| 24 | u32 BehaviorInfo::GetUserRevision() const { | ||
| 25 | return Common::MakeMagic('R', 'E', 'V', | ||
| 26 | static_cast<char>(static_cast<u8>('0') + user_revision)); | ||
| 27 | } | ||
| 28 | |||
| 29 | void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) { | ||
| 30 | user_revision = GetRevisionNum(user_revision_); | ||
| 31 | } | ||
| 32 | |||
| 33 | void BehaviorInfo::ClearError() { | ||
| 34 | error_count = 0; | ||
| 35 | } | ||
| 36 | |||
| 37 | void 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 | |||
| 45 | void 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 | |||
| 55 | void BehaviorInfo::UpdateFlags(const Flags flags_) { | ||
| 56 | flags = flags_; | ||
| 57 | } | ||
| 58 | |||
| 59 | bool BehaviorInfo::IsMemoryForceMappingEnabled() const { | ||
| 60 | return flags.IsMemoryForceMappingEnabled; | ||
| 61 | } | ||
| 62 | |||
| 63 | bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { | ||
| 64 | return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision); | ||
| 65 | } | ||
| 66 | |||
| 67 | bool BehaviorInfo::IsSplitterSupported() const { | ||
| 68 | return CheckFeatureSupported(SupportTags::Splitter, user_revision); | ||
| 69 | } | ||
| 70 | |||
| 71 | bool BehaviorInfo::IsSplitterBugFixed() const { | ||
| 72 | return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision); | ||
| 73 | } | ||
| 74 | |||
| 75 | bool BehaviorInfo::IsEffectInfoVersion2Supported() const { | ||
| 76 | return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision); | ||
| 77 | } | ||
| 78 | |||
| 79 | bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const { | ||
| 80 | return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize, | ||
| 81 | user_revision); | ||
| 82 | } | ||
| 83 | |||
| 84 | bool BehaviorInfo::IsWaveBufferVer2Supported() const { | ||
| 85 | return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision); | ||
| 86 | } | ||
| 87 | |||
| 88 | bool BehaviorInfo::IsLongSizePreDelaySupported() const { | ||
| 89 | return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision); | ||
| 90 | } | ||
| 91 | |||
| 92 | bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const { | ||
| 93 | return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2, | ||
| 94 | user_revision); | ||
| 95 | } | ||
| 96 | |||
| 97 | bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const { | ||
| 98 | return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3, | ||
| 99 | user_revision); | ||
| 100 | } | ||
| 101 | |||
| 102 | bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const { | ||
| 103 | return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4, | ||
| 104 | user_revision); | ||
| 105 | } | ||
| 106 | |||
| 107 | bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const { | ||
| 108 | return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4, | ||
| 109 | user_revision); | ||
| 110 | } | ||
| 111 | |||
| 112 | bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const { | ||
| 113 | return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent, | ||
| 114 | user_revision); | ||
| 115 | } | ||
| 116 | |||
| 117 | bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const { | ||
| 118 | return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent, | ||
| 119 | user_revision); | ||
| 120 | } | ||
| 121 | |||
| 122 | bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const { | ||
| 123 | return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent, | ||
| 124 | user_revision); | ||
| 125 | } | ||
| 126 | |||
| 127 | bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { | ||
| 128 | return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision); | ||
| 129 | } | ||
| 130 | |||
| 131 | bool BehaviorInfo::IsElapsedFrameCountSupported() const { | ||
| 132 | return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision); | ||
| 133 | } | ||
| 134 | |||
| 135 | bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const { | ||
| 136 | return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision); | ||
| 137 | } | ||
| 138 | |||
| 139 | size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const { | ||
| 140 | if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) { | ||
| 141 | return 2; | ||
| 142 | } | ||
| 143 | return 1; | ||
| 144 | } | ||
| 145 | |||
| 146 | bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { | ||
| 147 | return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision); | ||
| 148 | } | ||
| 149 | |||
| 150 | bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { | ||
| 151 | return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint, | ||
| 152 | user_revision); | ||
| 153 | } | ||
| 154 | |||
| 155 | bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const { | ||
| 156 | return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision); | ||
| 157 | } | ||
| 158 | |||
| 159 | bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const { | ||
| 160 | return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision); | ||
| 161 | } | ||
| 162 | |||
| 163 | bool BehaviorInfo::UseBiquadFilterFloatProcessing() const { | ||
| 164 | return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision); | ||
| 165 | } | ||
| 166 | |||
| 167 | bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { | ||
| 168 | return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision); | ||
| 169 | } | ||
| 170 | |||
| 171 | bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const { | ||
| 172 | return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision); | ||
| 173 | } | ||
| 174 | |||
| 175 | bool BehaviorInfo::IsDeviceApiVersion2Supported() const { | ||
| 176 | return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision); | ||
| 177 | } | ||
| 178 | |||
| 179 | bool BehaviorInfo::IsDelayChannelMappingChanged() const { | ||
| 180 | return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision); | ||
| 181 | } | ||
| 182 | |||
| 183 | bool BehaviorInfo::IsReverbChannelMappingChanged() const { | ||
| 184 | return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision); | ||
| 185 | } | ||
| 186 | |||
| 187 | bool 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | /** | ||
| 15 | * Holds host and user revisions, checks whether render features can be enabled, and reports errors. | ||
| 16 | */ | ||
| 17 | class BehaviorInfo { | ||
| 18 | static constexpr u32 MaxErrors = 10; | ||
| 19 | |||
| 20 | public: | ||
| 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 | |||
| 18 | namespace AudioCore::AudioRenderer { | ||
| 19 | |||
| 20 | InfoUpdater::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 | |||
| 32 | Result 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 | |||
| 58 | Result 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 | |||
| 139 | Result 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 | |||
| 151 | Result 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 | |||
| 198 | Result 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 | |||
| 253 | Result 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 | |||
| 338 | Result 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 | |||
| 398 | Result 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 | |||
| 436 | Result 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 | |||
| 466 | Result 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 | |||
| 488 | Result 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 | |||
| 500 | Result 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 | |||
| 511 | Result 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 | |||
| 530 | Result 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | class BehaviorInfo; | ||
| 13 | class VoiceContext; | ||
| 14 | class MixContext; | ||
| 15 | class SinkContext; | ||
| 16 | class SplitterContext; | ||
| 17 | class EffectContext; | ||
| 18 | class MemoryPoolInfo; | ||
| 19 | class PerformanceManager; | ||
| 20 | |||
| 21 | class 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 | |||
| 41 | public: | ||
| 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 | |||
| 158 | private: | ||
| 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 | |||
| 19 | namespace AudioCore::AudioRenderer { | ||
| 20 | |||
| 21 | template <typename T, CommandId Id> | ||
| 22 | T& 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 | |||
| 41 | template <typename T> | ||
| 42 | void 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 | |||
| 49 | void 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 | |||
| 75 | void 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 | |||
| 99 | void 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 | |||
| 125 | void 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 | |||
| 149 | void 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 | |||
| 174 | void 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 | |||
| 199 | void 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 | |||
| 212 | void 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 | |||
| 225 | void 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 | |||
| 246 | void 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 | |||
| 272 | void 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 | |||
| 285 | void 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 | |||
| 306 | void 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 | |||
| 328 | void 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 | |||
| 347 | void 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 | |||
| 360 | void 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 | |||
| 390 | void 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 | |||
| 414 | void 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 | |||
| 431 | void 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 | |||
| 453 | void 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 | |||
| 481 | void 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 | |||
| 501 | void 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 | |||
| 533 | void 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 | |||
| 564 | void 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 | |||
| 574 | void CommandBuffer::GenerateClearMixCommand(const s32 node_id) { | ||
| 575 | auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)}; | ||
| 576 | GenerateEnd<ClearMixBufferCommand>(cmd); | ||
| 577 | } | ||
| 578 | |||
| 579 | void 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 | |||
| 591 | void 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, ¶meter, 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 | |||
| 616 | void 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 | |||
| 647 | void 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 | |||
| 670 | void 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 | |||
| 690 | void 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | struct UpsamplerInfo; | ||
| 15 | struct VoiceState; | ||
| 16 | class EffectInfoBase; | ||
| 17 | class ICommandProcessingTimeEstimator; | ||
| 18 | class MixInfo; | ||
| 19 | class MemoryPoolInfo; | ||
| 20 | class SinkInfoBase; | ||
| 21 | class VoiceInfo; | ||
| 22 | |||
| 23 | /** | ||
| 24 | * Utility functions to generate and add commands into the current command list. | ||
| 25 | */ | ||
| 26 | class CommandBuffer { | ||
| 27 | public: | ||
| 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 | |||
| 459 | private: | ||
| 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 | |||
| 24 | namespace AudioCore::AudioRenderer { | ||
| 25 | |||
| 26 | CommandGenerator::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 | |||
| 40 | void 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 | |||
| 118 | void 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 | |||
| 146 | void 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 | |||
| 168 | void 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 | |||
| 275 | void 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 | |||
| 299 | void 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 | |||
| 319 | void 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 | |||
| 324 | void 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 | |||
| 331 | void 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 | |||
| 337 | void 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 | |||
| 361 | void 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 | |||
| 400 | void 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 | |||
| 424 | void 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 | |||
| 446 | void 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 | |||
| 451 | void 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 | |||
| 580 | void 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 | |||
| 633 | void 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 | |||
| 649 | void 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 | |||
| 669 | void 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 | |||
| 693 | void 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 | |||
| 703 | void 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 | |||
| 746 | void 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 | |||
| 769 | void 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 | |||
| 791 | void 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 | |||
| 12 | namespace AudioCore { | ||
| 13 | struct AudioRendererSystemContext; | ||
| 14 | |||
| 15 | namespace AudioRenderer { | ||
| 16 | class CommandBuffer; | ||
| 17 | struct CommandListHeader; | ||
| 18 | class VoiceContext; | ||
| 19 | class MixContext; | ||
| 20 | class EffectContext; | ||
| 21 | class SplitterContext; | ||
| 22 | class SinkContext; | ||
| 23 | class BehaviorInfo; | ||
| 24 | class VoiceInfo; | ||
| 25 | struct VoiceState; | ||
| 26 | class MixInfo; | ||
| 27 | class SinkInfoBase; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Generates all commands to build up a command list, which are sent to the AudioRender for | ||
| 31 | * processing. | ||
| 32 | */ | ||
| 33 | class CommandGenerator { | ||
| 34 | public: | ||
| 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 | |||
| 327 | private: | ||
| 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | |||
| 13 | struct 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 9 | const PcmInt16DataSourceVersion1Command& command) const { | ||
| 10 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | ||
| 11 | } | ||
| 12 | |||
| 13 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 14 | const PcmInt16DataSourceVersion2Command& command) const { | ||
| 15 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | ||
| 16 | } | ||
| 17 | |||
| 18 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 19 | [[maybe_unused]] const PcmFloatDataSourceVersion1Command& command) const { | ||
| 20 | return 0; | ||
| 21 | } | ||
| 22 | |||
| 23 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 24 | [[maybe_unused]] const PcmFloatDataSourceVersion2Command& command) const { | ||
| 25 | return 0; | ||
| 26 | } | ||
| 27 | |||
| 28 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 29 | const AdpcmDataSourceVersion1Command& command) const { | ||
| 30 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | ||
| 31 | } | ||
| 32 | |||
| 33 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 34 | const AdpcmDataSourceVersion2Command& command) const { | ||
| 35 | return static_cast<u32>(command.pitch * 0.25f * 1.2f); | ||
| 36 | } | ||
| 37 | |||
| 38 | u32 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 | |||
| 43 | u32 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 | |||
| 48 | u32 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 | |||
| 53 | u32 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 | |||
| 58 | u32 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 | |||
| 63 | u32 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 | |||
| 75 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 76 | [[maybe_unused]] const DepopPrepareCommand& command) const { | ||
| 77 | return 1080; | ||
| 78 | } | ||
| 79 | |||
| 80 | u32 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 | |||
| 86 | u32 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 | |||
| 91 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 92 | [[maybe_unused]] const UpsampleCommand& command) const { | ||
| 93 | return 357915; | ||
| 94 | } | ||
| 95 | |||
| 96 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 97 | [[maybe_unused]] const DownMix6chTo2chCommand& command) const { | ||
| 98 | return 16108; | ||
| 99 | } | ||
| 100 | |||
| 101 | u32 CommandProcessingTimeEstimatorVersion1::Estimate(const AuxCommand& command) const { | ||
| 102 | if (command.enabled) { | ||
| 103 | return 15956; | ||
| 104 | } | ||
| 105 | return 3765; | ||
| 106 | } | ||
| 107 | |||
| 108 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 109 | [[maybe_unused]] const DeviceSinkCommand& command) const { | ||
| 110 | return 10042; | ||
| 111 | } | ||
| 112 | |||
| 113 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 114 | [[maybe_unused]] const CircularBufferSinkCommand& command) const { | ||
| 115 | return 55; | ||
| 116 | } | ||
| 117 | |||
| 118 | u32 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 | |||
| 126 | u32 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 | |||
| 134 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 135 | [[maybe_unused]] const PerformanceCommand& command) const { | ||
| 136 | return 1454; | ||
| 137 | } | ||
| 138 | |||
| 139 | u32 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 | |||
| 145 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 146 | [[maybe_unused]] const CopyMixBufferCommand& command) const { | ||
| 147 | return 0; | ||
| 148 | } | ||
| 149 | |||
| 150 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 151 | [[maybe_unused]] const LightLimiterVersion1Command& command) const { | ||
| 152 | return 0; | ||
| 153 | } | ||
| 154 | |||
| 155 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 156 | [[maybe_unused]] const LightLimiterVersion2Command& command) const { | ||
| 157 | return 0; | ||
| 158 | } | ||
| 159 | |||
| 160 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 161 | [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { | ||
| 162 | return 0; | ||
| 163 | } | ||
| 164 | |||
| 165 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 166 | [[maybe_unused]] const CaptureCommand& command) const { | ||
| 167 | return 0; | ||
| 168 | } | ||
| 169 | |||
| 170 | u32 CommandProcessingTimeEstimatorVersion1::Estimate( | ||
| 171 | [[maybe_unused]] const CompressorCommand& command) const { | ||
| 172 | return 0; | ||
| 173 | } | ||
| 174 | |||
| 175 | u32 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 | |||
| 194 | u32 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 | |||
| 213 | u32 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 | |||
| 232 | u32 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 | |||
| 251 | u32 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 | |||
| 270 | u32 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 | |||
| 289 | u32 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 | |||
| 302 | u32 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 | |||
| 315 | u32 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 | |||
| 328 | u32 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 | |||
| 341 | u32 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 | |||
| 354 | u32 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 | |||
| 375 | u32 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 | |||
| 388 | u32 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 | |||
| 401 | u32 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 | |||
| 469 | u32 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 | |||
| 482 | u32 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 | |||
| 495 | u32 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 | |||
| 520 | u32 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 | |||
| 548 | u32 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 | |||
| 561 | u32 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 | |||
| 629 | u32 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 | |||
| 697 | u32 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 | |||
| 710 | u32 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 | |||
| 723 | u32 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 | |||
| 736 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 737 | [[maybe_unused]] const LightLimiterVersion1Command& command) const { | ||
| 738 | return 0; | ||
| 739 | } | ||
| 740 | |||
| 741 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 742 | [[maybe_unused]] const LightLimiterVersion2Command& command) const { | ||
| 743 | return 0; | ||
| 744 | } | ||
| 745 | |||
| 746 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 747 | [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { | ||
| 748 | return 0; | ||
| 749 | } | ||
| 750 | |||
| 751 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 752 | [[maybe_unused]] const CaptureCommand& command) const { | ||
| 753 | return 0; | ||
| 754 | } | ||
| 755 | |||
| 756 | u32 CommandProcessingTimeEstimatorVersion2::Estimate( | ||
| 757 | [[maybe_unused]] const CompressorCommand& command) const { | ||
| 758 | return 0; | ||
| 759 | } | ||
| 760 | |||
| 761 | u32 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 | |||
| 782 | u32 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 | |||
| 849 | u32 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 | |||
| 870 | u32 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 | |||
| 937 | u32 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 | |||
| 958 | u32 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 | |||
| 1025 | u32 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 | |||
| 1038 | u32 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 | |||
| 1051 | u32 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 | |||
| 1064 | u32 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 | |||
| 1077 | u32 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 | |||
| 1090 | u32 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 | |||
| 1111 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1112 | [[maybe_unused]] const DepopPrepareCommand& command) const { | ||
| 1113 | return 0; | ||
| 1114 | } | ||
| 1115 | |||
| 1116 | u32 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 | |||
| 1129 | u32 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 | |||
| 1197 | u32 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 | |||
| 1210 | u32 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 | |||
| 1223 | u32 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 | |||
| 1241 | u32 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 | |||
| 1269 | u32 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 | |||
| 1282 | u32 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 | |||
| 1350 | u32 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 | |||
| 1418 | u32 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 | |||
| 1431 | u32 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 | |||
| 1444 | u32 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 | |||
| 1457 | u32 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 | |||
| 1526 | u32 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 | |||
| 1627 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1628 | [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { | ||
| 1629 | return 0; | ||
| 1630 | } | ||
| 1631 | |||
| 1632 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1633 | [[maybe_unused]] const CaptureCommand& command) const { | ||
| 1634 | return 0; | ||
| 1635 | } | ||
| 1636 | |||
| 1637 | u32 CommandProcessingTimeEstimatorVersion3::Estimate( | ||
| 1638 | [[maybe_unused]] const CompressorCommand& command) const { | ||
| 1639 | return 0; | ||
| 1640 | } | ||
| 1641 | |||
| 1642 | u32 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 | |||
| 1663 | u32 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 | |||
| 1730 | u32 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 | |||
| 1751 | u32 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 | |||
| 1818 | u32 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 | |||
| 1839 | u32 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 | |||
| 1906 | u32 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 | |||
| 1919 | u32 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 | |||
| 1932 | u32 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 | |||
| 1945 | u32 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 | |||
| 1958 | u32 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 | |||
| 1971 | u32 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 | |||
| 1992 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 1993 | [[maybe_unused]] const DepopPrepareCommand& command) const { | ||
| 1994 | return 0; | ||
| 1995 | } | ||
| 1996 | |||
| 1997 | u32 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 | |||
| 2010 | u32 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 | |||
| 2078 | u32 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 | |||
| 2091 | u32 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 | |||
| 2104 | u32 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 | |||
| 2122 | u32 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 | |||
| 2150 | u32 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 | |||
| 2163 | u32 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 | |||
| 2231 | u32 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 | |||
| 2299 | u32 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 | |||
| 2312 | u32 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 | |||
| 2325 | u32 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 | |||
| 2338 | u32 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 | |||
| 2407 | u32 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 | |||
| 2508 | u32 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 | |||
| 2521 | u32 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 | |||
| 2539 | u32 CommandProcessingTimeEstimatorVersion4::Estimate( | ||
| 2540 | [[maybe_unused]] const CompressorCommand& command) const { | ||
| 2541 | return 0; | ||
| 2542 | } | ||
| 2543 | |||
| 2544 | u32 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 | |||
| 2565 | u32 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 | |||
| 2632 | u32 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 | |||
| 2653 | u32 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 | |||
| 2720 | u32 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 | |||
| 2741 | u32 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 | |||
| 2808 | u32 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 | |||
| 2821 | u32 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 | |||
| 2834 | u32 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 | |||
| 2847 | u32 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 | |||
| 2860 | u32 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 | |||
| 2873 | u32 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 | |||
| 2894 | u32 CommandProcessingTimeEstimatorVersion5::Estimate( | ||
| 2895 | [[maybe_unused]] const DepopPrepareCommand& command) const { | ||
| 2896 | return 0; | ||
| 2897 | } | ||
| 2898 | |||
| 2899 | u32 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 | |||
| 2912 | u32 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 | |||
| 2980 | u32 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 | |||
| 2993 | u32 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 | |||
| 3006 | u32 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 | |||
| 3024 | u32 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 | |||
| 3052 | u32 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 | |||
| 3065 | u32 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 | |||
| 3133 | u32 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 | |||
| 3201 | u32 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 | |||
| 3214 | u32 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 | |||
| 3227 | u32 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 | |||
| 3240 | u32 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 | |||
| 3309 | u32 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 | |||
| 3494 | u32 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 | |||
| 3507 | u32 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 | |||
| 3525 | u32 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Estimate the processing time required for all commands. | ||
| 12 | */ | ||
| 13 | class ICommandProcessingTimeEstimator { | ||
| 14 | public: | ||
| 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 | |||
| 49 | class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator { | ||
| 50 | public: | ||
| 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 | |||
| 85 | private: | ||
| 86 | u32 sample_count{}; | ||
| 87 | u32 buffer_count{}; | ||
| 88 | }; | ||
| 89 | |||
| 90 | class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator { | ||
| 91 | public: | ||
| 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 | |||
| 126 | private: | ||
| 127 | u32 sample_count{}; | ||
| 128 | u32 buffer_count{}; | ||
| 129 | }; | ||
| 130 | |||
| 131 | class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator { | ||
| 132 | public: | ||
| 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 | |||
| 167 | private: | ||
| 168 | u32 sample_count{}; | ||
| 169 | u32 buffer_count{}; | ||
| 170 | }; | ||
| 171 | |||
| 172 | class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator { | ||
| 173 | public: | ||
| 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 | |||
| 208 | private: | ||
| 209 | u32 sample_count{}; | ||
| 210 | u32 buffer_count{}; | ||
| 211 | }; | ||
| 212 | |||
| 213 | class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator { | ||
| 214 | public: | ||
| 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 | |||
| 249 | private: | ||
| 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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | void 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 | |||
| 19 | void 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 | |||
| 44 | bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 45 | return true; | ||
| 46 | } | ||
| 47 | |||
| 48 | void 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 | |||
| 55 | void 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 | |||
| 80 | bool 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 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | namespace ADSP { | ||
| 16 | class CommandListProcessor; | ||
| 17 | } | ||
| 18 | |||
| 19 | /** | ||
| 20 | * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers | ||
| 21 | * into the output_index mix buffer. | ||
| 22 | */ | ||
| 23 | struct 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 | */ | ||
| 71 | struct 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | |||
| 15 | constexpr u32 TempBufferSize = 0x3F00; | ||
| 16 | constexpr 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 | */ | ||
| 27 | template <typename T> | ||
| 28 | static 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 | */ | ||
| 102 | static 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 | */ | ||
| 229 | void 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 | |||
| 14 | namespace Core::Memory { | ||
| 15 | class Memory; | ||
| 16 | } | ||
| 17 | |||
| 18 | namespace AudioCore::AudioRenderer { | ||
| 19 | |||
| 20 | struct 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 | |||
| 38 | struct 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 | */ | ||
| 57 | void 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 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | void 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 | |||
| 19 | void 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 | |||
| 44 | bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 45 | return true; | ||
| 46 | } | ||
| 47 | |||
| 48 | void 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 | |||
| 57 | void 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 | |||
| 82 | bool 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | ||
| 15 | } | ||
| 16 | |||
| 17 | /** | ||
| 18 | * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers | ||
| 19 | * into the output_index mix buffer. | ||
| 20 | */ | ||
| 21 | struct 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 | */ | ||
| 69 | struct 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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | void 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 | |||
| 21 | void 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 | |||
| 46 | bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 47 | return true; | ||
| 48 | } | ||
| 49 | |||
| 50 | void 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 | |||
| 59 | void 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 | |||
| 83 | bool 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class CommandListProcessor; | ||
| 15 | } | ||
| 16 | |||
| 17 | /** | ||
| 18 | * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers | ||
| 19 | * into the output_index mix buffer. | ||
| 20 | */ | ||
| 21 | struct 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 | */ | ||
| 69 | struct 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 | |||
| 9 | namespace 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 | */ | ||
| 16 | static 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 | */ | ||
| 43 | static 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 | */ | ||
| 115 | static 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 | |||
| 170 | void 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 | |||
| 176 | void 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 | |||
| 203 | bool 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class 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 | */ | ||
| 20 | struct 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 | |||
| 8 | namespace 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 | */ | ||
| 19 | void 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 | */ | ||
| 60 | static 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 | |||
| 87 | void 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 | |||
| 94 | void 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 | |||
| 114 | bool 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class 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 | */ | ||
| 22 | struct 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 | */ | ||
| 70 | void 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 | |||
| 9 | namespace 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 | */ | ||
| 16 | static 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 | */ | ||
| 41 | static 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 | |||
| 121 | void 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 | |||
| 127 | void 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 | |||
| 138 | bool 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class 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 | */ | ||
| 20 | struct 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | static 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 | |||
| 34 | static 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 | |||
| 45 | static 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 | |||
| 114 | void 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 | |||
| 127 | void 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 | |||
| 152 | bool 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for limiting volume between a high and low threshold. | ||
| 20 | * Version 1. | ||
| 21 | */ | ||
| 22 | struct 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 | |||
| 7 | namespace 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 | */ | ||
| 14 | static 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 | */ | ||
| 34 | static 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 | */ | ||
| 75 | template <size_t NumChannels> | ||
| 76 | static 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 | */ | ||
| 155 | static 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 | |||
| 197 | void 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 | |||
| 210 | void 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 | |||
| 234 | bool 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class 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 | */ | ||
| 22 | struct 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{ | ||
| 12 | 5.0f, | ||
| 13 | 6.0f, | ||
| 14 | 13.0f, | ||
| 15 | 14.0f, | ||
| 16 | }; | ||
| 17 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MaxDelayLineTimes{ | ||
| 18 | 45.7042007446f, | ||
| 19 | 82.7817001343f, | ||
| 20 | 149.938293457f, | ||
| 21 | 271.575805664f, | ||
| 22 | }; | ||
| 23 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay0MaxDelayLineTimes{17.0f, 13.0f, | ||
| 24 | 9.0f, 7.0f}; | ||
| 25 | constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay1MaxDelayLineTimes{19.0f, 11.0f, | ||
| 26 | 10.0f, 6.0f}; | ||
| 27 | constexpr 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 | |||
| 50 | constexpr 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 | */ | ||
| 62 | static 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 | */ | ||
| 162 | static 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 | */ | ||
| 195 | static 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 | */ | ||
| 215 | static 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 | */ | ||
| 242 | template <size_t NumChannels> | ||
| 243 | static 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 | */ | ||
| 367 | static 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 | |||
| 396 | void 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 | |||
| 409 | void 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 | |||
| 433 | bool 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class 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 | */ | ||
| 22 | struct 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 | |||
| 7 | namespace 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 | */ | ||
| 15 | static 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 | */ | ||
| 25 | static 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 | */ | ||
| 48 | static 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 | |||
| 136 | void 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 | |||
| 149 | void 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 | |||
| 175 | bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { | ||
| 176 | return true; | ||
| 177 | } | ||
| 178 | |||
| 179 | void 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 | |||
| 192 | void 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 | |||
| 218 | bool 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for limiting volume between a high and low threshold. | ||
| 20 | * Version 1. | ||
| 21 | */ | ||
| 22 | struct 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 | */ | ||
| 64 | struct 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 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | void 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 | |||
| 17 | void 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 | |||
| 41 | bool 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for applying multiple biquads at once. | ||
| 20 | */ | ||
| 21 | struct 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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = { | ||
| 13 | 53.9532470703125f, | ||
| 14 | 79.19256591796875f, | ||
| 15 | 116.23876953125f, | ||
| 16 | 170.61529541015625f, | ||
| 17 | }; | ||
| 18 | |||
| 19 | constexpr std::array<f32, ReverbInfo::MaxDelayLines> DecayMaxDelayLineTimes = { | ||
| 20 | 7.0f, | ||
| 21 | 9.0f, | ||
| 22 | 13.0f, | ||
| 23 | 17.0f, | ||
| 24 | }; | ||
| 25 | |||
| 26 | constexpr 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 | |||
| 40 | constexpr 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 | |||
| 54 | constexpr 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 | |||
| 63 | constexpr 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 | */ | ||
| 78 | static 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 | */ | ||
| 168 | static 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 | */ | ||
| 208 | static 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 | */ | ||
| 227 | static 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 | */ | ||
| 250 | template <size_t NumChannels> | ||
| 251 | static 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 | */ | ||
| 369 | static 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 | |||
| 397 | void 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 | |||
| 412 | void 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 | |||
| 436 | bool 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives | ||
| 20 | * the results. | ||
| 21 | */ | ||
| 22 | struct 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | namespace ADSP { | ||
| 11 | class CommandListProcessor; | ||
| 12 | } | ||
| 13 | |||
| 14 | enum 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 | |||
| 48 | constexpr u32 CommandMagic{0xCAFEBABE}; | ||
| 49 | |||
| 50 | /** | ||
| 51 | * A command, generated by the host, and processed by the ADSP's AudioRenderer. | ||
| 52 | */ | ||
| 53 | struct 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, | ||
| 12 | std::string& string) { | ||
| 13 | string += fmt::format("ClearMixBufferCommand\n"); | ||
| 14 | } | ||
| 15 | |||
| 16 | void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { | ||
| 17 | memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes()); | ||
| 18 | } | ||
| 19 | |||
| 20 | bool 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for a clearing the mix buffers. | ||
| 18 | * Used at the start of each command list. | ||
| 19 | */ | ||
| 20 | struct 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 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | void 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 | |||
| 15 | void 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 | |||
| 23 | bool 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for a copying a mix buffer from input to output. | ||
| 18 | */ | ||
| 19 | struct 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 | |||
| 8 | namespace 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 | */ | ||
| 19 | static 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 | |||
| 39 | void 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 | |||
| 45 | void 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 | |||
| 60 | bool 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class 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 | */ | ||
| 21 | struct 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | void 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 | |||
| 20 | void 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 | |||
| 32 | bool 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for preparing depop. | ||
| 18 | * Adds the previusly output last samples to the depop buffer. | ||
| 19 | */ | ||
| 20 | struct 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 | |||
| 12 | namespace 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 | */ | ||
| 22 | template <size_t Q> | ||
| 23 | static 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 | |||
| 31 | void 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 | |||
| 40 | void 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 | |||
| 66 | bool 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class 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 | */ | ||
| 20 | struct 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 | |||
| 9 | namespace 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 | */ | ||
| 21 | template <size_t Q> | ||
| 22 | s32 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 | |||
| 43 | template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32, | ||
| 44 | const u32); | ||
| 45 | template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32, | ||
| 46 | const u32); | ||
| 47 | |||
| 48 | void 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 | |||
| 59 | void 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 | |||
| 90 | bool 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class 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 | */ | ||
| 21 | struct 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 | */ | ||
| 69 | template <size_t Q> | ||
| 70 | s32 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 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | void 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 | |||
| 24 | void 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 | |||
| 61 | bool 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class 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 | */ | ||
| 21 | struct 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 | |||
| 9 | namespace 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 | */ | ||
| 19 | template <size_t Q> | ||
| 20 | static 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 | |||
| 32 | void 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 | |||
| 41 | void 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 | |||
| 68 | bool 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for applying volume to a mix buffer. | ||
| 18 | */ | ||
| 19 | struct 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 | |||
| 8 | namespace 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 | */ | ||
| 19 | template <size_t Q> | ||
| 20 | static 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 | |||
| 41 | void 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 | |||
| 52 | void 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 | |||
| 80 | bool 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class 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 | */ | ||
| 20 | struct 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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | void 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 | |||
| 17 | void 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 | |||
| 39 | bool 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule. | ||
| 20 | */ | ||
| 21 | struct 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 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | void 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 | |||
| 22 | void 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 | |||
| 70 | bool 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | namespace ADSP { | ||
| 14 | class 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 | */ | ||
| 27 | struct 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | static 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 | |||
| 26 | static 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 | |||
| 310 | static 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 | |||
| 865 | void 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 | |||
| 12 | namespace 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 | */ | ||
| 25 | void 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 | |||
| 10 | namespace 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 | */ | ||
| 19 | static 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 | |||
| 222 | auto 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 | |||
| 237 | void 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 | |||
| 258 | bool 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class 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 | */ | ||
| 20 | struct 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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | void 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 | |||
| 23 | void 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 | |||
| 44 | bool 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | namespace ADSP { | ||
| 13 | class CommandListProcessor; | ||
| 14 | } | ||
| 15 | |||
| 16 | /** | ||
| 17 | * AudioRenderer command for sinking samples to a circular buffer. | ||
| 18 | */ | ||
| 19 | struct 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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | void 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 | |||
| 22 | void 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 | |||
| 51 | bool 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | namespace ADSP { | ||
| 15 | class CommandListProcessor; | ||
| 16 | } | ||
| 17 | |||
| 18 | /** | ||
| 19 | * AudioRenderer command for sinking samples to an output device. | ||
| 20 | */ | ||
| 21 | struct 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void 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 | |||
| 42 | void 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 | |||
| 77 | void AuxInfo::UpdateForCommandGeneration() { | ||
| 78 | if (enabled) { | ||
| 79 | usage_state = UsageState::Enabled; | ||
| 80 | } else { | ||
| 81 | usage_state = UsageState::Disabled; | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | void AuxInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 86 | |||
| 87 | void AuxInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} | ||
| 88 | |||
| 89 | CpuAddr 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 | |||
| 12 | namespace 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 | */ | ||
| 21 | class AuxInfo : public EffectInfoBase { | ||
| 22 | public: | ||
| 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void 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 | |||
| 22 | void 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 | |||
| 36 | void 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 | |||
| 47 | void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 48 | |||
| 49 | void 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | class BiquadFilterInfo : public EffectInfoBase { | ||
| 15 | public: | ||
| 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void 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 | |||
| 22 | void 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 | |||
| 36 | void BufferMixerInfo::UpdateForCommandGeneration() { | ||
| 37 | if (enabled) { | ||
| 38 | usage_state = UsageState::Enabled; | ||
| 39 | } else { | ||
| 40 | usage_state = UsageState::Disabled; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 45 | |||
| 46 | void 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | class BufferMixerInfo : public EffectInfoBase { | ||
| 15 | public: | ||
| 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 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | void 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 | |||
| 37 | void 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 | |||
| 66 | void CaptureInfo::UpdateForCommandGeneration() { | ||
| 67 | if (enabled) { | ||
| 68 | usage_state = UsageState::Enabled; | ||
| 69 | } else { | ||
| 70 | usage_state = UsageState::Disabled; | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | void CaptureInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 75 | |||
| 76 | void CaptureInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} | ||
| 77 | |||
| 78 | CpuAddr 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | class CaptureInfo : public EffectInfoBase { | ||
| 15 | public: | ||
| 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info, | ||
| 9 | const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {} | ||
| 10 | |||
| 11 | void 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 | |||
| 25 | void 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 | |||
| 36 | CpuAddr 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | |||
| 15 | class CompressorInfo : public EffectInfoBase { | ||
| 16 | public: | ||
| 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void 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 | |||
| 41 | void 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 | |||
| 74 | void 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 | |||
| 85 | void DelayInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 86 | |||
| 87 | void DelayInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} | ||
| 88 | |||
| 89 | CpuAddr 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 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | |||
| 16 | class DelayInfo : public EffectInfoBase { | ||
| 17 | public: | ||
| 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void 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 | |||
| 19 | EffectInfoBase& EffectContext::GetInfo(const u32 index) { | ||
| 20 | return effect_infos[index]; | ||
| 21 | } | ||
| 22 | |||
| 23 | EffectResultState& EffectContext::GetResultState(const u32 index) { | ||
| 24 | return result_states_cpu[index]; | ||
| 25 | } | ||
| 26 | |||
| 27 | EffectResultState& EffectContext::GetDspSharedResultState(const u32 index) { | ||
| 28 | return result_states_dsp[index]; | ||
| 29 | } | ||
| 30 | |||
| 31 | u32 EffectContext::GetCount() const { | ||
| 32 | return effect_count; | ||
| 33 | } | ||
| 34 | |||
| 35 | void 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | |||
| 14 | class EffectContext { | ||
| 15 | public: | ||
| 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 | |||
| 60 | private: | ||
| 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 | |||
| 15 | namespace 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 | */ | ||
| 20 | class EffectInfoBase { | ||
| 21 | public: | ||
| 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 | |||
| 406 | protected: | ||
| 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 | |||
| 17 | namespace 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 | */ | ||
| 24 | static 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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | struct 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void 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 | |||
| 41 | void 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 | |||
| 74 | void 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 | |||
| 85 | void I3dl2ReverbInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 86 | |||
| 87 | void I3dl2ReverbInfo::UpdateResultState(EffectResultState& cpu_state, | ||
| 88 | EffectResultState& dsp_state) {} | ||
| 89 | |||
| 90 | CpuAddr 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 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | |||
| 16 | class I3dl2ReverbInfo : public EffectInfoBase { | ||
| 17 | public: | ||
| 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void 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 | |||
| 29 | void 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 | |||
| 50 | void 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 | |||
| 62 | void 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 | |||
| 69 | void 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 | |||
| 77 | CpuAddr 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 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | |||
| 16 | class LightLimiterInfo : public EffectInfoBase { | ||
| 17 | public: | ||
| 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void 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 | |||
| 41 | void 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 | |||
| 74 | void 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 | |||
| 85 | void ReverbInfo::InitializeResultState(EffectResultState& result_state) {} | ||
| 86 | |||
| 87 | void ReverbInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} | ||
| 88 | |||
| 89 | CpuAddr 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 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | |||
| 16 | class ReverbInfo : public EffectInfoBase { | ||
| 17 | public: | ||
| 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | /** | ||
| 12 | * Represents a region of mapped or unmapped memory. | ||
| 13 | */ | ||
| 14 | class AddressInfo { | ||
| 15 | public: | ||
| 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 | |||
| 114 | private: | ||
| 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | CpuAddr MemoryPoolInfo::GetCpuAddress() const { | ||
| 9 | return cpu_address; | ||
| 10 | } | ||
| 11 | |||
| 12 | CpuAddr MemoryPoolInfo::GetDspAddress() const { | ||
| 13 | return dsp_address; | ||
| 14 | } | ||
| 15 | |||
| 16 | u64 MemoryPoolInfo::GetSize() const { | ||
| 17 | return size; | ||
| 18 | } | ||
| 19 | |||
| 20 | MemoryPoolInfo::Location MemoryPoolInfo::GetLocation() const { | ||
| 21 | return location; | ||
| 22 | } | ||
| 23 | |||
| 24 | void MemoryPoolInfo::SetCpuAddress(const CpuAddr address, const u64 size_) { | ||
| 25 | cpu_address = address; | ||
| 26 | size = size_; | ||
| 27 | } | ||
| 28 | |||
| 29 | void MemoryPoolInfo::SetDspAddress(const CpuAddr address) { | ||
| 30 | dsp_address = address; | ||
| 31 | } | ||
| 32 | |||
| 33 | bool MemoryPoolInfo::Contains(const CpuAddr address_, const u64 size_) const { | ||
| 34 | return cpu_address <= address_ && (address_ + size_) <= (cpu_address + size); | ||
| 35 | } | ||
| 36 | |||
| 37 | bool MemoryPoolInfo::IsMapped() const { | ||
| 38 | return dsp_address != 0; | ||
| 39 | } | ||
| 40 | |||
| 41 | CpuAddr 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 | |||
| 53 | void MemoryPoolInfo::SetUsed(const bool used) { | ||
| 54 | in_use = used; | ||
| 55 | } | ||
| 56 | |||
| 57 | bool 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | /** | ||
| 13 | * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper). | ||
| 14 | */ | ||
| 15 | class MemoryPoolInfo { | ||
| 16 | public: | ||
| 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 | |||
| 157 | private: | ||
| 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | PoolMapper::PoolMapper(u32 process_handle_, bool force_map_) | ||
| 12 | : process_handle{process_handle_}, force_map{force_map_} {} | ||
| 13 | |||
| 14 | PoolMapper::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 | |||
| 19 | void 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 | |||
| 25 | MemoryPoolInfo* 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 | |||
| 36 | MemoryPoolInfo* 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 | |||
| 46 | bool 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 | |||
| 69 | bool 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 | |||
| 90 | bool 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 | |||
| 105 | bool PoolMapper::IsForceMapEnabled() const { | ||
| 106 | return force_map; | ||
| 107 | } | ||
| 108 | |||
| 109 | u32 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 | |||
| 120 | bool 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 | |||
| 126 | bool 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 | |||
| 143 | bool 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 | |||
| 149 | bool 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 | |||
| 166 | void 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 | |||
| 174 | MemoryPoolInfo::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 | |||
| 224 | bool 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | class AddressInfo; | ||
| 15 | |||
| 16 | /** | ||
| 17 | * Utility functions for managing MemoryPoolInfos | ||
| 18 | */ | ||
| 19 | class PoolMapper { | ||
| 20 | public: | ||
| 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 | |||
| 168 | private: | ||
| 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | void 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 | |||
| 32 | MixInfo* MixContext::GetSortedInfo(const s32 index) { | ||
| 33 | return sorted_mix_infos[index]; | ||
| 34 | } | ||
| 35 | |||
| 36 | void MixContext::SetSortedInfo(const s32 index, MixInfo& mix_info) { | ||
| 37 | sorted_mix_infos[index] = &mix_info; | ||
| 38 | } | ||
| 39 | |||
| 40 | MixInfo* MixContext::GetInfo(const s32 index) { | ||
| 41 | return &mix_infos[index]; | ||
| 42 | } | ||
| 43 | |||
| 44 | MixInfo* MixContext::GetFinalMixInfo() { | ||
| 45 | return &mix_infos[0]; | ||
| 46 | } | ||
| 47 | |||
| 48 | s32 MixContext::GetCount() const { | ||
| 49 | return count; | ||
| 50 | } | ||
| 51 | |||
| 52 | void 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 | |||
| 95 | void 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 | |||
| 105 | void 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 | |||
| 117 | bool 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 | |||
| 137 | EdgeMatrix& 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | class SplitterContext; | ||
| 15 | |||
| 16 | /* | ||
| 17 | * Manages mixing states, sorting and building a node graph to describe a mix order. | ||
| 18 | */ | ||
| 19 | class MixContext { | ||
| 20 | public: | ||
| 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 | |||
| 107 | private: | ||
| 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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | MixInfo::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 | |||
| 18 | void MixInfo::Cleanup() { | ||
| 19 | mix_id = UnusedMixId; | ||
| 20 | dst_mix_id = UnusedMixId; | ||
| 21 | dst_splitter_id = UnusedSplitterId; | ||
| 22 | } | ||
| 23 | |||
| 24 | void MixInfo::ClearEffectProcessingOrder() { | ||
| 25 | for (s32 i = 0; i < effect_count; i++) { | ||
| 26 | effect_order_buffer[i] = -1; | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | bool 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 | |||
| 70 | bool 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 | |||
| 116 | bool 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | class EdgeMatrix; | ||
| 14 | class SplitterContext; | ||
| 15 | class EffectContext; | ||
| 16 | class BehaviorInfo; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * A single mix, which may feed through other mixes in a chain until reaching the final output mix. | ||
| 20 | */ | ||
| 21 | class MixInfo { | ||
| 22 | public: | ||
| 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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | /** | ||
| 12 | * Represents an array of bits used for nodes and edges for the mixing graph. | ||
| 13 | */ | ||
| 14 | struct 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void 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 | |||
| 16 | bool EdgeMatrix::Connected(const u32 id, const u32 destination_id) const { | ||
| 17 | return edges.buffer[count * id + destination_id]; | ||
| 18 | } | ||
| 19 | |||
| 20 | void EdgeMatrix::Connect(const u32 id, const u32 destination_id) { | ||
| 21 | edges.buffer[count * id + destination_id] = true; | ||
| 22 | } | ||
| 23 | |||
| 24 | void EdgeMatrix::Disconnect(const u32 id, const u32 destination_id) { | ||
| 25 | edges.buffer[count * id + destination_id] = false; | ||
| 26 | } | ||
| 27 | |||
| 28 | void EdgeMatrix::RemoveEdges(const u32 id) { | ||
| 29 | for (u32 dest = 0; dest < count; dest++) { | ||
| 30 | Disconnect(id, dest); | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | u32 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | /** | ||
| 14 | * An edge matrix, holding the connections for each node to every other node in the graph. | ||
| 15 | */ | ||
| 16 | class EdgeMatrix { | ||
| 17 | public: | ||
| 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 | |||
| 75 | private: | ||
| 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 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | void 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 | |||
| 39 | bool NodeStates::Tsort(const EdgeMatrix& edge_matrix) { | ||
| 40 | return DepthFirstSearch(edge_matrix, stack); | ||
| 41 | } | ||
| 42 | |||
| 43 | bool 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 | |||
| 93 | NodeStates::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 | |||
| 102 | void NodeStates::PushTsortResult(const u32 id) { | ||
| 103 | results[result_pos++] = id; | ||
| 104 | } | ||
| 105 | |||
| 106 | void 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 | |||
| 126 | void NodeStates::ResetState() { | ||
| 127 | nodes_found.reset(); | ||
| 128 | nodes_complete.reset(); | ||
| 129 | std::fill(results.begin(), results.end(), -1); | ||
| 130 | result_pos = 0; | ||
| 131 | } | ||
| 132 | |||
| 133 | u32 NodeStates::GetNodeCount() const { | ||
| 134 | return node_count; | ||
| 135 | } | ||
| 136 | |||
| 137 | std::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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | /** | ||
| 15 | * Graph utility functions for sorting and getting results from the DAG. | ||
| 16 | */ | ||
| 17 | class 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 | |||
| 100 | public: | ||
| 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 | |||
| 180 | private: | ||
| 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 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | DetailAspect::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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | class CommandGenerator; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Holds detailed information about performance metrics, filled in by the AudioRenderer during | ||
| 15 | * Performance commands. | ||
| 16 | */ | ||
| 17 | class DetailAspect { | ||
| 18 | public: | ||
| 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 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | EntryAspect::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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | class CommandGenerator; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Holds entry information about performance metrics, filled in by the AudioRenderer during | ||
| 15 | * Performance commands. | ||
| 16 | */ | ||
| 17 | class EntryAspect { | ||
| 18 | public: | ||
| 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | enum 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 | |||
| 28 | struct 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 | }; | ||
| 35 | static_assert(sizeof(PerformanceDetailVersion1) == 0x10, | ||
| 36 | "PerformanceDetailVersion1 has the worng size!"); | ||
| 37 | |||
| 38 | struct 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 | }; | ||
| 47 | static_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 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | enum class PerformanceEntryType : u8 { | ||
| 11 | Invalid, | ||
| 12 | Voice, | ||
| 13 | SubMix, | ||
| 14 | FinalMix, | ||
| 15 | Sink, | ||
| 16 | }; | ||
| 17 | |||
| 18 | struct PerformanceEntryVersion1 { | ||
| 19 | /* 0x00 */ u32 node_id; | ||
| 20 | /* 0x04 */ u32 start_time; | ||
| 21 | /* 0x08 */ u32 processed_time; | ||
| 22 | /* 0x0C */ PerformanceEntryType entry_type; | ||
| 23 | }; | ||
| 24 | static_assert(sizeof(PerformanceEntryVersion1) == 0x10, | ||
| 25 | "PerformanceEntryVersion1 has the worng size!"); | ||
| 26 | |||
| 27 | struct 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 | }; | ||
| 34 | static_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 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | struct 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 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | struct 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 | }; | ||
| 18 | static_assert(sizeof(PerformanceFrameHeaderVersion1) == 0x18, | ||
| 19 | "PerformanceFrameHeaderVersion1 has the worng size!"); | ||
| 20 | |||
| 21 | struct 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 | }; | ||
| 33 | static_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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | void 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 | |||
| 32 | void 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 | |||
| 40 | bool PerformanceManager::IsInitialized() const { | ||
| 41 | if (impl) { | ||
| 42 | return impl->IsInitialized(); | ||
| 43 | } | ||
| 44 | return false; | ||
| 45 | } | ||
| 46 | |||
| 47 | u32 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 | |||
| 54 | bool 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 | |||
| 63 | bool 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 | |||
| 71 | bool 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 | |||
| 80 | void 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 | |||
| 87 | bool PerformanceManager::IsDetailTarget(const u32 target_node_id) const { | ||
| 88 | if (impl) { | ||
| 89 | return impl->IsDetailTarget(target_node_id); | ||
| 90 | } | ||
| 91 | return false; | ||
| 92 | } | ||
| 93 | |||
| 94 | void PerformanceManager::SetDetailTarget(const u32 target_node_id) { | ||
| 95 | if (impl) { | ||
| 96 | impl->SetDetailTarget(target_node_id); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | template <> | ||
| 101 | void 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 | |||
| 154 | template <> | ||
| 155 | bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, | ||
| 156 | PerformanceEntryVersion1, PerformanceDetailVersion1>::IsInitialized() | ||
| 157 | const { | ||
| 158 | return is_initialized; | ||
| 159 | } | ||
| 160 | |||
| 161 | template <> | ||
| 162 | u32 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 | |||
| 256 | template <> | ||
| 257 | bool 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 | |||
| 265 | template <> | ||
| 266 | bool 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 | |||
| 291 | template <> | ||
| 292 | bool 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 | |||
| 319 | template <> | ||
| 320 | void 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 | |||
| 345 | template <> | ||
| 346 | bool 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 | |||
| 352 | template <> | ||
| 353 | void PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, | ||
| 354 | PerformanceEntryVersion1, | ||
| 355 | PerformanceDetailVersion1>::SetDetailTarget(const u32 target_node_id_) { | ||
| 356 | target_node_id = target_node_id_; | ||
| 357 | } | ||
| 358 | |||
| 359 | template <> | ||
| 360 | void 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 | |||
| 413 | template <> | ||
| 414 | bool PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, | ||
| 415 | PerformanceEntryVersion2, PerformanceDetailVersion2>::IsInitialized() | ||
| 416 | const { | ||
| 417 | return is_initialized; | ||
| 418 | } | ||
| 419 | |||
| 420 | template <> | ||
| 421 | u32 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 | |||
| 518 | template <> | ||
| 519 | bool 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 | |||
| 548 | template <> | ||
| 549 | bool 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 | |||
| 574 | template <> | ||
| 575 | bool 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 | |||
| 602 | template <> | ||
| 603 | void 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 | |||
| 631 | template <> | ||
| 632 | bool 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 | |||
| 638 | template <> | ||
| 639 | void 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 | |||
| 17 | namespace AudioCore::AudioRenderer { | ||
| 18 | class BehaviorInfo; | ||
| 19 | class MemoryPoolInfo; | ||
| 20 | |||
| 21 | enum class PerformanceVersion { | ||
| 22 | Version1, | ||
| 23 | Version2, | ||
| 24 | }; | ||
| 25 | |||
| 26 | enum class PerformanceSysDetailType { | ||
| 27 | PcmInt16 = 15, | ||
| 28 | PcmFloat = 16, | ||
| 29 | Adpcm = 17, | ||
| 30 | LightLimiter = 37, | ||
| 31 | }; | ||
| 32 | |||
| 33 | enum 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 | */ | ||
| 55 | class PerformanceManager { | ||
| 56 | public: | ||
| 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 | |||
| 196 | private: | ||
| 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 | |||
| 209 | template <PerformanceVersion Version, typename FrameHeaderVersion, typename EntryVersion, | ||
| 210 | typename DetailVersion> | ||
| 211 | class PerformanceManagerImpl : public PerformanceManager { | ||
| 212 | public: | ||
| 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 | |||
| 228 | private: | ||
| 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 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | CircularBufferSinkInfo::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 | |||
| 19 | void 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 | |||
| 31 | void 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 | |||
| 59 | void 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Info for a circular buffer sink. | ||
| 12 | */ | ||
| 13 | class CircularBufferSinkInfo : public SinkInfoBase { | ||
| 14 | public: | ||
| 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 | }; | ||
| 38 | static_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 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | DeviceSinkInfo::DeviceSinkInfo() { | ||
| 10 | state.fill(0); | ||
| 11 | parameter.fill(0); | ||
| 12 | type = Type::DeviceSink; | ||
| 13 | } | ||
| 14 | |||
| 15 | void 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 | |||
| 27 | void 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 | |||
| 55 | void 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Info for a device sink. | ||
| 12 | */ | ||
| 13 | class DeviceSinkInfo : public SinkInfoBase { | ||
| 14 | public: | ||
| 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 | }; | ||
| 38 | static_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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) { | ||
| 9 | sink_infos = sink_infos_; | ||
| 10 | sink_count = sink_count_; | ||
| 11 | } | ||
| 12 | |||
| 13 | SinkInfoBase* SinkContext::GetInfo(const u32 index) { | ||
| 14 | return &sink_infos[index]; | ||
| 15 | } | ||
| 16 | |||
| 17 | u32 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | /** | ||
| 13 | * Manages output sinks. | ||
| 14 | */ | ||
| 15 | class SinkContext { | ||
| 16 | public: | ||
| 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 | |||
| 40 | private: | ||
| 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 | |||
| 7 | namespace AudioCore::AudioRenderer { | ||
| 8 | |||
| 9 | void SinkInfoBase::CleanUp() { | ||
| 10 | type = Type::Invalid; | ||
| 11 | } | ||
| 12 | |||
| 13 | void 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 | |||
| 21 | void SinkInfoBase::UpdateForCommandGeneration() {} | ||
| 22 | |||
| 23 | SinkInfoBase::DeviceState* SinkInfoBase::GetDeviceState() { | ||
| 24 | return reinterpret_cast<DeviceState*>(state.data()); | ||
| 25 | } | ||
| 26 | |||
| 27 | SinkInfoBase::Type SinkInfoBase::GetType() const { | ||
| 28 | return type; | ||
| 29 | } | ||
| 30 | |||
| 31 | bool SinkInfoBase::IsUsed() const { | ||
| 32 | return in_use; | ||
| 33 | } | ||
| 34 | |||
| 35 | bool SinkInfoBase::ShouldSkip() const { | ||
| 36 | return buffer_unmapped; | ||
| 37 | } | ||
| 38 | |||
| 39 | u32 SinkInfoBase::GetNodeId() const { | ||
| 40 | return node_id; | ||
| 41 | } | ||
| 42 | |||
| 43 | u8* SinkInfoBase::GetState() { | ||
| 44 | return state.data(); | ||
| 45 | } | ||
| 46 | |||
| 47 | u8* 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 | |||
| 14 | namespace AudioCore::AudioRenderer { | ||
| 15 | struct UpsamplerInfo; | ||
| 16 | class 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 | */ | ||
| 22 | class SinkInfoBase { | ||
| 23 | public: | ||
| 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 | |||
| 161 | protected: | ||
| 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 | |||
| 10 | namespace AudioCore::AudioRenderer { | ||
| 11 | |||
| 12 | SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id, | ||
| 13 | const s32 destination_id) { | ||
| 14 | return splitter_infos[splitter_id].GetData(destination_id); | ||
| 15 | } | ||
| 16 | |||
| 17 | SplitterInfo& SplitterContext::GetInfo(const s32 splitter_id) { | ||
| 18 | return splitter_infos[splitter_id]; | ||
| 19 | } | ||
| 20 | |||
| 21 | u32 SplitterContext::GetDataCount() const { | ||
| 22 | return destinations_count; | ||
| 23 | } | ||
| 24 | |||
| 25 | u32 SplitterContext::GetInfoCount() const { | ||
| 26 | return info_count; | ||
| 27 | } | ||
| 28 | |||
| 29 | SplitterDestinationData& SplitterContext::GetData(const u32 index) { | ||
| 30 | return splitter_destinations[index]; | ||
| 31 | } | ||
| 32 | |||
| 33 | void 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 | |||
| 43 | bool SplitterContext::UsingSplitter() const { | ||
| 44 | return splitter_infos.size() > 0 && info_count > 0 && splitter_destinations != nullptr && | ||
| 45 | destinations_count > 0; | ||
| 46 | } | ||
| 47 | |||
| 48 | void SplitterContext::ClearAllNewConnectionFlag() { | ||
| 49 | for (s32 i = 0; i < info_count; i++) { | ||
| 50 | splitter_infos[i].SetNewConnectionFlag(); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | bool 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 | |||
| 89 | bool 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 | |||
| 114 | u32 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 | |||
| 135 | u32 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 | |||
| 155 | void SplitterContext::UpdateInternalState() { | ||
| 156 | for (s32 i = 0; i < info_count; i++) { | ||
| 157 | splitter_infos[i].UpdateInternalState(); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | void 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 | |||
| 194 | u32 SplitterContext::GetDestCountPerInfoForCompat() const { | ||
| 195 | if (info_count <= 0) { | ||
| 196 | return 0; | ||
| 197 | } | ||
| 198 | return static_cast<u32>(destinations_count / info_count); | ||
| 199 | } | ||
| 200 | |||
| 201 | u64 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 | |||
| 12 | namespace AudioCore { | ||
| 13 | struct AudioRendererParameterInternal; | ||
| 14 | class WorkbufferAllocator; | ||
| 15 | |||
| 16 | namespace AudioRenderer { | ||
| 17 | class 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 | */ | ||
| 27 | class 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 | |||
| 37 | public: | ||
| 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 | |||
| 162 | private: | ||
| 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {} | ||
| 9 | |||
| 10 | void SplitterDestinationData::ClearMixVolume() { | ||
| 11 | mix_volumes.fill(0.0f); | ||
| 12 | prev_mix_volumes.fill(0.0f); | ||
| 13 | } | ||
| 14 | |||
| 15 | s32 SplitterDestinationData::GetId() const { | ||
| 16 | return id; | ||
| 17 | } | ||
| 18 | |||
| 19 | bool SplitterDestinationData::IsConfigured() const { | ||
| 20 | return in_use && destination_id != UnusedMixId; | ||
| 21 | } | ||
| 22 | |||
| 23 | s32 SplitterDestinationData::GetMixId() const { | ||
| 24 | return destination_id; | ||
| 25 | } | ||
| 26 | |||
| 27 | f32 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 | |||
| 35 | std::span<f32> SplitterDestinationData::GetMixVolume() { | ||
| 36 | return mix_volumes; | ||
| 37 | } | ||
| 38 | |||
| 39 | f32 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 | |||
| 48 | std::span<f32> SplitterDestinationData::GetMixVolumePrev() { | ||
| 49 | return prev_mix_volumes; | ||
| 50 | } | ||
| 51 | |||
| 52 | void 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 | |||
| 68 | void SplitterDestinationData::MarkAsNeedToUpdateInternalState() { | ||
| 69 | need_update = true; | ||
| 70 | } | ||
| 71 | |||
| 72 | void SplitterDestinationData::UpdateInternalState() { | ||
| 73 | if (in_use && need_update) { | ||
| 74 | prev_mix_volumes = mix_volumes; | ||
| 75 | } | ||
| 76 | need_update = false; | ||
| 77 | } | ||
| 78 | |||
| 79 | SplitterDestinationData* SplitterDestinationData::GetNext() const { | ||
| 80 | return next; | ||
| 81 | } | ||
| 82 | |||
| 83 | void 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 | |||
| 12 | namespace 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 | */ | ||
| 17 | class SplitterDestinationData { | ||
| 18 | public: | ||
| 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 | |||
| 118 | private: | ||
| 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {} | ||
| 9 | |||
| 10 | void 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 | |||
| 23 | u32 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 | |||
| 33 | SplitterDestinationData* 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 | |||
| 47 | u32 SplitterInfo::GetDestinationCount() const { | ||
| 48 | return destination_count; | ||
| 49 | } | ||
| 50 | |||
| 51 | void SplitterInfo::SetDestinationCount(const u32 count) { | ||
| 52 | destination_count = count; | ||
| 53 | } | ||
| 54 | |||
| 55 | bool SplitterInfo::HasNewConnection() const { | ||
| 56 | return has_new_connection; | ||
| 57 | } | ||
| 58 | |||
| 59 | void SplitterInfo::ClearNewConnectionFlag() { | ||
| 60 | has_new_connection = false; | ||
| 61 | } | ||
| 62 | |||
| 63 | void SplitterInfo::SetNewConnectionFlag() { | ||
| 64 | has_new_connection = true; | ||
| 65 | } | ||
| 66 | |||
| 67 | void SplitterInfo::UpdateInternalState() { | ||
| 68 | auto destination{destinations}; | ||
| 69 | while (destination != nullptr) { | ||
| 70 | destination->UpdateInternalState(); | ||
| 71 | destination = destination->GetNext(); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | void 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | /** | ||
| 11 | * Represents a splitter, wraps multiple output destinations to split an input mix into. | ||
| 12 | */ | ||
| 13 | class SplitterInfo { | ||
| 14 | public: | ||
| 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 | |||
| 92 | private: | ||
| 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 | |||
| 37 | namespace AudioCore::AudioRenderer { | ||
| 38 | |||
| 39 | u64 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 | |||
| 97 | System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_) | ||
| 98 | : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {} | ||
| 99 | |||
| 100 | Result 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 | |||
| 396 | void 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 | |||
| 424 | void System::Start() { | ||
| 425 | std::scoped_lock l{lock}; | ||
| 426 | frames_elapsed = 0; | ||
| 427 | state = State::Started; | ||
| 428 | active = true; | ||
| 429 | } | ||
| 430 | |||
| 431 | void 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 | |||
| 446 | Result 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 | |||
| 546 | u32 System::GetRenderingTimeLimit() const { | ||
| 547 | return render_time_limit_percent; | ||
| 548 | } | ||
| 549 | |||
| 550 | void System::SetRenderingTimeLimit(const u32 limit) { | ||
| 551 | render_time_limit_percent = limit; | ||
| 552 | } | ||
| 553 | |||
| 554 | u32 System::GetSessionId() const { | ||
| 555 | return session_id; | ||
| 556 | } | ||
| 557 | |||
| 558 | u32 System::GetSampleRate() const { | ||
| 559 | return sample_rate; | ||
| 560 | } | ||
| 561 | |||
| 562 | u32 System::GetSampleCount() const { | ||
| 563 | return sample_count; | ||
| 564 | } | ||
| 565 | |||
| 566 | u32 System::GetMixBufferCount() const { | ||
| 567 | return mix_buffer_count; | ||
| 568 | } | ||
| 569 | |||
| 570 | ExecutionMode System::GetExecutionMode() const { | ||
| 571 | return execution_mode; | ||
| 572 | } | ||
| 573 | |||
| 574 | u32 System::GetRenderingDevice() const { | ||
| 575 | return render_device; | ||
| 576 | } | ||
| 577 | |||
| 578 | bool System::IsActive() const { | ||
| 579 | return active; | ||
| 580 | } | ||
| 581 | |||
| 582 | void 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 | |||
| 637 | u64 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 | |||
| 740 | u32 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 | |||
| 23 | namespace Core { | ||
| 24 | namespace Memory { | ||
| 25 | class Memory; | ||
| 26 | } | ||
| 27 | class System; | ||
| 28 | } // namespace Core | ||
| 29 | |||
| 30 | namespace Kernel { | ||
| 31 | class KEvent; | ||
| 32 | class KTransferMemory; | ||
| 33 | } // namespace Kernel | ||
| 34 | |||
| 35 | namespace AudioCore { | ||
| 36 | struct AudioRendererParameterInternal; | ||
| 37 | |||
| 38 | namespace AudioRenderer { | ||
| 39 | class CommandBuffer; | ||
| 40 | namespace ADSP { | ||
| 41 | class ADSP; | ||
| 42 | } | ||
| 43 | |||
| 44 | /** | ||
| 45 | * Audio Renderer System, the main worker for audio rendering. | ||
| 46 | */ | ||
| 47 | class System { | ||
| 48 | enum class State { | ||
| 49 | Started = 0, | ||
| 50 | Stopped = 2, | ||
| 51 | }; | ||
| 52 | |||
| 53 | public: | ||
| 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 | |||
| 199 | private: | ||
| 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 | |||
| 14 | MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager", | ||
| 15 | MP_RGB(60, 19, 97)); | ||
| 16 | |||
| 17 | namespace AudioCore::AudioRenderer { | ||
| 18 | constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; | ||
| 19 | constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL}; | ||
| 20 | |||
| 21 | SystemManager::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 | |||
| 30 | SystemManager::~SystemManager() { | ||
| 31 | Stop(); | ||
| 32 | } | ||
| 33 | |||
| 34 | bool 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 | |||
| 47 | void 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 | |||
| 59 | bool 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 | |||
| 81 | bool 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 | |||
| 99 | void 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 | |||
| 123 | std::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 | |||
| 155 | void 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 | |||
| 14 | namespace Core { | ||
| 15 | namespace Timing { | ||
| 16 | struct EventType; | ||
| 17 | } | ||
| 18 | class System; | ||
| 19 | } // namespace Core | ||
| 20 | |||
| 21 | namespace AudioCore::AudioRenderer { | ||
| 22 | namespace ADSP { | ||
| 23 | class ADSP; | ||
| 24 | class AudioRenderer_Mailbox; | ||
| 25 | } // namespace ADSP | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Manages all audio renderers, responsible for triggering command list generation and signalling | ||
| 29 | * the ADSP. | ||
| 30 | */ | ||
| 31 | class SystemManager { | ||
| 32 | public: | ||
| 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 | |||
| 65 | private: | ||
| 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 | |||
| 6 | namespace AudioCore::AudioRenderer {} // namespace AudioCore::AudioRenderer | ||
diff --git a/src/audio_core/renderer/upsampler/upsampler_info.h b/src/audio_core/renderer/upsampler/upsampler_info.h new file mode 100644 index 000000000..a43c15af3 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_info.h | |||
| @@ -0,0 +1,35 @@ | |||
| 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | class UpsamplerManager; | ||
| 14 | |||
| 15 | /** | ||
| 16 | * Manages information needed to upsample a mix buffer. | ||
| 17 | */ | ||
| 18 | struct 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 | |||
| 6 | namespace AudioCore::AudioRenderer { | ||
| 7 | |||
| 8 | UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_, | ||
| 9 | std::span<s32> workbuffer_) | ||
| 10 | : count{count_}, upsampler_infos{infos_}, workbuffer{workbuffer_} {} | ||
| 11 | |||
| 12 | UpsamplerInfo* 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 | |||
| 39 | void 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 | |||
| 12 | namespace AudioCore::AudioRenderer { | ||
| 13 | /** | ||
| 14 | * Manages and has utility functions for upsampler infos. | ||
| 15 | */ | ||
| 16 | class UpsamplerManager { | ||
| 17 | public: | ||
| 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 | |||
| 34 | private: | ||
| 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | /** | ||
| 13 | * Upsampling state used by the AudioRenderer across calls. | ||
| 14 | */ | ||
| 15 | struct 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 | |||
| 11 | namespace AudioCore::AudioRenderer { | ||
| 12 | /** | ||
| 13 | * Represents one channel for mixing a voice. | ||
| 14 | */ | ||
| 15 | class VoiceChannelResource { | ||
| 16 | public: | ||
| 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 | |||
| 8 | namespace AudioCore::AudioRenderer { | ||
| 9 | |||
| 10 | VoiceState& 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 | |||
| 17 | VoiceChannelResource& 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 | |||
| 24 | void 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 | |||
| 38 | VoiceInfo* 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 | |||
| 45 | VoiceInfo& 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 | |||
| 52 | VoiceState& 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 | |||
| 59 | u32 VoiceContext::GetCount() const { | ||
| 60 | return voice_count; | ||
| 61 | } | ||
| 62 | |||
| 63 | u32 VoiceContext::GetActiveCount() const { | ||
| 64 | return active_count; | ||
| 65 | } | ||
| 66 | |||
| 67 | void VoiceContext::SetActiveCount(const u32 active_count_) { | ||
| 68 | active_count = active_count_; | ||
| 69 | } | ||
| 70 | |||
| 71 | void 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 | |||
| 82 | void 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 | |||
| 13 | namespace AudioCore::AudioRenderer { | ||
| 14 | /** | ||
| 15 | * Contains all voices, with utility functions for managing them. | ||
| 16 | */ | ||
| 17 | class VoiceContext { | ||
| 18 | public: | ||
| 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 | |||
| 109 | private: | ||
| 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 | |||
| 9 | namespace AudioCore::AudioRenderer { | ||
| 10 | |||
| 11 | VoiceInfo::VoiceInfo() { | ||
| 12 | Initialize(); | ||
| 13 | } | ||
| 14 | |||
| 15 | void 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 | |||
| 46 | bool 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 | |||
| 51 | void 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 | |||
| 105 | void 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 | |||
| 126 | void 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 | |||
| 143 | void 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 | |||
| 164 | void 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 | |||
| 260 | bool VoiceInfo::ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const { | ||
| 261 | return !wave_buffer_internal.sent_to_DSP || buffer_unmapped; | ||
| 262 | } | ||
| 263 | |||
| 264 | void 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 | |||
| 281 | bool VoiceInfo::ShouldSkip() const { | ||
| 282 | return !in_use || wave_buffer_count == 0 || data_unmapped || buffer_unmapped || voice_dropped; | ||
| 283 | } | ||
| 284 | |||
| 285 | bool VoiceInfo::HasAnyConnection() const { | ||
| 286 | return mix_id != UnusedMixId || splitter_id != UnusedSplitterId; | ||
| 287 | } | ||
| 288 | |||
| 289 | void 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 | |||
| 310 | bool 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 | |||
| 382 | bool 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 | |||
| 398 | void 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 | |||
| 15 | namespace AudioCore::AudioRenderer { | ||
| 16 | class PoolMapper; | ||
| 17 | class VoiceContext; | ||
| 18 | struct 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 | */ | ||
| 24 | class VoiceInfo { | ||
| 25 | public: | ||
| 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 | |||
| 12 | namespace 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 | */ | ||
| 17 | struct 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 | ||