another try
This commit is contained in:
@@ -1,118 +1,118 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/adsp.h"
|
||||
#include "audio_core/renderer/adsp/command_buffer.h"
|
||||
#include "audio_core/sink/sink.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
||||
ADSP::ADSP(Core::System& system_, Sink::Sink& sink_)
|
||||
: system{system_}, memory{system.Memory()}, sink{sink_} {}
|
||||
|
||||
ADSP::~ADSP() {
|
||||
ClearCommandBuffers();
|
||||
}
|
||||
|
||||
State ADSP::GetState() const {
|
||||
if (running) {
|
||||
return State::Started;
|
||||
}
|
||||
return State::Stopped;
|
||||
}
|
||||
|
||||
AudioRenderer_Mailbox* ADSP::GetRenderMailbox() {
|
||||
return &render_mailbox;
|
||||
}
|
||||
|
||||
void ADSP::ClearRemainCount(const u32 session_id) {
|
||||
render_mailbox.ClearRemainCount(session_id);
|
||||
}
|
||||
|
||||
u64 ADSP::GetSignalledTick() const {
|
||||
return render_mailbox.GetSignalledTick();
|
||||
}
|
||||
|
||||
u64 ADSP::GetTimeTaken() const {
|
||||
return render_mailbox.GetRenderTimeTaken();
|
||||
}
|
||||
|
||||
u64 ADSP::GetRenderTimeTaken(const u32 session_id) {
|
||||
return render_mailbox.GetCommandBuffer(session_id).render_time_taken;
|
||||
}
|
||||
|
||||
u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
|
||||
return render_mailbox.GetRemainCommandCount(session_id);
|
||||
}
|
||||
|
||||
void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) {
|
||||
render_mailbox.SetCommandBuffer(session_id, command_buffer);
|
||||
}
|
||||
|
||||
u64 ADSP::GetRenderingStartTick(const u32 session_id) {
|
||||
return render_mailbox.GetSignalledTick() +
|
||||
render_mailbox.GetCommandBuffer(session_id).render_time_taken;
|
||||
}
|
||||
|
||||
bool ADSP::Start() {
|
||||
if (running) {
|
||||
return running;
|
||||
}
|
||||
|
||||
running = true;
|
||||
systems_active++;
|
||||
audio_renderer = std::make_unique<AudioRenderer>(system);
|
||||
audio_renderer->Start(&render_mailbox);
|
||||
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK);
|
||||
if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
|
||||
LOG_ERROR(
|
||||
Service_Audio,
|
||||
"Host Audio Renderer -- Failed to receive initialize message response from ADSP!");
|
||||
}
|
||||
return running;
|
||||
}
|
||||
|
||||
void ADSP::Stop() {
|
||||
systems_active--;
|
||||
if (running && systems_active == 0) {
|
||||
{
|
||||
std::scoped_lock l{mailbox_lock};
|
||||
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown);
|
||||
if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) {
|
||||
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
|
||||
"message response from ADSP!");
|
||||
}
|
||||
}
|
||||
audio_renderer->Stop();
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ADSP::Signal() {
|
||||
const auto signalled_tick{system.CoreTiming().GetClockTicks()};
|
||||
render_mailbox.SetSignalledTick(signalled_tick);
|
||||
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render);
|
||||
}
|
||||
|
||||
void ADSP::Wait() {
|
||||
std::scoped_lock l{mailbox_lock};
|
||||
auto response{render_mailbox.HostWaitMessage()};
|
||||
if (response != RenderMessage::AudioRenderer_RenderResponse) {
|
||||
LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}",
|
||||
static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse),
|
||||
static_cast<u32>(response));
|
||||
}
|
||||
|
||||
ClearCommandBuffers();
|
||||
}
|
||||
|
||||
void ADSP::ClearCommandBuffers() {
|
||||
render_mailbox.ClearCommandBuffers();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer::ADSP
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/adsp.h"
|
||||
#include "audio_core/renderer/adsp/command_buffer.h"
|
||||
#include "audio_core/sink/sink.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
||||
ADSP::ADSP(Core::System& system_, Sink::Sink& sink_)
|
||||
: system{system_}, memory{system.Memory()}, sink{sink_} {}
|
||||
|
||||
ADSP::~ADSP() {
|
||||
ClearCommandBuffers();
|
||||
}
|
||||
|
||||
State ADSP::GetState() const {
|
||||
if (running) {
|
||||
return State::Started;
|
||||
}
|
||||
return State::Stopped;
|
||||
}
|
||||
|
||||
AudioRenderer_Mailbox* ADSP::GetRenderMailbox() {
|
||||
return &render_mailbox;
|
||||
}
|
||||
|
||||
void ADSP::ClearRemainCount(const u32 session_id) {
|
||||
render_mailbox.ClearRemainCount(session_id);
|
||||
}
|
||||
|
||||
u64 ADSP::GetSignalledTick() const {
|
||||
return render_mailbox.GetSignalledTick();
|
||||
}
|
||||
|
||||
u64 ADSP::GetTimeTaken() const {
|
||||
return render_mailbox.GetRenderTimeTaken();
|
||||
}
|
||||
|
||||
u64 ADSP::GetRenderTimeTaken(const u32 session_id) {
|
||||
return render_mailbox.GetCommandBuffer(session_id).render_time_taken;
|
||||
}
|
||||
|
||||
u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
|
||||
return render_mailbox.GetRemainCommandCount(session_id);
|
||||
}
|
||||
|
||||
void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) {
|
||||
render_mailbox.SetCommandBuffer(session_id, command_buffer);
|
||||
}
|
||||
|
||||
u64 ADSP::GetRenderingStartTick(const u32 session_id) {
|
||||
return render_mailbox.GetSignalledTick() +
|
||||
render_mailbox.GetCommandBuffer(session_id).render_time_taken;
|
||||
}
|
||||
|
||||
bool ADSP::Start() {
|
||||
if (running) {
|
||||
return running;
|
||||
}
|
||||
|
||||
running = true;
|
||||
systems_active++;
|
||||
audio_renderer = std::make_unique<AudioRenderer>(system);
|
||||
audio_renderer->Start(&render_mailbox);
|
||||
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK);
|
||||
if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
|
||||
LOG_ERROR(
|
||||
Service_Audio,
|
||||
"Host Audio Renderer -- Failed to receive initialize message response from ADSP!");
|
||||
}
|
||||
return running;
|
||||
}
|
||||
|
||||
void ADSP::Stop() {
|
||||
systems_active--;
|
||||
if (running && systems_active == 0) {
|
||||
{
|
||||
std::scoped_lock l{mailbox_lock};
|
||||
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown);
|
||||
if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) {
|
||||
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
|
||||
"message response from ADSP!");
|
||||
}
|
||||
}
|
||||
audio_renderer->Stop();
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ADSP::Signal() {
|
||||
const auto signalled_tick{system.CoreTiming().GetClockTicks()};
|
||||
render_mailbox.SetSignalledTick(signalled_tick);
|
||||
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render);
|
||||
}
|
||||
|
||||
void ADSP::Wait() {
|
||||
std::scoped_lock l{mailbox_lock};
|
||||
auto response{render_mailbox.HostWaitMessage()};
|
||||
if (response != RenderMessage::AudioRenderer_RenderResponse) {
|
||||
LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}",
|
||||
static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse),
|
||||
static_cast<u32>(response));
|
||||
}
|
||||
|
||||
ClearCommandBuffers();
|
||||
}
|
||||
|
||||
void ADSP::ClearCommandBuffers() {
|
||||
render_mailbox.ClearCommandBuffers();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer::ADSP
|
||||
|
||||
@@ -1,171 +1,171 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "audio_core/renderer/adsp/audio_renderer.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
namespace Memory {
|
||||
class Memory;
|
||||
}
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace AudioCore {
|
||||
namespace Sink {
|
||||
class Sink;
|
||||
}
|
||||
|
||||
namespace AudioRenderer::ADSP {
|
||||
struct CommandBuffer;
|
||||
|
||||
enum class State {
|
||||
Started,
|
||||
Stopped,
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the ADSP embedded within the audio sysmodule.
|
||||
* This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot.
|
||||
*
|
||||
* The kernel will run apps you program for it, Nintendo have the following:
|
||||
*
|
||||
* Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all
|
||||
* audio samples end up, and we skip it entirely, since we have very different backends and
|
||||
* mixing is implicitly handled by the OS (but also due to lack of research/simplicity).
|
||||
*
|
||||
* AudioRenderer - Receives command lists generated by the audio render
|
||||
* system, processes them, and sends the samples to Gmix.
|
||||
*
|
||||
* OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix.
|
||||
* Not much research done here, TODO if needed.
|
||||
*
|
||||
* We only implement the AudioRenderer for now.
|
||||
*
|
||||
* Communication for the apps is done through mailboxes, and some shared memory.
|
||||
*/
|
||||
class ADSP {
|
||||
public:
|
||||
explicit ADSP(Core::System& system, Sink::Sink& sink);
|
||||
~ADSP();
|
||||
|
||||
/**
|
||||
* Start the ADSP.
|
||||
*
|
||||
* @return True if started or already running, otherwise false.
|
||||
*/
|
||||
bool Start();
|
||||
|
||||
/**
|
||||
* Stop the ADSP.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
/**
|
||||
* Get the ADSP's state.
|
||||
*
|
||||
* @return Started or Stopped.
|
||||
*/
|
||||
State GetState() const;
|
||||
|
||||
/**
|
||||
* Get the AudioRenderer mailbox to communicate with it.
|
||||
*
|
||||
* @return The AudioRenderer mailbox.
|
||||
*/
|
||||
AudioRenderer_Mailbox* GetRenderMailbox();
|
||||
|
||||
/**
|
||||
* Get the tick the ADSP was signalled.
|
||||
*
|
||||
* @return The tick the ADSP was signalled.
|
||||
*/
|
||||
u64 GetSignalledTick() const;
|
||||
|
||||
/**
|
||||
* Get the total time it took for the ADSP to run the last command lists (both command lists).
|
||||
*
|
||||
* @return The tick the ADSP was signalled.
|
||||
*/
|
||||
u64 GetTimeTaken() const;
|
||||
|
||||
/**
|
||||
* Get the last time a given command list took to run.
|
||||
*
|
||||
* @param session_id - The session id to check (0 or 1).
|
||||
* @return The time it took.
|
||||
*/
|
||||
u64 GetRenderTimeTaken(u32 session_id);
|
||||
|
||||
/**
|
||||
* Clear the remaining command count for a given session.
|
||||
*
|
||||
* @param session_id - The session id to check (0 or 1).
|
||||
*/
|
||||
void ClearRemainCount(u32 session_id);
|
||||
|
||||
/**
|
||||
* Get the remaining number of commands left to process for a command list.
|
||||
*
|
||||
* @param session_id - The session id to check (0 or 1).
|
||||
* @return The number of commands remaining.
|
||||
*/
|
||||
u32 GetRemainCommandCount(u32 session_id) const;
|
||||
|
||||
/**
|
||||
* Get the last tick a command list started processing.
|
||||
*
|
||||
* @param session_id - The session id to check (0 or 1).
|
||||
* @return The last tick the given command list started.
|
||||
*/
|
||||
u64 GetRenderingStartTick(u32 session_id);
|
||||
|
||||
/**
|
||||
* Set a command buffer to be processed.
|
||||
*
|
||||
* @param session_id - The session id to check (0 or 1).
|
||||
* @param command_buffer - The command buffer to process.
|
||||
*/
|
||||
void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer);
|
||||
|
||||
/**
|
||||
* Clear the command buffers (does not clear the time taken or the remaining command count)
|
||||
*/
|
||||
void ClearCommandBuffers();
|
||||
|
||||
/**
|
||||
* Signal the AudioRenderer to begin processing.
|
||||
*/
|
||||
void Signal();
|
||||
|
||||
/**
|
||||
* Wait for the AudioRenderer to finish processing.
|
||||
*/
|
||||
void Wait();
|
||||
|
||||
private:
|
||||
/// Core system
|
||||
Core::System& system;
|
||||
/// Core memory
|
||||
Core::Memory::Memory& memory;
|
||||
/// Number of systems active, used to prevent accidental shutdowns
|
||||
u8 systems_active{0};
|
||||
/// ADSP running state
|
||||
std::atomic<bool> running{false};
|
||||
/// Output sink used by the ADSP
|
||||
Sink::Sink& sink;
|
||||
/// AudioRenderer app
|
||||
std::unique_ptr<AudioRenderer> audio_renderer{};
|
||||
/// Communication for the AudioRenderer
|
||||
AudioRenderer_Mailbox render_mailbox{};
|
||||
/// Mailbox lock ffor the render mailbox
|
||||
std::mutex mailbox_lock;
|
||||
};
|
||||
|
||||
} // namespace AudioRenderer::ADSP
|
||||
} // namespace AudioCore
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "audio_core/renderer/adsp/audio_renderer.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
namespace Memory {
|
||||
class Memory;
|
||||
}
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace AudioCore {
|
||||
namespace Sink {
|
||||
class Sink;
|
||||
}
|
||||
|
||||
namespace AudioRenderer::ADSP {
|
||||
struct CommandBuffer;
|
||||
|
||||
enum class State {
|
||||
Started,
|
||||
Stopped,
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the ADSP embedded within the audio sysmodule.
|
||||
* This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot.
|
||||
*
|
||||
* The kernel will run apps you program for it, Nintendo have the following:
|
||||
*
|
||||
* Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all
|
||||
* audio samples end up, and we skip it entirely, since we have very different backends and
|
||||
* mixing is implicitly handled by the OS (but also due to lack of research/simplicity).
|
||||
*
|
||||
* AudioRenderer - Receives command lists generated by the audio render
|
||||
* system, processes them, and sends the samples to Gmix.
|
||||
*
|
||||
* OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix.
|
||||
* Not much research done here, TODO if needed.
|
||||
*
|
||||
* We only implement the AudioRenderer for now.
|
||||
*
|
||||
* Communication for the apps is done through mailboxes, and some shared memory.
|
||||
*/
|
||||
class ADSP {
|
||||
public:
|
||||
explicit ADSP(Core::System& system, Sink::Sink& sink);
|
||||
~ADSP();
|
||||
|
||||
/**
|
||||
* Start the ADSP.
|
||||
*
|
||||
* @return True if started or already running, otherwise false.
|
||||
*/
|
||||
bool Start();
|
||||
|
||||
/**
|
||||
* Stop the ADSP.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
/**
|
||||
* Get the ADSP's state.
|
||||
*
|
||||
* @return Started or Stopped.
|
||||
*/
|
||||
State GetState() const;
|
||||
|
||||
/**
|
||||
* Get the AudioRenderer mailbox to communicate with it.
|
||||
*
|
||||
* @return The AudioRenderer mailbox.
|
||||
*/
|
||||
AudioRenderer_Mailbox* GetRenderMailbox();
|
||||
|
||||
/**
|
||||
* Get the tick the ADSP was signalled.
|
||||
*
|
||||
* @return The tick the ADSP was signalled.
|
||||
*/
|
||||
u64 GetSignalledTick() const;
|
||||
|
||||
/**
|
||||
* Get the total time it took for the ADSP to run the last command lists (both command lists).
|
||||
*
|
||||
* @return The tick the ADSP was signalled.
|
||||
*/
|
||||
u64 GetTimeTaken() const;
|
||||
|
||||
/**
|
||||
* Get the last time a given command list took to run.
|
||||
*
|
||||
* @param session_id - The session id to check (0 or 1).
|
||||
* @return The time it took.
|
||||
*/
|
||||
u64 GetRenderTimeTaken(u32 session_id);
|
||||
|
||||
/**
|
||||
* Clear the remaining command count for a given session.
|
||||
*
|
||||
* @param session_id - The session id to check (0 or 1).
|
||||
*/
|
||||
void ClearRemainCount(u32 session_id);
|
||||
|
||||
/**
|
||||
* Get the remaining number of commands left to process for a command list.
|
||||
*
|
||||
* @param session_id - The session id to check (0 or 1).
|
||||
* @return The number of commands remaining.
|
||||
*/
|
||||
u32 GetRemainCommandCount(u32 session_id) const;
|
||||
|
||||
/**
|
||||
* Get the last tick a command list started processing.
|
||||
*
|
||||
* @param session_id - The session id to check (0 or 1).
|
||||
* @return The last tick the given command list started.
|
||||
*/
|
||||
u64 GetRenderingStartTick(u32 session_id);
|
||||
|
||||
/**
|
||||
* Set a command buffer to be processed.
|
||||
*
|
||||
* @param session_id - The session id to check (0 or 1).
|
||||
* @param command_buffer - The command buffer to process.
|
||||
*/
|
||||
void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer);
|
||||
|
||||
/**
|
||||
* Clear the command buffers (does not clear the time taken or the remaining command count)
|
||||
*/
|
||||
void ClearCommandBuffers();
|
||||
|
||||
/**
|
||||
* Signal the AudioRenderer to begin processing.
|
||||
*/
|
||||
void Signal();
|
||||
|
||||
/**
|
||||
* Wait for the AudioRenderer to finish processing.
|
||||
*/
|
||||
void Wait();
|
||||
|
||||
private:
|
||||
/// Core system
|
||||
Core::System& system;
|
||||
/// Core memory
|
||||
Core::Memory::Memory& memory;
|
||||
/// Number of systems active, used to prevent accidental shutdowns
|
||||
u8 systems_active{0};
|
||||
/// ADSP running state
|
||||
std::atomic<bool> running{false};
|
||||
/// Output sink used by the ADSP
|
||||
Sink::Sink& sink;
|
||||
/// AudioRenderer app
|
||||
std::unique_ptr<AudioRenderer> audio_renderer{};
|
||||
/// Communication for the AudioRenderer
|
||||
AudioRenderer_Mailbox render_mailbox{};
|
||||
/// Mailbox lock ffor the render mailbox
|
||||
std::mutex mailbox_lock;
|
||||
};
|
||||
|
||||
} // namespace AudioRenderer::ADSP
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -1,219 +1,219 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/adsp/audio_renderer.h"
|
||||
#include "audio_core/sink/sink.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
||||
void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) {
|
||||
adsp_messages.enqueue(message_);
|
||||
adsp_event.Set();
|
||||
}
|
||||
|
||||
RenderMessage AudioRenderer_Mailbox::HostWaitMessage() {
|
||||
host_event.Wait();
|
||||
RenderMessage msg{RenderMessage::Invalid};
|
||||
if (!host_messages.try_dequeue(msg)) {
|
||||
LOG_ERROR(Service_Audio, "Failed to dequeue host message!");
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) {
|
||||
host_messages.enqueue(message_);
|
||||
host_event.Set();
|
||||
}
|
||||
|
||||
RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
|
||||
adsp_event.Wait();
|
||||
RenderMessage msg{RenderMessage::Invalid};
|
||||
if (!adsp_messages.try_dequeue(msg)) {
|
||||
LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!");
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) {
|
||||
return command_buffers[session_id];
|
||||
}
|
||||
|
||||
void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
|
||||
command_buffers[session_id] = buffer;
|
||||
}
|
||||
|
||||
u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const {
|
||||
return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken;
|
||||
}
|
||||
|
||||
u64 AudioRenderer_Mailbox::GetSignalledTick() const {
|
||||
return signalled_tick;
|
||||
}
|
||||
|
||||
void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) {
|
||||
signalled_tick = tick;
|
||||
}
|
||||
|
||||
void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) {
|
||||
command_buffers[session_id].remaining_command_count = 0;
|
||||
}
|
||||
|
||||
u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const {
|
||||
return command_buffers[session_id].remaining_command_count;
|
||||
}
|
||||
|
||||
void AudioRenderer_Mailbox::ClearCommandBuffers() {
|
||||
command_buffers[0].buffer = 0;
|
||||
command_buffers[0].size = 0;
|
||||
command_buffers[0].reset_buffers = false;
|
||||
command_buffers[1].buffer = 0;
|
||||
command_buffers[1].size = 0;
|
||||
command_buffers[1].reset_buffers = false;
|
||||
}
|
||||
|
||||
AudioRenderer::AudioRenderer(Core::System& system_)
|
||||
: system{system_}, sink{system.AudioCore().GetOutputSink()} {
|
||||
CreateSinkStreams();
|
||||
}
|
||||
|
||||
AudioRenderer::~AudioRenderer() {
|
||||
Stop();
|
||||
for (auto& stream : streams) {
|
||||
if (stream) {
|
||||
sink.CloseStream(stream);
|
||||
}
|
||||
stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
|
||||
mailbox = mailbox_;
|
||||
thread = std::thread(&AudioRenderer::ThreadFunc, this);
|
||||
running = true;
|
||||
}
|
||||
|
||||
void AudioRenderer::Stop() {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& stream : streams) {
|
||||
stream->Stop();
|
||||
}
|
||||
thread.join();
|
||||
running = false;
|
||||
}
|
||||
|
||||
void AudioRenderer::CreateSinkStreams() {
|
||||
u32 channels{sink.GetDeviceChannels()};
|
||||
for (u32 i = 0; i < MaxRendererSessions; i++) {
|
||||
std::string name{fmt::format("ADSP_RenderStream-{}", i)};
|
||||
streams[i] =
|
||||
sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
|
||||
streams[i]->SetRingSize(4);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioRenderer::ThreadFunc() {
|
||||
constexpr char name[]{"AudioRenderer"};
|
||||
MicroProfileOnThreadCreate(name);
|
||||
Common::SetCurrentThreadName(name);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"ADSP Audio Renderer -- Failed to receive initialize message from host!");
|
||||
return;
|
||||
}
|
||||
|
||||
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
|
||||
|
||||
constexpr u64 max_process_time{2'304'000ULL};
|
||||
|
||||
while (true) {
|
||||
auto message{mailbox->ADSPWaitMessage()};
|
||||
switch (message) {
|
||||
case RenderMessage::AudioRenderer_Shutdown:
|
||||
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown);
|
||||
return;
|
||||
|
||||
case RenderMessage::AudioRenderer_Render: {
|
||||
std::array<bool, MaxRendererSessions> buffers_reset{};
|
||||
std::array<u64, MaxRendererSessions> render_times_taken{};
|
||||
const auto start_time{system.CoreTiming().GetClockTicks()};
|
||||
|
||||
for (u32 index = 0; index < 2; index++) {
|
||||
auto& command_buffer{mailbox->GetCommandBuffer(index)};
|
||||
auto& command_list_processor{command_list_processors[index]};
|
||||
|
||||
// Check this buffer is valid, as it may not be used.
|
||||
if (command_buffer.buffer != 0) {
|
||||
// If there are no remaining commands (from the previous list),
|
||||
// this is a new command list, initalize it.
|
||||
if (command_buffer.remaining_command_count == 0) {
|
||||
command_list_processor.Initialize(system, command_buffer.buffer,
|
||||
command_buffer.size, streams[index]);
|
||||
}
|
||||
|
||||
if (command_buffer.reset_buffers && !buffers_reset[index]) {
|
||||
streams[index]->ClearQueue();
|
||||
buffers_reset[index] = true;
|
||||
}
|
||||
|
||||
u64 max_time{max_process_time};
|
||||
if (index == 1 && command_buffer.applet_resource_user_id ==
|
||||
mailbox->GetCommandBuffer(0).applet_resource_user_id) {
|
||||
max_time = max_process_time -
|
||||
Core::Timing::CyclesToNs(render_times_taken[0]).count();
|
||||
if (render_times_taken[0] > max_process_time) {
|
||||
max_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
max_time = std::min(command_buffer.time_limit, max_time);
|
||||
command_list_processor.SetProcessTimeMax(max_time);
|
||||
|
||||
// Process the command list
|
||||
{
|
||||
MICROPROFILE_SCOPE(Audio_Renderer);
|
||||
render_times_taken[index] =
|
||||
command_list_processor.Process(index) - start_time;
|
||||
}
|
||||
|
||||
const auto end_time{system.CoreTiming().GetClockTicks()};
|
||||
|
||||
command_buffer.remaining_command_count =
|
||||
command_list_processor.GetRemainingCommandCount();
|
||||
command_buffer.render_time_taken = end_time - start_time;
|
||||
}
|
||||
}
|
||||
|
||||
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG_WARNING(Service_Audio,
|
||||
"ADSP AudioRenderer received an invalid message, msg={:02X}!",
|
||||
static_cast<u32>(message));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer::ADSP
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/adsp/audio_renderer.h"
|
||||
#include "audio_core/sink/sink.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
||||
void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) {
|
||||
adsp_messages.enqueue(message_);
|
||||
adsp_event.Set();
|
||||
}
|
||||
|
||||
RenderMessage AudioRenderer_Mailbox::HostWaitMessage() {
|
||||
host_event.Wait();
|
||||
RenderMessage msg{RenderMessage::Invalid};
|
||||
if (!host_messages.try_dequeue(msg)) {
|
||||
LOG_ERROR(Service_Audio, "Failed to dequeue host message!");
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) {
|
||||
host_messages.enqueue(message_);
|
||||
host_event.Set();
|
||||
}
|
||||
|
||||
RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
|
||||
adsp_event.Wait();
|
||||
RenderMessage msg{RenderMessage::Invalid};
|
||||
if (!adsp_messages.try_dequeue(msg)) {
|
||||
LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!");
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) {
|
||||
return command_buffers[session_id];
|
||||
}
|
||||
|
||||
void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
|
||||
command_buffers[session_id] = buffer;
|
||||
}
|
||||
|
||||
u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const {
|
||||
return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken;
|
||||
}
|
||||
|
||||
u64 AudioRenderer_Mailbox::GetSignalledTick() const {
|
||||
return signalled_tick;
|
||||
}
|
||||
|
||||
void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) {
|
||||
signalled_tick = tick;
|
||||
}
|
||||
|
||||
void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) {
|
||||
command_buffers[session_id].remaining_command_count = 0;
|
||||
}
|
||||
|
||||
u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const {
|
||||
return command_buffers[session_id].remaining_command_count;
|
||||
}
|
||||
|
||||
void AudioRenderer_Mailbox::ClearCommandBuffers() {
|
||||
command_buffers[0].buffer = 0;
|
||||
command_buffers[0].size = 0;
|
||||
command_buffers[0].reset_buffers = false;
|
||||
command_buffers[1].buffer = 0;
|
||||
command_buffers[1].size = 0;
|
||||
command_buffers[1].reset_buffers = false;
|
||||
}
|
||||
|
||||
AudioRenderer::AudioRenderer(Core::System& system_)
|
||||
: system{system_}, sink{system.AudioCore().GetOutputSink()} {
|
||||
CreateSinkStreams();
|
||||
}
|
||||
|
||||
AudioRenderer::~AudioRenderer() {
|
||||
Stop();
|
||||
for (auto& stream : streams) {
|
||||
if (stream) {
|
||||
sink.CloseStream(stream);
|
||||
}
|
||||
stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
|
||||
mailbox = mailbox_;
|
||||
thread = std::thread(&AudioRenderer::ThreadFunc, this);
|
||||
running = true;
|
||||
}
|
||||
|
||||
void AudioRenderer::Stop() {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& stream : streams) {
|
||||
stream->Stop();
|
||||
}
|
||||
thread.join();
|
||||
running = false;
|
||||
}
|
||||
|
||||
void AudioRenderer::CreateSinkStreams() {
|
||||
u32 channels{sink.GetDeviceChannels()};
|
||||
for (u32 i = 0; i < MaxRendererSessions; i++) {
|
||||
std::string name{fmt::format("ADSP_RenderStream-{}", i)};
|
||||
streams[i] =
|
||||
sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
|
||||
streams[i]->SetRingSize(4);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioRenderer::ThreadFunc() {
|
||||
constexpr char name[]{"AudioRenderer"};
|
||||
MicroProfileOnThreadCreate(name);
|
||||
Common::SetCurrentThreadName(name);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"ADSP Audio Renderer -- Failed to receive initialize message from host!");
|
||||
return;
|
||||
}
|
||||
|
||||
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
|
||||
|
||||
constexpr u64 max_process_time{2'304'000ULL};
|
||||
|
||||
while (true) {
|
||||
auto message{mailbox->ADSPWaitMessage()};
|
||||
switch (message) {
|
||||
case RenderMessage::AudioRenderer_Shutdown:
|
||||
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown);
|
||||
return;
|
||||
|
||||
case RenderMessage::AudioRenderer_Render: {
|
||||
std::array<bool, MaxRendererSessions> buffers_reset{};
|
||||
std::array<u64, MaxRendererSessions> render_times_taken{};
|
||||
const auto start_time{system.CoreTiming().GetClockTicks()};
|
||||
|
||||
for (u32 index = 0; index < 2; index++) {
|
||||
auto& command_buffer{mailbox->GetCommandBuffer(index)};
|
||||
auto& command_list_processor{command_list_processors[index]};
|
||||
|
||||
// Check this buffer is valid, as it may not be used.
|
||||
if (command_buffer.buffer != 0) {
|
||||
// If there are no remaining commands (from the previous list),
|
||||
// this is a new command list, initalize it.
|
||||
if (command_buffer.remaining_command_count == 0) {
|
||||
command_list_processor.Initialize(system, command_buffer.buffer,
|
||||
command_buffer.size, streams[index]);
|
||||
}
|
||||
|
||||
if (command_buffer.reset_buffers && !buffers_reset[index]) {
|
||||
streams[index]->ClearQueue();
|
||||
buffers_reset[index] = true;
|
||||
}
|
||||
|
||||
u64 max_time{max_process_time};
|
||||
if (index == 1 && command_buffer.applet_resource_user_id ==
|
||||
mailbox->GetCommandBuffer(0).applet_resource_user_id) {
|
||||
max_time = max_process_time -
|
||||
Core::Timing::CyclesToNs(render_times_taken[0]).count();
|
||||
if (render_times_taken[0] > max_process_time) {
|
||||
max_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
max_time = std::min(command_buffer.time_limit, max_time);
|
||||
command_list_processor.SetProcessTimeMax(max_time);
|
||||
|
||||
// Process the command list
|
||||
{
|
||||
MICROPROFILE_SCOPE(Audio_Renderer);
|
||||
render_times_taken[index] =
|
||||
command_list_processor.Process(index) - start_time;
|
||||
}
|
||||
|
||||
const auto end_time{system.CoreTiming().GetClockTicks()};
|
||||
|
||||
command_buffer.remaining_command_count =
|
||||
command_list_processor.GetRemainingCommandCount();
|
||||
command_buffer.render_time_taken = end_time - start_time;
|
||||
}
|
||||
}
|
||||
|
||||
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG_WARNING(Service_Audio,
|
||||
"ADSP AudioRenderer received an invalid message, msg={:02X}!",
|
||||
static_cast<u32>(message));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer::ADSP
|
||||
|
||||
@@ -1,203 +1,203 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_buffer.h"
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/reader_writer_queue.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
namespace Core {
|
||||
namespace Timing {
|
||||
struct EventType;
|
||||
}
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace AudioCore {
|
||||
namespace Sink {
|
||||
class Sink;
|
||||
}
|
||||
|
||||
namespace AudioRenderer::ADSP {
|
||||
|
||||
enum class RenderMessage {
|
||||
/* 0x00 */ Invalid,
|
||||
/* 0x01 */ AudioRenderer_MapUnmap_Map,
|
||||
/* 0x02 */ AudioRenderer_MapUnmap_MapResponse,
|
||||
/* 0x03 */ AudioRenderer_MapUnmap_Unmap,
|
||||
/* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse,
|
||||
/* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache,
|
||||
/* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse,
|
||||
/* 0x07 */ AudioRenderer_MapUnmap_Shutdown,
|
||||
/* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse,
|
||||
/* 0x16 */ AudioRenderer_InitializeOK = 0x16,
|
||||
/* 0x20 */ AudioRenderer_RenderResponse = 0x20,
|
||||
/* 0x2A */ AudioRenderer_Render = 0x2A,
|
||||
/* 0x34 */ AudioRenderer_Shutdown = 0x34,
|
||||
};
|
||||
|
||||
/**
|
||||
* A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer
|
||||
* running on the ADSP.
|
||||
*/
|
||||
class AudioRenderer_Mailbox {
|
||||
public:
|
||||
/**
|
||||
* Send a message from the host to the AudioRenderer.
|
||||
*
|
||||
* @param message - The message to send to the AudioRenderer.
|
||||
*/
|
||||
void HostSendMessage(RenderMessage message);
|
||||
|
||||
/**
|
||||
* Host wait for a message from the AudioRenderer.
|
||||
*
|
||||
* @return The message returned from the AudioRenderer.
|
||||
*/
|
||||
RenderMessage HostWaitMessage();
|
||||
|
||||
/**
|
||||
* Send a message from the AudioRenderer to the host.
|
||||
*
|
||||
* @param message - The message to send to the host.
|
||||
*/
|
||||
void ADSPSendMessage(RenderMessage message);
|
||||
|
||||
/**
|
||||
* AudioRenderer wait for a message from the host.
|
||||
*
|
||||
* @return The message returned from the AudioRenderer.
|
||||
*/
|
||||
RenderMessage ADSPWaitMessage();
|
||||
|
||||
/**
|
||||
* Get the command buffer with the given session id (0 or 1).
|
||||
*
|
||||
* @param session_id - The session id to get (0 or 1).
|
||||
* @return The command buffer.
|
||||
*/
|
||||
CommandBuffer& GetCommandBuffer(u32 session_id);
|
||||
|
||||
/**
|
||||
* Set the command buffer with the given session id (0 or 1).
|
||||
*
|
||||
* @param session_id - The session id to get (0 or 1).
|
||||
* @param buffer - The command buffer to set.
|
||||
*/
|
||||
void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer);
|
||||
|
||||
/**
|
||||
* Get the total render time taken for the last command lists sent.
|
||||
*
|
||||
* @return Total render time taken for the last command lists.
|
||||
*/
|
||||
u64 GetRenderTimeTaken() const;
|
||||
|
||||
/**
|
||||
* Get the tick the AudioRenderer was signalled.
|
||||
*
|
||||
* @return The tick the AudioRenderer was signalled.
|
||||
*/
|
||||
u64 GetSignalledTick() const;
|
||||
|
||||
/**
|
||||
* Set the tick the AudioRenderer was signalled.
|
||||
*
|
||||
* @param tick - The tick the AudioRenderer was signalled.
|
||||
*/
|
||||
void SetSignalledTick(u64 tick);
|
||||
|
||||
/**
|
||||
* Clear the remaining command count.
|
||||
*
|
||||
* @param session_id - Index for which command list to clear (0 or 1).
|
||||
*/
|
||||
void ClearRemainCount(u32 session_id);
|
||||
|
||||
/**
|
||||
* Get the remaining command count for a given command list.
|
||||
*
|
||||
* @param session_id - Index for which command list to clear (0 or 1).
|
||||
* @return The remaining command count.
|
||||
*/
|
||||
u32 GetRemainCommandCount(u32 session_id) const;
|
||||
|
||||
/**
|
||||
* Clear the command buffers (does not clear the time taken or the remaining command count).
|
||||
*/
|
||||
void ClearCommandBuffers();
|
||||
|
||||
private:
|
||||
/// Host signalling event
|
||||
Common::Event host_event{};
|
||||
/// AudioRenderer signalling event
|
||||
Common::Event adsp_event{};
|
||||
/// Host message queue
|
||||
|
||||
Common::ReaderWriterQueue<RenderMessage> host_messages{};
|
||||
/// AudioRenderer message queue
|
||||
|
||||
Common::ReaderWriterQueue<RenderMessage> adsp_messages{};
|
||||
/// Command buffers
|
||||
|
||||
std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
|
||||
/// Tick the AudioRnederer was signalled
|
||||
u64 signalled_tick{};
|
||||
};
|
||||
|
||||
/**
|
||||
* The AudioRenderer application running on the ADSP.
|
||||
*/
|
||||
class AudioRenderer {
|
||||
public:
|
||||
explicit AudioRenderer(Core::System& system);
|
||||
~AudioRenderer();
|
||||
|
||||
/**
|
||||
* Start the AudioRenderer.
|
||||
*
|
||||
* @param mailbox The mailbox to use for this session.
|
||||
*/
|
||||
void Start(AudioRenderer_Mailbox* mailbox);
|
||||
|
||||
/**
|
||||
* Stop the AudioRenderer.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Main AudioRenderer thread, responsible for processing the command lists.
|
||||
*/
|
||||
void ThreadFunc();
|
||||
|
||||
/**
|
||||
* Creates the streams which will receive the processed samples.
|
||||
*/
|
||||
void CreateSinkStreams();
|
||||
|
||||
/// Core system
|
||||
Core::System& system;
|
||||
/// Main thread
|
||||
std::thread thread{};
|
||||
/// The current state
|
||||
std::atomic<bool> running{};
|
||||
/// The active mailbox
|
||||
AudioRenderer_Mailbox* mailbox{};
|
||||
/// The command lists to process
|
||||
std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
|
||||
/// The output sink the AudioRenderer will use
|
||||
Sink::Sink& sink;
|
||||
/// The streams which will receive the processed samples
|
||||
std::array<Sink::SinkStream*, MaxRendererSessions> streams;
|
||||
};
|
||||
|
||||
} // namespace AudioRenderer::ADSP
|
||||
} // namespace AudioCore
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_buffer.h"
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/reader_writer_queue.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
namespace Core {
|
||||
namespace Timing {
|
||||
struct EventType;
|
||||
}
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace AudioCore {
|
||||
namespace Sink {
|
||||
class Sink;
|
||||
}
|
||||
|
||||
namespace AudioRenderer::ADSP {
|
||||
|
||||
enum class RenderMessage {
|
||||
/* 0x00 */ Invalid,
|
||||
/* 0x01 */ AudioRenderer_MapUnmap_Map,
|
||||
/* 0x02 */ AudioRenderer_MapUnmap_MapResponse,
|
||||
/* 0x03 */ AudioRenderer_MapUnmap_Unmap,
|
||||
/* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse,
|
||||
/* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache,
|
||||
/* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse,
|
||||
/* 0x07 */ AudioRenderer_MapUnmap_Shutdown,
|
||||
/* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse,
|
||||
/* 0x16 */ AudioRenderer_InitializeOK = 0x16,
|
||||
/* 0x20 */ AudioRenderer_RenderResponse = 0x20,
|
||||
/* 0x2A */ AudioRenderer_Render = 0x2A,
|
||||
/* 0x34 */ AudioRenderer_Shutdown = 0x34,
|
||||
};
|
||||
|
||||
/**
|
||||
* A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer
|
||||
* running on the ADSP.
|
||||
*/
|
||||
class AudioRenderer_Mailbox {
|
||||
public:
|
||||
/**
|
||||
* Send a message from the host to the AudioRenderer.
|
||||
*
|
||||
* @param message - The message to send to the AudioRenderer.
|
||||
*/
|
||||
void HostSendMessage(RenderMessage message);
|
||||
|
||||
/**
|
||||
* Host wait for a message from the AudioRenderer.
|
||||
*
|
||||
* @return The message returned from the AudioRenderer.
|
||||
*/
|
||||
RenderMessage HostWaitMessage();
|
||||
|
||||
/**
|
||||
* Send a message from the AudioRenderer to the host.
|
||||
*
|
||||
* @param message - The message to send to the host.
|
||||
*/
|
||||
void ADSPSendMessage(RenderMessage message);
|
||||
|
||||
/**
|
||||
* AudioRenderer wait for a message from the host.
|
||||
*
|
||||
* @return The message returned from the AudioRenderer.
|
||||
*/
|
||||
RenderMessage ADSPWaitMessage();
|
||||
|
||||
/**
|
||||
* Get the command buffer with the given session id (0 or 1).
|
||||
*
|
||||
* @param session_id - The session id to get (0 or 1).
|
||||
* @return The command buffer.
|
||||
*/
|
||||
CommandBuffer& GetCommandBuffer(u32 session_id);
|
||||
|
||||
/**
|
||||
* Set the command buffer with the given session id (0 or 1).
|
||||
*
|
||||
* @param session_id - The session id to get (0 or 1).
|
||||
* @param buffer - The command buffer to set.
|
||||
*/
|
||||
void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer);
|
||||
|
||||
/**
|
||||
* Get the total render time taken for the last command lists sent.
|
||||
*
|
||||
* @return Total render time taken for the last command lists.
|
||||
*/
|
||||
u64 GetRenderTimeTaken() const;
|
||||
|
||||
/**
|
||||
* Get the tick the AudioRenderer was signalled.
|
||||
*
|
||||
* @return The tick the AudioRenderer was signalled.
|
||||
*/
|
||||
u64 GetSignalledTick() const;
|
||||
|
||||
/**
|
||||
* Set the tick the AudioRenderer was signalled.
|
||||
*
|
||||
* @param tick - The tick the AudioRenderer was signalled.
|
||||
*/
|
||||
void SetSignalledTick(u64 tick);
|
||||
|
||||
/**
|
||||
* Clear the remaining command count.
|
||||
*
|
||||
* @param session_id - Index for which command list to clear (0 or 1).
|
||||
*/
|
||||
void ClearRemainCount(u32 session_id);
|
||||
|
||||
/**
|
||||
* Get the remaining command count for a given command list.
|
||||
*
|
||||
* @param session_id - Index for which command list to clear (0 or 1).
|
||||
* @return The remaining command count.
|
||||
*/
|
||||
u32 GetRemainCommandCount(u32 session_id) const;
|
||||
|
||||
/**
|
||||
* Clear the command buffers (does not clear the time taken or the remaining command count).
|
||||
*/
|
||||
void ClearCommandBuffers();
|
||||
|
||||
private:
|
||||
/// Host signalling event
|
||||
Common::Event host_event{};
|
||||
/// AudioRenderer signalling event
|
||||
Common::Event adsp_event{};
|
||||
/// Host message queue
|
||||
|
||||
Common::ReaderWriterQueue<RenderMessage> host_messages{};
|
||||
/// AudioRenderer message queue
|
||||
|
||||
Common::ReaderWriterQueue<RenderMessage> adsp_messages{};
|
||||
/// Command buffers
|
||||
|
||||
std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
|
||||
/// Tick the AudioRnederer was signalled
|
||||
u64 signalled_tick{};
|
||||
};
|
||||
|
||||
/**
|
||||
* The AudioRenderer application running on the ADSP.
|
||||
*/
|
||||
class AudioRenderer {
|
||||
public:
|
||||
explicit AudioRenderer(Core::System& system);
|
||||
~AudioRenderer();
|
||||
|
||||
/**
|
||||
* Start the AudioRenderer.
|
||||
*
|
||||
* @param mailbox The mailbox to use for this session.
|
||||
*/
|
||||
void Start(AudioRenderer_Mailbox* mailbox);
|
||||
|
||||
/**
|
||||
* Stop the AudioRenderer.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Main AudioRenderer thread, responsible for processing the command lists.
|
||||
*/
|
||||
void ThreadFunc();
|
||||
|
||||
/**
|
||||
* Creates the streams which will receive the processed samples.
|
||||
*/
|
||||
void CreateSinkStreams();
|
||||
|
||||
/// Core system
|
||||
Core::System& system;
|
||||
/// Main thread
|
||||
std::thread thread{};
|
||||
/// The current state
|
||||
std::atomic<bool> running{};
|
||||
/// The active mailbox
|
||||
AudioRenderer_Mailbox* mailbox{};
|
||||
/// The command lists to process
|
||||
std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
|
||||
/// The output sink the AudioRenderer will use
|
||||
Sink::Sink& sink;
|
||||
/// The streams which will receive the processed samples
|
||||
std::array<Sink::SinkStream*, MaxRendererSessions> streams;
|
||||
};
|
||||
|
||||
} // namespace AudioRenderer::ADSP
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
||||
struct CommandBuffer {
|
||||
CpuAddr buffer;
|
||||
u64 size;
|
||||
u64 time_limit;
|
||||
u32 remaining_command_count;
|
||||
bool reset_buffers;
|
||||
u64 applet_resource_user_id;
|
||||
u64 render_time_taken;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer::ADSP
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
||||
struct CommandBuffer {
|
||||
CpuAddr buffer;
|
||||
u64 size;
|
||||
u64 time_limit;
|
||||
u32 remaining_command_count;
|
||||
bool reset_buffers;
|
||||
u64 applet_resource_user_id;
|
||||
u64 render_time_taken;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer::ADSP
|
||||
|
||||
@@ -1,109 +1,109 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/command_list_header.h"
|
||||
#include "audio_core/renderer/command/commands.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
||||
void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size,
|
||||
Sink::SinkStream* stream_) {
|
||||
system = &system_;
|
||||
memory = &system->Memory();
|
||||
stream = stream_;
|
||||
header = reinterpret_cast<CommandListHeader*>(buffer);
|
||||
commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
|
||||
commands_buffer_size = size;
|
||||
command_count = header->command_count;
|
||||
sample_count = header->sample_count;
|
||||
target_sample_rate = header->sample_rate;
|
||||
mix_buffers = header->samples_buffer;
|
||||
buffer_count = header->buffer_count;
|
||||
processed_command_count = 0;
|
||||
}
|
||||
|
||||
void CommandListProcessor::SetProcessTimeMax(const u64 time) {
|
||||
max_process_time = time;
|
||||
}
|
||||
|
||||
u32 CommandListProcessor::GetRemainingCommandCount() const {
|
||||
return command_count - processed_command_count;
|
||||
}
|
||||
|
||||
void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
|
||||
commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
|
||||
commands_buffer_size = size;
|
||||
}
|
||||
|
||||
Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
|
||||
return stream;
|
||||
}
|
||||
|
||||
u64 CommandListProcessor::Process(u32 session_id) {
|
||||
const auto start_time_{system->CoreTiming().GetClockTicks()};
|
||||
const auto command_base{CpuAddr(commands)};
|
||||
|
||||
if (processed_command_count > 0) {
|
||||
current_processing_time += start_time_ - end_time;
|
||||
} else {
|
||||
start_time = start_time_;
|
||||
current_processing_time = 0;
|
||||
}
|
||||
|
||||
std::string dump{fmt::format("\nSession {}\n", session_id)};
|
||||
|
||||
for (u32 index = 0; index < command_count; index++) {
|
||||
auto& command{*reinterpret_cast<ICommand*>(commands)};
|
||||
|
||||
if (command.magic != 0xCAFEBABE) {
|
||||
LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}",
|
||||
command.magic);
|
||||
return system->CoreTiming().GetClockTicks() - start_time_;
|
||||
}
|
||||
|
||||
auto current_offset{CpuAddr(commands) - command_base};
|
||||
|
||||
if (current_offset + command.size > commands_buffer_size) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}",
|
||||
commands_buffer_size,
|
||||
CpuAddr(commands) + command.size - sizeof(CommandListHeader));
|
||||
return system->CoreTiming().GetClockTicks() - start_time_;
|
||||
}
|
||||
|
||||
if (Settings::values.dump_audio_commands) {
|
||||
command.Dump(*this, dump);
|
||||
}
|
||||
|
||||
if (!command.Verify(*this)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (command.enabled) {
|
||||
command.Process(*this);
|
||||
} else {
|
||||
dump += fmt::format("\tDisabled!\n");
|
||||
}
|
||||
|
||||
processed_command_count++;
|
||||
commands += command.size;
|
||||
}
|
||||
|
||||
if (Settings::values.dump_audio_commands && dump != last_dump) {
|
||||
LOG_WARNING(Service_Audio, "{}", dump);
|
||||
last_dump = dump;
|
||||
}
|
||||
|
||||
end_time = system->CoreTiming().GetClockTicks();
|
||||
return end_time - start_time_;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer::ADSP
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/command_list_header.h"
|
||||
#include "audio_core/renderer/command/commands.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
||||
void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size,
|
||||
Sink::SinkStream* stream_) {
|
||||
system = &system_;
|
||||
memory = &system->Memory();
|
||||
stream = stream_;
|
||||
header = reinterpret_cast<CommandListHeader*>(buffer);
|
||||
commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
|
||||
commands_buffer_size = size;
|
||||
command_count = header->command_count;
|
||||
sample_count = header->sample_count;
|
||||
target_sample_rate = header->sample_rate;
|
||||
mix_buffers = header->samples_buffer;
|
||||
buffer_count = header->buffer_count;
|
||||
processed_command_count = 0;
|
||||
}
|
||||
|
||||
void CommandListProcessor::SetProcessTimeMax(const u64 time) {
|
||||
max_process_time = time;
|
||||
}
|
||||
|
||||
u32 CommandListProcessor::GetRemainingCommandCount() const {
|
||||
return command_count - processed_command_count;
|
||||
}
|
||||
|
||||
void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
|
||||
commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
|
||||
commands_buffer_size = size;
|
||||
}
|
||||
|
||||
Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
|
||||
return stream;
|
||||
}
|
||||
|
||||
u64 CommandListProcessor::Process(u32 session_id) {
|
||||
const auto start_time_{system->CoreTiming().GetClockTicks()};
|
||||
const auto command_base{CpuAddr(commands)};
|
||||
|
||||
if (processed_command_count > 0) {
|
||||
current_processing_time += start_time_ - end_time;
|
||||
} else {
|
||||
start_time = start_time_;
|
||||
current_processing_time = 0;
|
||||
}
|
||||
|
||||
std::string dump{fmt::format("\nSession {}\n", session_id)};
|
||||
|
||||
for (u32 index = 0; index < command_count; index++) {
|
||||
auto& command{*reinterpret_cast<ICommand*>(commands)};
|
||||
|
||||
if (command.magic != 0xCAFEBABE) {
|
||||
LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}",
|
||||
command.magic);
|
||||
return system->CoreTiming().GetClockTicks() - start_time_;
|
||||
}
|
||||
|
||||
auto current_offset{CpuAddr(commands) - command_base};
|
||||
|
||||
if (current_offset + command.size > commands_buffer_size) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}",
|
||||
commands_buffer_size,
|
||||
CpuAddr(commands) + command.size - sizeof(CommandListHeader));
|
||||
return system->CoreTiming().GetClockTicks() - start_time_;
|
||||
}
|
||||
|
||||
if (Settings::values.dump_audio_commands) {
|
||||
command.Dump(*this, dump);
|
||||
}
|
||||
|
||||
if (!command.Verify(*this)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (command.enabled) {
|
||||
command.Process(*this);
|
||||
} else {
|
||||
dump += fmt::format("\tDisabled!\n");
|
||||
}
|
||||
|
||||
processed_command_count++;
|
||||
commands += command.size;
|
||||
}
|
||||
|
||||
if (Settings::values.dump_audio_commands && dump != last_dump) {
|
||||
LOG_WARNING(Service_Audio, "{}", dump);
|
||||
last_dump = dump;
|
||||
}
|
||||
|
||||
end_time = system->CoreTiming().GetClockTicks();
|
||||
return end_time - start_time_;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer::ADSP
|
||||
|
||||
@@ -1,119 +1,119 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
namespace Memory {
|
||||
class Memory;
|
||||
}
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace AudioCore {
|
||||
namespace Sink {
|
||||
class SinkStream;
|
||||
}
|
||||
|
||||
namespace AudioRenderer {
|
||||
struct CommandListHeader;
|
||||
|
||||
namespace ADSP {
|
||||
|
||||
/**
|
||||
* A processor for command lists given to the AudioRenderer.
|
||||
*/
|
||||
class CommandListProcessor {
|
||||
public:
|
||||
/**
|
||||
* Initialize the processor.
|
||||
*
|
||||
* @param system - The core system.
|
||||
* @param buffer - The command buffer to process.
|
||||
* @param size - The size of the buffer.
|
||||
* @param stream - The stream to be used for sending the samples.
|
||||
*/
|
||||
void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
|
||||
|
||||
/**
|
||||
* Set the maximum processing time for this command list.
|
||||
*
|
||||
* @param time - The maximum process time.
|
||||
*/
|
||||
void SetProcessTimeMax(u64 time);
|
||||
|
||||
/**
|
||||
* Get the remaining command count for this list.
|
||||
*
|
||||
* @return The remaining command count.
|
||||
*/
|
||||
u32 GetRemainingCommandCount() const;
|
||||
|
||||
/**
|
||||
* Set the command buffer.
|
||||
*
|
||||
* @param buffer - The buffer to use.
|
||||
* @param size - The size of the buffer.
|
||||
*/
|
||||
void SetBuffer(CpuAddr buffer, u64 size);
|
||||
|
||||
/**
|
||||
* Get the stream for this command list.
|
||||
*
|
||||
* @return The stream associated with this command list.
|
||||
*/
|
||||
Sink::SinkStream* GetOutputSinkStream() const;
|
||||
|
||||
/**
|
||||
* Process the command list.
|
||||
*
|
||||
* @param session_id - Session ID for the commands being processed.
|
||||
*
|
||||
* @return The time taken to process.
|
||||
*/
|
||||
u64 Process(u32 session_id);
|
||||
|
||||
/// Core system
|
||||
Core::System* system{};
|
||||
/// Core memory
|
||||
Core::Memory::Memory* memory{};
|
||||
/// Stream for the processed samples
|
||||
Sink::SinkStream* stream{};
|
||||
/// Header info for this command list
|
||||
CommandListHeader* header{};
|
||||
/// The command buffer
|
||||
u8* commands{};
|
||||
/// The command buffer size
|
||||
u64 commands_buffer_size{};
|
||||
/// The maximum processing time allotted
|
||||
u64 max_process_time{};
|
||||
/// The number of commands in the buffer
|
||||
u32 command_count{};
|
||||
/// The target sample count for output
|
||||
u32 sample_count{};
|
||||
/// The target sample rate for output
|
||||
u32 target_sample_rate{};
|
||||
/// The mixing buffers used by the commands
|
||||
std::span<s32> mix_buffers{};
|
||||
/// The number of mix buffers
|
||||
u32 buffer_count{};
|
||||
/// The number of processed commands so far
|
||||
u32 processed_command_count{};
|
||||
/// The processing start time of this list
|
||||
u64 start_time{};
|
||||
/// The current processing time for this list
|
||||
u64 current_processing_time{};
|
||||
/// The end processing time for this list
|
||||
u64 end_time{};
|
||||
/// Last command list string generated, used for dumping audio commands to console
|
||||
std::string last_dump{};
|
||||
};
|
||||
|
||||
} // namespace ADSP
|
||||
} // namespace AudioRenderer
|
||||
} // namespace AudioCore
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
namespace Memory {
|
||||
class Memory;
|
||||
}
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace AudioCore {
|
||||
namespace Sink {
|
||||
class SinkStream;
|
||||
}
|
||||
|
||||
namespace AudioRenderer {
|
||||
struct CommandListHeader;
|
||||
|
||||
namespace ADSP {
|
||||
|
||||
/**
|
||||
* A processor for command lists given to the AudioRenderer.
|
||||
*/
|
||||
class CommandListProcessor {
|
||||
public:
|
||||
/**
|
||||
* Initialize the processor.
|
||||
*
|
||||
* @param system - The core system.
|
||||
* @param buffer - The command buffer to process.
|
||||
* @param size - The size of the buffer.
|
||||
* @param stream - The stream to be used for sending the samples.
|
||||
*/
|
||||
void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
|
||||
|
||||
/**
|
||||
* Set the maximum processing time for this command list.
|
||||
*
|
||||
* @param time - The maximum process time.
|
||||
*/
|
||||
void SetProcessTimeMax(u64 time);
|
||||
|
||||
/**
|
||||
* Get the remaining command count for this list.
|
||||
*
|
||||
* @return The remaining command count.
|
||||
*/
|
||||
u32 GetRemainingCommandCount() const;
|
||||
|
||||
/**
|
||||
* Set the command buffer.
|
||||
*
|
||||
* @param buffer - The buffer to use.
|
||||
* @param size - The size of the buffer.
|
||||
*/
|
||||
void SetBuffer(CpuAddr buffer, u64 size);
|
||||
|
||||
/**
|
||||
* Get the stream for this command list.
|
||||
*
|
||||
* @return The stream associated with this command list.
|
||||
*/
|
||||
Sink::SinkStream* GetOutputSinkStream() const;
|
||||
|
||||
/**
|
||||
* Process the command list.
|
||||
*
|
||||
* @param session_id - Session ID for the commands being processed.
|
||||
*
|
||||
* @return The time taken to process.
|
||||
*/
|
||||
u64 Process(u32 session_id);
|
||||
|
||||
/// Core system
|
||||
Core::System* system{};
|
||||
/// Core memory
|
||||
Core::Memory::Memory* memory{};
|
||||
/// Stream for the processed samples
|
||||
Sink::SinkStream* stream{};
|
||||
/// Header info for this command list
|
||||
CommandListHeader* header{};
|
||||
/// The command buffer
|
||||
u8* commands{};
|
||||
/// The command buffer size
|
||||
u64 commands_buffer_size{};
|
||||
/// The maximum processing time allotted
|
||||
u64 max_process_time{};
|
||||
/// The number of commands in the buffer
|
||||
u32 command_count{};
|
||||
/// The target sample count for output
|
||||
u32 sample_count{};
|
||||
/// The target sample rate for output
|
||||
u32 target_sample_rate{};
|
||||
/// The mixing buffers used by the commands
|
||||
std::span<s32> mix_buffers{};
|
||||
/// The number of mix buffers
|
||||
u32 buffer_count{};
|
||||
/// The number of processed commands so far
|
||||
u32 processed_command_count{};
|
||||
/// The processing start time of this list
|
||||
u64 start_time{};
|
||||
/// The current processing time for this list
|
||||
u64 current_processing_time{};
|
||||
/// The end processing time for this list
|
||||
u64 end_time{};
|
||||
/// Last command list string generated, used for dumping audio commands to console
|
||||
std::string last_dump{};
|
||||
};
|
||||
|
||||
} // namespace ADSP
|
||||
} // namespace AudioRenderer
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -1,74 +1,74 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "audio_core/common/feature_support.h"
|
||||
#include "audio_core/renderer/audio_device.h"
|
||||
#include "audio_core/sink/sink.h"
|
||||
#include "core/core.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
constexpr std::array usb_device_names{
|
||||
AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioTvOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"},
|
||||
};
|
||||
|
||||
constexpr std::array device_names{
|
||||
AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioTvOutput"},
|
||||
};
|
||||
|
||||
constexpr std::array output_device_names{
|
||||
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioTvOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioExternalOutput"},
|
||||
};
|
||||
|
||||
AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
|
||||
const u32 revision)
|
||||
: output_sink{system.AudioCore().GetOutputSink()},
|
||||
applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
|
||||
|
||||
u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
|
||||
const size_t max_count) const {
|
||||
std::span<const AudioDeviceName> names{};
|
||||
|
||||
if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
|
||||
names = usb_device_names;
|
||||
} else {
|
||||
names = device_names;
|
||||
}
|
||||
|
||||
const u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
|
||||
for (u32 i = 0; i < out_count; i++) {
|
||||
out_buffer.push_back(names[i]);
|
||||
}
|
||||
return out_count;
|
||||
}
|
||||
|
||||
u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
|
||||
const size_t max_count) const {
|
||||
const u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
|
||||
|
||||
for (u32 i = 0; i < out_count; i++) {
|
||||
out_buffer.push_back(output_device_names[i]);
|
||||
}
|
||||
return out_count;
|
||||
}
|
||||
|
||||
void AudioDevice::SetDeviceVolumes(const f32 volume) {
|
||||
output_sink.SetDeviceVolume(volume);
|
||||
}
|
||||
|
||||
f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
|
||||
return output_sink.GetDeviceVolume();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "audio_core/common/feature_support.h"
|
||||
#include "audio_core/renderer/audio_device.h"
|
||||
#include "audio_core/sink/sink.h"
|
||||
#include "core/core.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
constexpr std::array usb_device_names{
|
||||
AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioTvOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"},
|
||||
};
|
||||
|
||||
constexpr std::array device_names{
|
||||
AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioTvOutput"},
|
||||
};
|
||||
|
||||
constexpr std::array output_device_names{
|
||||
AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioTvOutput"},
|
||||
AudioDevice::AudioDeviceName{"AudioExternalOutput"},
|
||||
};
|
||||
|
||||
AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
|
||||
const u32 revision)
|
||||
: output_sink{system.AudioCore().GetOutputSink()},
|
||||
applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
|
||||
|
||||
u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
|
||||
const size_t max_count) const {
|
||||
std::span<const AudioDeviceName> names{};
|
||||
|
||||
if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
|
||||
names = usb_device_names;
|
||||
} else {
|
||||
names = device_names;
|
||||
}
|
||||
|
||||
const u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
|
||||
for (u32 i = 0; i < out_count; i++) {
|
||||
out_buffer.push_back(names[i]);
|
||||
}
|
||||
return out_count;
|
||||
}
|
||||
|
||||
u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
|
||||
const size_t max_count) const {
|
||||
const u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
|
||||
|
||||
for (u32 i = 0; i < out_count; i++) {
|
||||
out_buffer.push_back(output_device_names[i]);
|
||||
}
|
||||
return out_count;
|
||||
}
|
||||
|
||||
void AudioDevice::SetDeviceVolumes(const f32 volume) {
|
||||
output_sink.SetDeviceVolume(volume);
|
||||
}
|
||||
|
||||
f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
|
||||
return output_sink.GetDeviceVolume();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "audio_core/audio_render_manager.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace AudioCore {
|
||||
namespace Sink {
|
||||
class Sink;
|
||||
}
|
||||
|
||||
namespace AudioRenderer {
|
||||
/**
|
||||
* An interface to an output audio device available to the Switch.
|
||||
*/
|
||||
class AudioDevice {
|
||||
public:
|
||||
struct AudioDeviceName {
|
||||
std::array<char, 0x100> name{};
|
||||
|
||||
constexpr AudioDeviceName(std::string_view name_) {
|
||||
name_.copy(name.data(), name.size() - 1);
|
||||
}
|
||||
};
|
||||
|
||||
explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
|
||||
|
||||
/**
|
||||
* Get a list of the available output devices.
|
||||
*
|
||||
* @param out_buffer - Output buffer to write the available device names.
|
||||
* @param max_count - Maximum number of devices to write (count of out_buffer).
|
||||
* @return Number of device names written.
|
||||
*/
|
||||
u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
|
||||
|
||||
/**
|
||||
* Get a list of the available output devices.
|
||||
* Different to above somehow...
|
||||
*
|
||||
* @param out_buffer - Output buffer to write the available device names.
|
||||
* @param max_count - Maximum number of devices to write (count of out_buffer).
|
||||
* @return Number of device names written.
|
||||
*/
|
||||
u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
|
||||
|
||||
/**
|
||||
* Set the volume of all streams in the backend sink.
|
||||
*
|
||||
* @param volume - Volume to set.
|
||||
*/
|
||||
void SetDeviceVolumes(f32 volume);
|
||||
|
||||
/**
|
||||
* Get the volume for a given device name.
|
||||
* Note: This is not fully implemented, we only assume 1 device for all streams.
|
||||
*
|
||||
* @param name - Name of the device to check. Unused.
|
||||
* @return Volume of the device.
|
||||
*/
|
||||
f32 GetDeviceVolume(std::string_view name) const;
|
||||
|
||||
private:
|
||||
/// Backend output sink for the device
|
||||
Sink::Sink& output_sink;
|
||||
/// Resource id this device is used for
|
||||
const u64 applet_resource_user_id;
|
||||
/// User audio renderer revision
|
||||
const u32 user_revision;
|
||||
};
|
||||
|
||||
} // namespace AudioRenderer
|
||||
} // namespace AudioCore
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "audio_core/audio_render_manager.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace AudioCore {
|
||||
namespace Sink {
|
||||
class Sink;
|
||||
}
|
||||
|
||||
namespace AudioRenderer {
|
||||
/**
|
||||
* An interface to an output audio device available to the Switch.
|
||||
*/
|
||||
class AudioDevice {
|
||||
public:
|
||||
struct AudioDeviceName {
|
||||
std::array<char, 0x100> name{};
|
||||
|
||||
constexpr AudioDeviceName(std::string_view name_) {
|
||||
name_.copy(name.data(), name.size() - 1);
|
||||
}
|
||||
};
|
||||
|
||||
explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
|
||||
|
||||
/**
|
||||
* Get a list of the available output devices.
|
||||
*
|
||||
* @param out_buffer - Output buffer to write the available device names.
|
||||
* @param max_count - Maximum number of devices to write (count of out_buffer).
|
||||
* @return Number of device names written.
|
||||
*/
|
||||
u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
|
||||
|
||||
/**
|
||||
* Get a list of the available output devices.
|
||||
* Different to above somehow...
|
||||
*
|
||||
* @param out_buffer - Output buffer to write the available device names.
|
||||
* @param max_count - Maximum number of devices to write (count of out_buffer).
|
||||
* @return Number of device names written.
|
||||
*/
|
||||
u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
|
||||
|
||||
/**
|
||||
* Set the volume of all streams in the backend sink.
|
||||
*
|
||||
* @param volume - Volume to set.
|
||||
*/
|
||||
void SetDeviceVolumes(f32 volume);
|
||||
|
||||
/**
|
||||
* Get the volume for a given device name.
|
||||
* Note: This is not fully implemented, we only assume 1 device for all streams.
|
||||
*
|
||||
* @param name - Name of the device to check. Unused.
|
||||
* @return Volume of the device.
|
||||
*/
|
||||
f32 GetDeviceVolume(std::string_view name) const;
|
||||
|
||||
private:
|
||||
/// Backend output sink for the device
|
||||
Sink::Sink& output_sink;
|
||||
/// Resource id this device is used for
|
||||
const u64 applet_resource_user_id;
|
||||
/// User audio renderer revision
|
||||
const u32 user_revision;
|
||||
};
|
||||
|
||||
} // namespace AudioRenderer
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -1,67 +1,67 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/audio_render_manager.h"
|
||||
#include "audio_core/common/audio_renderer_parameter.h"
|
||||
#include "audio_core/renderer/audio_renderer.h"
|
||||
#include "audio_core/renderer/system_manager.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_transfer_memory.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event)
|
||||
: core{system_}, manager{manager_}, system{system_, rendered_event} {}
|
||||
|
||||
Result Renderer::Initialize(const AudioRendererParameterInternal& params,
|
||||
Kernel::KTransferMemory* transfer_memory,
|
||||
const u64 transfer_memory_size, const u32 process_handle,
|
||||
const u64 applet_resource_user_id, const s32 session_id) {
|
||||
if (params.execution_mode == ExecutionMode::Auto) {
|
||||
if (!manager.AddSystem(system)) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"Both Audio Render sessions are in use, cannot create any more");
|
||||
return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
|
||||
}
|
||||
system_registered = true;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
system.Initialize(params, transfer_memory, transfer_memory_size, process_handle,
|
||||
applet_resource_user_id, session_id);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void Renderer::Finalize() {
|
||||
auto session_id{system.GetSessionId()};
|
||||
|
||||
system.Finalize();
|
||||
|
||||
if (system_registered) {
|
||||
manager.RemoveSystem(system);
|
||||
system_registered = false;
|
||||
}
|
||||
|
||||
manager.ReleaseSessionId(session_id);
|
||||
}
|
||||
|
||||
System& Renderer::GetSystem() {
|
||||
return system;
|
||||
}
|
||||
|
||||
void Renderer::Start() {
|
||||
system.Start();
|
||||
}
|
||||
|
||||
void Renderer::Stop() {
|
||||
system.Stop();
|
||||
}
|
||||
|
||||
Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance,
|
||||
std::span<u8> output) {
|
||||
return system.Update(input, performance, output);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/audio_render_manager.h"
|
||||
#include "audio_core/common/audio_renderer_parameter.h"
|
||||
#include "audio_core/renderer/audio_renderer.h"
|
||||
#include "audio_core/renderer/system_manager.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_transfer_memory.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event)
|
||||
: core{system_}, manager{manager_}, system{system_, rendered_event} {}
|
||||
|
||||
Result Renderer::Initialize(const AudioRendererParameterInternal& params,
|
||||
Kernel::KTransferMemory* transfer_memory,
|
||||
const u64 transfer_memory_size, const u32 process_handle,
|
||||
const u64 applet_resource_user_id, const s32 session_id) {
|
||||
if (params.execution_mode == ExecutionMode::Auto) {
|
||||
if (!manager.AddSystem(system)) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"Both Audio Render sessions are in use, cannot create any more");
|
||||
return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
|
||||
}
|
||||
system_registered = true;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
system.Initialize(params, transfer_memory, transfer_memory_size, process_handle,
|
||||
applet_resource_user_id, session_id);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void Renderer::Finalize() {
|
||||
auto session_id{system.GetSessionId()};
|
||||
|
||||
system.Finalize();
|
||||
|
||||
if (system_registered) {
|
||||
manager.RemoveSystem(system);
|
||||
system_registered = false;
|
||||
}
|
||||
|
||||
manager.ReleaseSessionId(session_id);
|
||||
}
|
||||
|
||||
System& Renderer::GetSystem() {
|
||||
return system;
|
||||
}
|
||||
|
||||
void Renderer::Start() {
|
||||
system.Start();
|
||||
}
|
||||
|
||||
void Renderer::Stop() {
|
||||
system.Stop();
|
||||
}
|
||||
|
||||
Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance,
|
||||
std::span<u8> output) {
|
||||
return system.Update(input, performance, output);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,97 +1,97 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/system.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class KTransferMemory;
|
||||
}
|
||||
|
||||
namespace AudioCore {
|
||||
struct AudioRendererParameterInternal;
|
||||
|
||||
namespace AudioRenderer {
|
||||
class Manager;
|
||||
|
||||
/**
|
||||
* Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls.
|
||||
*/
|
||||
class Renderer {
|
||||
public:
|
||||
explicit Renderer(Core::System& system, Manager& manager, Kernel::KEvent* rendered_event);
|
||||
|
||||
/**
|
||||
* Initialize the renderer.
|
||||
* Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes
|
||||
* everything to a default state.
|
||||
*
|
||||
* @param params - Input parameters to initialize the system with.
|
||||
* @param transfer_memory - Game-supplied memory for all workbuffers. Unused.
|
||||
* @param transfer_memory_size - Size of the transfer memory. Unused.
|
||||
* @param process_handle - Process handle, also used for memory. Unused.
|
||||
* @param applet_resource_user_id - Applet id for this renderer. Unused.
|
||||
* @param session_id - Session id of this renderer.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result Initialize(const AudioRendererParameterInternal& params,
|
||||
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
|
||||
u32 process_handle, u64 applet_resource_user_id, s32 session_id);
|
||||
|
||||
/**
|
||||
* Finalize the renderer for shutdown.
|
||||
*/
|
||||
void Finalize();
|
||||
|
||||
/**
|
||||
* Get the renderer's system.
|
||||
*
|
||||
* @return Reference to the system.
|
||||
*/
|
||||
System& GetSystem();
|
||||
|
||||
/**
|
||||
* Start the renderer.
|
||||
*/
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* Stop the renderer.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
/**
|
||||
* Update the audio renderer with new information.
|
||||
* Called via RequestUpdate from the AudRen:U service.
|
||||
*
|
||||
* @param input - Input buffer containing the new data.
|
||||
* @param performance - Optional performance buffer for outputting performance metrics.
|
||||
* @param output - Output data from the renderer.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result RequestUpdate(std::span<const u8> input, std::span<u8> performance,
|
||||
std::span<u8> output);
|
||||
|
||||
private:
|
||||
/// System core
|
||||
Core::System& core;
|
||||
/// Manager this renderer is registered with
|
||||
Manager& manager;
|
||||
/// Is the audio renderer initialized?
|
||||
bool initialized{};
|
||||
/// Is the system registered with the manager?
|
||||
bool system_registered{};
|
||||
/// Audio render system, main driver of audio rendering
|
||||
System system;
|
||||
};
|
||||
|
||||
} // namespace AudioRenderer
|
||||
} // namespace AudioCore
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/system.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class KTransferMemory;
|
||||
}
|
||||
|
||||
namespace AudioCore {
|
||||
struct AudioRendererParameterInternal;
|
||||
|
||||
namespace AudioRenderer {
|
||||
class Manager;
|
||||
|
||||
/**
|
||||
* Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls.
|
||||
*/
|
||||
class Renderer {
|
||||
public:
|
||||
explicit Renderer(Core::System& system, Manager& manager, Kernel::KEvent* rendered_event);
|
||||
|
||||
/**
|
||||
* Initialize the renderer.
|
||||
* Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes
|
||||
* everything to a default state.
|
||||
*
|
||||
* @param params - Input parameters to initialize the system with.
|
||||
* @param transfer_memory - Game-supplied memory for all workbuffers. Unused.
|
||||
* @param transfer_memory_size - Size of the transfer memory. Unused.
|
||||
* @param process_handle - Process handle, also used for memory. Unused.
|
||||
* @param applet_resource_user_id - Applet id for this renderer. Unused.
|
||||
* @param session_id - Session id of this renderer.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result Initialize(const AudioRendererParameterInternal& params,
|
||||
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
|
||||
u32 process_handle, u64 applet_resource_user_id, s32 session_id);
|
||||
|
||||
/**
|
||||
* Finalize the renderer for shutdown.
|
||||
*/
|
||||
void Finalize();
|
||||
|
||||
/**
|
||||
* Get the renderer's system.
|
||||
*
|
||||
* @return Reference to the system.
|
||||
*/
|
||||
System& GetSystem();
|
||||
|
||||
/**
|
||||
* Start the renderer.
|
||||
*/
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* Stop the renderer.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
/**
|
||||
* Update the audio renderer with new information.
|
||||
* Called via RequestUpdate from the AudRen:U service.
|
||||
*
|
||||
* @param input - Input buffer containing the new data.
|
||||
* @param performance - Optional performance buffer for outputting performance metrics.
|
||||
* @param output - Output data from the renderer.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result RequestUpdate(std::span<const u8> input, std::span<u8> performance,
|
||||
std::span<u8> output);
|
||||
|
||||
private:
|
||||
/// System core
|
||||
Core::System& core;
|
||||
/// Manager this renderer is registered with
|
||||
Manager& manager;
|
||||
/// Is the audio renderer initialized?
|
||||
bool initialized{};
|
||||
/// Is the system registered with the manager?
|
||||
bool system_registered{};
|
||||
/// Audio render system, main driver of audio rendering
|
||||
System system;
|
||||
};
|
||||
|
||||
} // namespace AudioRenderer
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -1,193 +1,193 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/common/feature_support.h"
|
||||
#include "audio_core/renderer/behavior/behavior_info.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {}
|
||||
|
||||
u32 BehaviorInfo::GetProcessRevisionNum() const {
|
||||
return process_revision;
|
||||
}
|
||||
|
||||
u32 BehaviorInfo::GetProcessRevision() const {
|
||||
return Common::MakeMagic('R', 'E', 'V',
|
||||
static_cast<char>(static_cast<u8>('0') + process_revision));
|
||||
}
|
||||
|
||||
u32 BehaviorInfo::GetUserRevisionNum() const {
|
||||
return user_revision;
|
||||
}
|
||||
|
||||
u32 BehaviorInfo::GetUserRevision() const {
|
||||
return Common::MakeMagic('R', 'E', 'V',
|
||||
static_cast<char>(static_cast<u8>('0') + user_revision));
|
||||
}
|
||||
|
||||
void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) {
|
||||
user_revision = GetRevisionNum(user_revision_);
|
||||
}
|
||||
|
||||
void BehaviorInfo::ClearError() {
|
||||
error_count = 0;
|
||||
}
|
||||
|
||||
void BehaviorInfo::AppendError(const ErrorInfo& error) {
|
||||
LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
|
||||
error.error_code.raw, error.address);
|
||||
if (error_count < MaxErrors) {
|
||||
errors[error_count++] = error;
|
||||
}
|
||||
}
|
||||
|
||||
void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const {
|
||||
out_count = std::min(error_count, MaxErrors);
|
||||
|
||||
for (size_t i = 0; i < MaxErrors; i++) {
|
||||
if (i < out_count) {
|
||||
out_errors[i] = errors[i];
|
||||
} else {
|
||||
out_errors[i] = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BehaviorInfo::UpdateFlags(const Flags flags_) {
|
||||
flags = flags_;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsMemoryForceMappingEnabled() const {
|
||||
return flags.IsMemoryForceMappingEnabled;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
|
||||
return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsSplitterSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::Splitter, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsSplitterBugFixed() const {
|
||||
return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsEffectInfoVersion2Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsWaveBufferVer2Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsLongSizePreDelaySupported() const {
|
||||
return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsElapsedFrameCountSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision);
|
||||
}
|
||||
|
||||
size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const {
|
||||
if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const {
|
||||
return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::UseBiquadFilterFloatProcessing() const {
|
||||
return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const {
|
||||
return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsDeviceApiVersion2Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsDelayChannelMappingChanged() const {
|
||||
return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsReverbChannelMappingChanged() const {
|
||||
return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const {
|
||||
return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/common/feature_support.h"
|
||||
#include "audio_core/renderer/behavior/behavior_info.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {}
|
||||
|
||||
u32 BehaviorInfo::GetProcessRevisionNum() const {
|
||||
return process_revision;
|
||||
}
|
||||
|
||||
u32 BehaviorInfo::GetProcessRevision() const {
|
||||
return Common::MakeMagic('R', 'E', 'V',
|
||||
static_cast<char>(static_cast<u8>('0') + process_revision));
|
||||
}
|
||||
|
||||
u32 BehaviorInfo::GetUserRevisionNum() const {
|
||||
return user_revision;
|
||||
}
|
||||
|
||||
u32 BehaviorInfo::GetUserRevision() const {
|
||||
return Common::MakeMagic('R', 'E', 'V',
|
||||
static_cast<char>(static_cast<u8>('0') + user_revision));
|
||||
}
|
||||
|
||||
void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) {
|
||||
user_revision = GetRevisionNum(user_revision_);
|
||||
}
|
||||
|
||||
void BehaviorInfo::ClearError() {
|
||||
error_count = 0;
|
||||
}
|
||||
|
||||
void BehaviorInfo::AppendError(const ErrorInfo& error) {
|
||||
LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
|
||||
error.error_code.raw, error.address);
|
||||
if (error_count < MaxErrors) {
|
||||
errors[error_count++] = error;
|
||||
}
|
||||
}
|
||||
|
||||
void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const {
|
||||
out_count = std::min(error_count, MaxErrors);
|
||||
|
||||
for (size_t i = 0; i < MaxErrors; i++) {
|
||||
if (i < out_count) {
|
||||
out_errors[i] = errors[i];
|
||||
} else {
|
||||
out_errors[i] = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BehaviorInfo::UpdateFlags(const Flags flags_) {
|
||||
flags = flags_;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsMemoryForceMappingEnabled() const {
|
||||
return flags.IsMemoryForceMappingEnabled;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
|
||||
return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsSplitterSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::Splitter, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsSplitterBugFixed() const {
|
||||
return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsEffectInfoVersion2Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsWaveBufferVer2Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsLongSizePreDelaySupported() const {
|
||||
return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsElapsedFrameCountSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision);
|
||||
}
|
||||
|
||||
size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const {
|
||||
if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint,
|
||||
user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const {
|
||||
return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::UseBiquadFilterFloatProcessing() const {
|
||||
return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
|
||||
return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const {
|
||||
return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsDeviceApiVersion2Supported() const {
|
||||
return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsDelayChannelMappingChanged() const {
|
||||
return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsReverbChannelMappingChanged() const {
|
||||
return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const {
|
||||
return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,376 +1,376 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Holds host and user revisions, checks whether render features can be enabled, and reports errors.
|
||||
*/
|
||||
class BehaviorInfo {
|
||||
static constexpr u32 MaxErrors = 10;
|
||||
|
||||
public:
|
||||
struct ErrorInfo {
|
||||
/* 0x00 */ Result error_code{0};
|
||||
/* 0x04 */ u32 unk_04;
|
||||
/* 0x08 */ CpuAddr address;
|
||||
};
|
||||
static_assert(sizeof(ErrorInfo) == 0x10, "BehaviorInfo::ErrorInfo has the wrong size!");
|
||||
|
||||
struct Flags {
|
||||
u64 IsMemoryForceMappingEnabled : 1;
|
||||
};
|
||||
|
||||
struct InParameter {
|
||||
/* 0x00 */ u32 revision;
|
||||
/* 0x08 */ Flags flags;
|
||||
};
|
||||
static_assert(sizeof(InParameter) == 0x10, "BehaviorInfo::InParameter has the wrong size!");
|
||||
|
||||
struct OutStatus {
|
||||
/* 0x00 */ std::array<ErrorInfo, MaxErrors> errors;
|
||||
/* 0xA0 */ u32 error_count;
|
||||
/* 0xA4 */ char unkA4[0xC];
|
||||
};
|
||||
static_assert(sizeof(OutStatus) == 0xB0, "BehaviorInfo::OutStatus has the wrong size!");
|
||||
|
||||
BehaviorInfo();
|
||||
|
||||
/**
|
||||
* Get the host revision as a number.
|
||||
*
|
||||
* @return The host revision.
|
||||
*/
|
||||
u32 GetProcessRevisionNum() const;
|
||||
|
||||
/**
|
||||
* Get the host revision in chars, e.g REV8.
|
||||
* Rev 10 and higher use the ascii characters above 9.
|
||||
* E.g:
|
||||
* Rev 10 = REV:
|
||||
* Rev 11 = REV;
|
||||
*
|
||||
* @return The host revision.
|
||||
*/
|
||||
u32 GetProcessRevision() const;
|
||||
|
||||
/**
|
||||
* Get the user revision as a number.
|
||||
*
|
||||
* @return The user revision.
|
||||
*/
|
||||
u32 GetUserRevisionNum() const;
|
||||
|
||||
/**
|
||||
* Get the user revision in chars, e.g REV8.
|
||||
* Rev 10 and higher use the ascii characters above 9. REV: REV; etc.
|
||||
*
|
||||
* @return The user revision.
|
||||
*/
|
||||
u32 GetUserRevision() const;
|
||||
|
||||
/**
|
||||
* Set the user revision.
|
||||
*
|
||||
* @param user_revision - The user's revision.
|
||||
*/
|
||||
void SetUserLibRevision(u32 user_revision);
|
||||
|
||||
/**
|
||||
* Clear the current error count.
|
||||
*/
|
||||
void ClearError();
|
||||
|
||||
/**
|
||||
* Append an error to the error list.
|
||||
*
|
||||
* @param error - The new error.
|
||||
*/
|
||||
void AppendError(const ErrorInfo& error);
|
||||
|
||||
/**
|
||||
* Copy errors to the given output container.
|
||||
*
|
||||
* @param out_errors - Output container to receive the errors.
|
||||
* @param out_count - The number of errors written.
|
||||
*/
|
||||
void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const;
|
||||
|
||||
/**
|
||||
* Update the behaviour flags.
|
||||
*
|
||||
* @param flags - New flags to use.
|
||||
*/
|
||||
void UpdateFlags(Flags flags);
|
||||
|
||||
/**
|
||||
* Check if memory pools can be forcibly mapped.
|
||||
*
|
||||
* @return True if enabled, otherwise false.
|
||||
*/
|
||||
bool IsMemoryForceMappingEnabled() const;
|
||||
|
||||
/**
|
||||
* Check if the ADPCM context bug is fixed.
|
||||
* The ADPCM context was not being sent to the AudioRenderer, leading to incorrect scaling being
|
||||
* used.
|
||||
*
|
||||
* @return True if fixed, otherwise false.
|
||||
*/
|
||||
bool IsAdpcmLoopContextBugFixed() const;
|
||||
|
||||
/**
|
||||
* Check if the splitter is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsSplitterSupported() const;
|
||||
|
||||
/**
|
||||
* Check if the splitter bug is fixed.
|
||||
* Update is given the wrong number of splitter destinations, leading to invalid data
|
||||
* being processed.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsSplitterBugFixed() const;
|
||||
|
||||
/**
|
||||
* Check if effects version 2 are supported.
|
||||
* This gives support for returning effect states from the AudioRenderer, currently only used
|
||||
* for Limiter statistics.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsEffectInfoVersion2Supported() const;
|
||||
|
||||
/**
|
||||
* Check if a variadic command buffer is supported.
|
||||
* As of Rev 5 with the added optional performance metric logging, the command
|
||||
* buffer can be a variable size, so take that into account for calcualting its size.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsVariadicCommandBufferSizeSupported() const;
|
||||
|
||||
/**
|
||||
* Check if wave buffers version 2 are supported.
|
||||
* See WaveBufferVersion1 and WaveBufferVersion2.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsWaveBufferVer2Supported() const;
|
||||
|
||||
/**
|
||||
* Check if long size pre delay is supported.
|
||||
* This allows a longer initial delay time for the Reverb command.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsLongSizePreDelaySupported() const;
|
||||
|
||||
/**
|
||||
* Check if the command time estimator version 2 is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsCommandProcessingTimeEstimatorVersion2Supported() const;
|
||||
|
||||
/**
|
||||
* Check if the command time estimator version 3 is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsCommandProcessingTimeEstimatorVersion3Supported() const;
|
||||
|
||||
/**
|
||||
* Check if the command time estimator version 4 is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsCommandProcessingTimeEstimatorVersion4Supported() const;
|
||||
|
||||
/**
|
||||
* Check if the command time estimator version 5 is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsCommandProcessingTimeEstimatorVersion5Supported() const;
|
||||
|
||||
/**
|
||||
* Check if the AudioRenderer can use up to 70% of the allocated processing timeslice.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
|
||||
|
||||
/**
|
||||
* Check if the AudioRenderer can use up to 75% of the allocated processing timeslice.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
|
||||
|
||||
/**
|
||||
* Check if the AudioRenderer can use up to 80% of the allocated processing timeslice.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
|
||||
|
||||
/**
|
||||
* Check if voice flushing is supported
|
||||
* This allowws low-priority voices to be dropped if the AudioRenderer is running behind.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsFlushVoiceWaveBuffersSupported() const;
|
||||
|
||||
/**
|
||||
* Check if counting the number of elapsed frames is supported.
|
||||
* This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
|
||||
* processed a command list.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsElapsedFrameCountSupported() const;
|
||||
|
||||
/**
|
||||
* Check if performance metrics version 2 are supported.
|
||||
* This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
|
||||
* (Unused?).
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsPerformanceMetricsDataFormatVersion2Supported() const;
|
||||
|
||||
/**
|
||||
* Get the supported performance metrics version.
|
||||
* Version 2 logs some extra fields in output, such as number of voices dropped,
|
||||
* processing start time, if the AudioRenderer exceeded its time, etc.
|
||||
*
|
||||
* @return Version supported, either 1 or 2.
|
||||
*/
|
||||
size_t GetPerformanceMetricsDataFormat() const;
|
||||
|
||||
/**
|
||||
* Check if skipping voice pitch and sample rate conversion is supported.
|
||||
* This speeds up the data source commands by skipping resampling if unwanted.
|
||||
* See AudioCore::AudioRenderer::DecodeFromWaveBuffers
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsVoicePitchAndSrcSkippedSupported() const;
|
||||
|
||||
/**
|
||||
* Check if resetting played sample count at loop points is supported.
|
||||
* This resets the number of samples played in a voice state when a loop point is reached.
|
||||
* See AudioCore::AudioRenderer::DecodeFromWaveBuffers
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
|
||||
|
||||
/**
|
||||
* Check if the clear state bug for biquad filters is fixed.
|
||||
* The biquad state was not marked as needing re-initialisation when the effect was updated, it
|
||||
* was only initialized once with a new effect.
|
||||
*
|
||||
* @return True if fixed, otherwise false.
|
||||
*/
|
||||
bool IsBiquadFilterEffectStateClearBugFixed() const;
|
||||
|
||||
/**
|
||||
* Check if Q23 precision is supported for fixed point.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsVolumeMixParameterPrecisionQ23Supported() const;
|
||||
|
||||
/**
|
||||
* Check if float processing for biuad filters is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool UseBiquadFilterFloatProcessing() const;
|
||||
|
||||
/**
|
||||
* Check if dirty-only mix updates are supported.
|
||||
* This saves a lot of buffer size as mixes can be large and not change much.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsMixInParameterDirtyOnlyUpdateSupported() const;
|
||||
|
||||
/**
|
||||
* Check if multi-tap biquad filters are supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool UseMultiTapBiquadFilterProcessing() const;
|
||||
|
||||
/**
|
||||
* Check if device api version 2 is supported.
|
||||
* In the SDK but not in any sysmodule? Not sure, left here for completeness anyway.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsDeviceApiVersion2Supported() const;
|
||||
|
||||
/**
|
||||
* Check if new channel mappings are used for Delay commands.
|
||||
* Older commands used:
|
||||
* front left/front right/back left/back right/center/lfe
|
||||
* Whereas everywhere else in the code uses:
|
||||
* front left/front right/center/lfe/back left/back right
|
||||
* This corrects that and makes everything standardised.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsDelayChannelMappingChanged() const;
|
||||
|
||||
/**
|
||||
* Check if new channel mappings are used for Reverb commands.
|
||||
* Older commands used:
|
||||
* front left/front right/back left/back right/center/lfe
|
||||
* Whereas everywhere else in the code uses:
|
||||
* front left/front right/center/lfe/back left/back right
|
||||
* This corrects that and makes everything standardised.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsReverbChannelMappingChanged() const;
|
||||
|
||||
/**
|
||||
* Check if new channel mappings are used for I3dl2Reverb commands.
|
||||
* Older commands used:
|
||||
* front left/front right/back left/back right/center/lfe
|
||||
* Whereas everywhere else in the code uses:
|
||||
* front left/front right/center/lfe/back left/back right
|
||||
* This corrects that and makes everything standardised.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsI3dl2ReverbChannelMappingChanged() const;
|
||||
|
||||
/// Host version
|
||||
u32 process_revision;
|
||||
/// User version
|
||||
u32 user_revision{};
|
||||
/// Behaviour flags
|
||||
Flags flags{};
|
||||
/// Errors generated and reported during Update
|
||||
std::array<ErrorInfo, MaxErrors> errors{};
|
||||
/// Error count
|
||||
u32 error_count{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Holds host and user revisions, checks whether render features can be enabled, and reports errors.
|
||||
*/
|
||||
class BehaviorInfo {
|
||||
static constexpr u32 MaxErrors = 10;
|
||||
|
||||
public:
|
||||
struct ErrorInfo {
|
||||
/* 0x00 */ Result error_code{0};
|
||||
/* 0x04 */ u32 unk_04;
|
||||
/* 0x08 */ CpuAddr address;
|
||||
};
|
||||
static_assert(sizeof(ErrorInfo) == 0x10, "BehaviorInfo::ErrorInfo has the wrong size!");
|
||||
|
||||
struct Flags {
|
||||
u64 IsMemoryForceMappingEnabled : 1;
|
||||
};
|
||||
|
||||
struct InParameter {
|
||||
/* 0x00 */ u32 revision;
|
||||
/* 0x08 */ Flags flags;
|
||||
};
|
||||
static_assert(sizeof(InParameter) == 0x10, "BehaviorInfo::InParameter has the wrong size!");
|
||||
|
||||
struct OutStatus {
|
||||
/* 0x00 */ std::array<ErrorInfo, MaxErrors> errors;
|
||||
/* 0xA0 */ u32 error_count;
|
||||
/* 0xA4 */ char unkA4[0xC];
|
||||
};
|
||||
static_assert(sizeof(OutStatus) == 0xB0, "BehaviorInfo::OutStatus has the wrong size!");
|
||||
|
||||
BehaviorInfo();
|
||||
|
||||
/**
|
||||
* Get the host revision as a number.
|
||||
*
|
||||
* @return The host revision.
|
||||
*/
|
||||
u32 GetProcessRevisionNum() const;
|
||||
|
||||
/**
|
||||
* Get the host revision in chars, e.g REV8.
|
||||
* Rev 10 and higher use the ascii characters above 9.
|
||||
* E.g:
|
||||
* Rev 10 = REV:
|
||||
* Rev 11 = REV;
|
||||
*
|
||||
* @return The host revision.
|
||||
*/
|
||||
u32 GetProcessRevision() const;
|
||||
|
||||
/**
|
||||
* Get the user revision as a number.
|
||||
*
|
||||
* @return The user revision.
|
||||
*/
|
||||
u32 GetUserRevisionNum() const;
|
||||
|
||||
/**
|
||||
* Get the user revision in chars, e.g REV8.
|
||||
* Rev 10 and higher use the ascii characters above 9. REV: REV; etc.
|
||||
*
|
||||
* @return The user revision.
|
||||
*/
|
||||
u32 GetUserRevision() const;
|
||||
|
||||
/**
|
||||
* Set the user revision.
|
||||
*
|
||||
* @param user_revision - The user's revision.
|
||||
*/
|
||||
void SetUserLibRevision(u32 user_revision);
|
||||
|
||||
/**
|
||||
* Clear the current error count.
|
||||
*/
|
||||
void ClearError();
|
||||
|
||||
/**
|
||||
* Append an error to the error list.
|
||||
*
|
||||
* @param error - The new error.
|
||||
*/
|
||||
void AppendError(const ErrorInfo& error);
|
||||
|
||||
/**
|
||||
* Copy errors to the given output container.
|
||||
*
|
||||
* @param out_errors - Output container to receive the errors.
|
||||
* @param out_count - The number of errors written.
|
||||
*/
|
||||
void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const;
|
||||
|
||||
/**
|
||||
* Update the behaviour flags.
|
||||
*
|
||||
* @param flags - New flags to use.
|
||||
*/
|
||||
void UpdateFlags(Flags flags);
|
||||
|
||||
/**
|
||||
* Check if memory pools can be forcibly mapped.
|
||||
*
|
||||
* @return True if enabled, otherwise false.
|
||||
*/
|
||||
bool IsMemoryForceMappingEnabled() const;
|
||||
|
||||
/**
|
||||
* Check if the ADPCM context bug is fixed.
|
||||
* The ADPCM context was not being sent to the AudioRenderer, leading to incorrect scaling being
|
||||
* used.
|
||||
*
|
||||
* @return True if fixed, otherwise false.
|
||||
*/
|
||||
bool IsAdpcmLoopContextBugFixed() const;
|
||||
|
||||
/**
|
||||
* Check if the splitter is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsSplitterSupported() const;
|
||||
|
||||
/**
|
||||
* Check if the splitter bug is fixed.
|
||||
* Update is given the wrong number of splitter destinations, leading to invalid data
|
||||
* being processed.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsSplitterBugFixed() const;
|
||||
|
||||
/**
|
||||
* Check if effects version 2 are supported.
|
||||
* This gives support for returning effect states from the AudioRenderer, currently only used
|
||||
* for Limiter statistics.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsEffectInfoVersion2Supported() const;
|
||||
|
||||
/**
|
||||
* Check if a variadic command buffer is supported.
|
||||
* As of Rev 5 with the added optional performance metric logging, the command
|
||||
* buffer can be a variable size, so take that into account for calcualting its size.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsVariadicCommandBufferSizeSupported() const;
|
||||
|
||||
/**
|
||||
* Check if wave buffers version 2 are supported.
|
||||
* See WaveBufferVersion1 and WaveBufferVersion2.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsWaveBufferVer2Supported() const;
|
||||
|
||||
/**
|
||||
* Check if long size pre delay is supported.
|
||||
* This allows a longer initial delay time for the Reverb command.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsLongSizePreDelaySupported() const;
|
||||
|
||||
/**
|
||||
* Check if the command time estimator version 2 is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsCommandProcessingTimeEstimatorVersion2Supported() const;
|
||||
|
||||
/**
|
||||
* Check if the command time estimator version 3 is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsCommandProcessingTimeEstimatorVersion3Supported() const;
|
||||
|
||||
/**
|
||||
* Check if the command time estimator version 4 is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsCommandProcessingTimeEstimatorVersion4Supported() const;
|
||||
|
||||
/**
|
||||
* Check if the command time estimator version 5 is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsCommandProcessingTimeEstimatorVersion5Supported() const;
|
||||
|
||||
/**
|
||||
* Check if the AudioRenderer can use up to 70% of the allocated processing timeslice.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
|
||||
|
||||
/**
|
||||
* Check if the AudioRenderer can use up to 75% of the allocated processing timeslice.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
|
||||
|
||||
/**
|
||||
* Check if the AudioRenderer can use up to 80% of the allocated processing timeslice.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
|
||||
|
||||
/**
|
||||
* Check if voice flushing is supported
|
||||
* This allowws low-priority voices to be dropped if the AudioRenderer is running behind.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsFlushVoiceWaveBuffersSupported() const;
|
||||
|
||||
/**
|
||||
* Check if counting the number of elapsed frames is supported.
|
||||
* This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
|
||||
* processed a command list.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsElapsedFrameCountSupported() const;
|
||||
|
||||
/**
|
||||
* Check if performance metrics version 2 are supported.
|
||||
* This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
|
||||
* (Unused?).
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsPerformanceMetricsDataFormatVersion2Supported() const;
|
||||
|
||||
/**
|
||||
* Get the supported performance metrics version.
|
||||
* Version 2 logs some extra fields in output, such as number of voices dropped,
|
||||
* processing start time, if the AudioRenderer exceeded its time, etc.
|
||||
*
|
||||
* @return Version supported, either 1 or 2.
|
||||
*/
|
||||
size_t GetPerformanceMetricsDataFormat() const;
|
||||
|
||||
/**
|
||||
* Check if skipping voice pitch and sample rate conversion is supported.
|
||||
* This speeds up the data source commands by skipping resampling if unwanted.
|
||||
* See AudioCore::AudioRenderer::DecodeFromWaveBuffers
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsVoicePitchAndSrcSkippedSupported() const;
|
||||
|
||||
/**
|
||||
* Check if resetting played sample count at loop points is supported.
|
||||
* This resets the number of samples played in a voice state when a loop point is reached.
|
||||
* See AudioCore::AudioRenderer::DecodeFromWaveBuffers
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
|
||||
|
||||
/**
|
||||
* Check if the clear state bug for biquad filters is fixed.
|
||||
* The biquad state was not marked as needing re-initialisation when the effect was updated, it
|
||||
* was only initialized once with a new effect.
|
||||
*
|
||||
* @return True if fixed, otherwise false.
|
||||
*/
|
||||
bool IsBiquadFilterEffectStateClearBugFixed() const;
|
||||
|
||||
/**
|
||||
* Check if Q23 precision is supported for fixed point.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsVolumeMixParameterPrecisionQ23Supported() const;
|
||||
|
||||
/**
|
||||
* Check if float processing for biuad filters is supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool UseBiquadFilterFloatProcessing() const;
|
||||
|
||||
/**
|
||||
* Check if dirty-only mix updates are supported.
|
||||
* This saves a lot of buffer size as mixes can be large and not change much.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsMixInParameterDirtyOnlyUpdateSupported() const;
|
||||
|
||||
/**
|
||||
* Check if multi-tap biquad filters are supported.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool UseMultiTapBiquadFilterProcessing() const;
|
||||
|
||||
/**
|
||||
* Check if device api version 2 is supported.
|
||||
* In the SDK but not in any sysmodule? Not sure, left here for completeness anyway.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsDeviceApiVersion2Supported() const;
|
||||
|
||||
/**
|
||||
* Check if new channel mappings are used for Delay commands.
|
||||
* Older commands used:
|
||||
* front left/front right/back left/back right/center/lfe
|
||||
* Whereas everywhere else in the code uses:
|
||||
* front left/front right/center/lfe/back left/back right
|
||||
* This corrects that and makes everything standardised.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsDelayChannelMappingChanged() const;
|
||||
|
||||
/**
|
||||
* Check if new channel mappings are used for Reverb commands.
|
||||
* Older commands used:
|
||||
* front left/front right/back left/back right/center/lfe
|
||||
* Whereas everywhere else in the code uses:
|
||||
* front left/front right/center/lfe/back left/back right
|
||||
* This corrects that and makes everything standardised.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsReverbChannelMappingChanged() const;
|
||||
|
||||
/**
|
||||
* Check if new channel mappings are used for I3dl2Reverb commands.
|
||||
* Older commands used:
|
||||
* front left/front right/back left/back right/center/lfe
|
||||
* Whereas everywhere else in the code uses:
|
||||
* front left/front right/center/lfe/back left/back right
|
||||
* This corrects that and makes everything standardised.
|
||||
*
|
||||
* @return True if supported, otherwise false.
|
||||
*/
|
||||
bool IsI3dl2ReverbChannelMappingChanged() const;
|
||||
|
||||
/// Host version
|
||||
u32 process_revision;
|
||||
/// User version
|
||||
u32 user_revision{};
|
||||
/// Behaviour flags
|
||||
Flags flags{};
|
||||
/// Errors generated and reported during Update
|
||||
std::array<ErrorInfo, MaxErrors> errors{};
|
||||
/// Error count
|
||||
u32 error_count{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,205 +1,205 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
class BehaviorInfo;
|
||||
class VoiceContext;
|
||||
class MixContext;
|
||||
class SinkContext;
|
||||
class SplitterContext;
|
||||
class EffectContext;
|
||||
class MemoryPoolInfo;
|
||||
class PerformanceManager;
|
||||
|
||||
class InfoUpdater {
|
||||
struct UpdateDataHeader {
|
||||
explicit UpdateDataHeader(u32 revision_) : revision{revision_} {}
|
||||
|
||||
/* 0x00 */ u32 revision;
|
||||
/* 0x04 */ u32 behaviour_size{};
|
||||
/* 0x08 */ u32 memory_pool_size{};
|
||||
/* 0x0C */ u32 voices_size{};
|
||||
/* 0x10 */ u32 voice_resources_size{};
|
||||
/* 0x14 */ u32 effects_size{};
|
||||
/* 0x18 */ u32 mix_size{};
|
||||
/* 0x1C */ u32 sinks_size{};
|
||||
/* 0x20 */ u32 performance_buffer_size{};
|
||||
/* 0x24 */ char unk24[4];
|
||||
/* 0x28 */ u32 render_info_size{};
|
||||
/* 0x2C */ char unk2C[0x10];
|
||||
/* 0x3C */ u32 size{sizeof(UpdateDataHeader)};
|
||||
};
|
||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has the wrong size!");
|
||||
|
||||
public:
|
||||
explicit InfoUpdater(std::span<const u8> input, std::span<u8> output, u32 process_handle,
|
||||
BehaviorInfo& behaviour);
|
||||
|
||||
/**
|
||||
* Update the voice channel resources.
|
||||
*
|
||||
* @param voice_context - Voice context to update.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateVoiceChannelResources(VoiceContext& voice_context);
|
||||
|
||||
/**
|
||||
* Update voices.
|
||||
*
|
||||
* @param voice_context - Voice context to update.
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateVoices(VoiceContext& voice_context, std::span<MemoryPoolInfo> memory_pools,
|
||||
u32 memory_pool_count);
|
||||
|
||||
/**
|
||||
* Update effects.
|
||||
*
|
||||
* @param effect_context - Effect context to update.
|
||||
* @param renderer_active - Whether the AudioRenderer is active.
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateEffects(EffectContext& effect_context, bool renderer_active,
|
||||
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
|
||||
|
||||
/**
|
||||
* Update mixes.
|
||||
*
|
||||
* @param mix_context - Mix context to update.
|
||||
* @param mix_buffer_count - Number of mix buffers.
|
||||
* @param effect_context - Effect context to update effort order.
|
||||
* @param splitter_context - Splitter context for the mixes.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateMixes(MixContext& mix_context, u32 mix_buffer_count, EffectContext& effect_context,
|
||||
SplitterContext& splitter_context);
|
||||
|
||||
/**
|
||||
* Update sinks.
|
||||
*
|
||||
* @param sink_context - Sink context to update.
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
|
||||
u32 memory_pool_count);
|
||||
|
||||
/**
|
||||
* Update memory pools.
|
||||
*
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
|
||||
|
||||
/**
|
||||
* Update the performance buffer.
|
||||
*
|
||||
* @param output - Output buffer for performance metrics.
|
||||
* @param output_size - Output buffer size.
|
||||
* @param performance_manager - Performance manager..
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdatePerformanceBuffer(std::span<u8> output, u64 output_size,
|
||||
PerformanceManager* performance_manager);
|
||||
|
||||
/**
|
||||
* Update behaviour.
|
||||
*
|
||||
* @param behaviour - Behaviour to update.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateBehaviorInfo(BehaviorInfo& behaviour);
|
||||
|
||||
/**
|
||||
* Update errors.
|
||||
*
|
||||
* @param behaviour - Behaviour to update.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateErrorInfo(const BehaviorInfo& behaviour);
|
||||
|
||||
/**
|
||||
* Update splitter.
|
||||
*
|
||||
* @param splitter_context - Splitter context to update.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateSplitterInfo(SplitterContext& splitter_context);
|
||||
|
||||
/**
|
||||
* Update renderer info.
|
||||
*
|
||||
* @param elapsed_frames - Number of elapsed frames.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateRendererInfo(u64 elapsed_frames);
|
||||
|
||||
/**
|
||||
* Check that the input.output sizes match their expected values.
|
||||
*
|
||||
* @return Result code.
|
||||
*/
|
||||
Result CheckConsumedSize();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Update effects version 1.
|
||||
*
|
||||
* @param effect_context - Effect context to update.
|
||||
* @param renderer_active - Is the AudioRenderer active?
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateEffectsVersion1(EffectContext& effect_context, bool renderer_active,
|
||||
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
|
||||
|
||||
/**
|
||||
* Update effects version 2.
|
||||
*
|
||||
* @param effect_context - Effect context to update.
|
||||
* @param renderer_active - Is the AudioRenderer active?
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateEffectsVersion2(EffectContext& effect_context, bool renderer_active,
|
||||
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
|
||||
|
||||
/// Input buffer
|
||||
u8 const* input;
|
||||
/// Input buffer start
|
||||
std::span<const u8> input_origin;
|
||||
/// Output buffer start
|
||||
u8* output;
|
||||
/// Output buffer start
|
||||
std::span<u8> output_origin;
|
||||
/// Input header
|
||||
const UpdateDataHeader* in_header;
|
||||
/// Output header
|
||||
UpdateDataHeader* out_header;
|
||||
/// Expected input size, see CheckConsumedSize
|
||||
u64 expected_input_size;
|
||||
/// Expected output size, see CheckConsumedSize
|
||||
u64 expected_output_size;
|
||||
/// Unused
|
||||
u32 process_handle;
|
||||
/// Behaviour
|
||||
BehaviorInfo& behaviour;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
class BehaviorInfo;
|
||||
class VoiceContext;
|
||||
class MixContext;
|
||||
class SinkContext;
|
||||
class SplitterContext;
|
||||
class EffectContext;
|
||||
class MemoryPoolInfo;
|
||||
class PerformanceManager;
|
||||
|
||||
class InfoUpdater {
|
||||
struct UpdateDataHeader {
|
||||
explicit UpdateDataHeader(u32 revision_) : revision{revision_} {}
|
||||
|
||||
/* 0x00 */ u32 revision;
|
||||
/* 0x04 */ u32 behaviour_size{};
|
||||
/* 0x08 */ u32 memory_pool_size{};
|
||||
/* 0x0C */ u32 voices_size{};
|
||||
/* 0x10 */ u32 voice_resources_size{};
|
||||
/* 0x14 */ u32 effects_size{};
|
||||
/* 0x18 */ u32 mix_size{};
|
||||
/* 0x1C */ u32 sinks_size{};
|
||||
/* 0x20 */ u32 performance_buffer_size{};
|
||||
/* 0x24 */ char unk24[4];
|
||||
/* 0x28 */ u32 render_info_size{};
|
||||
/* 0x2C */ char unk2C[0x10];
|
||||
/* 0x3C */ u32 size{sizeof(UpdateDataHeader)};
|
||||
};
|
||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has the wrong size!");
|
||||
|
||||
public:
|
||||
explicit InfoUpdater(std::span<const u8> input, std::span<u8> output, u32 process_handle,
|
||||
BehaviorInfo& behaviour);
|
||||
|
||||
/**
|
||||
* Update the voice channel resources.
|
||||
*
|
||||
* @param voice_context - Voice context to update.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateVoiceChannelResources(VoiceContext& voice_context);
|
||||
|
||||
/**
|
||||
* Update voices.
|
||||
*
|
||||
* @param voice_context - Voice context to update.
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateVoices(VoiceContext& voice_context, std::span<MemoryPoolInfo> memory_pools,
|
||||
u32 memory_pool_count);
|
||||
|
||||
/**
|
||||
* Update effects.
|
||||
*
|
||||
* @param effect_context - Effect context to update.
|
||||
* @param renderer_active - Whether the AudioRenderer is active.
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateEffects(EffectContext& effect_context, bool renderer_active,
|
||||
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
|
||||
|
||||
/**
|
||||
* Update mixes.
|
||||
*
|
||||
* @param mix_context - Mix context to update.
|
||||
* @param mix_buffer_count - Number of mix buffers.
|
||||
* @param effect_context - Effect context to update effort order.
|
||||
* @param splitter_context - Splitter context for the mixes.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateMixes(MixContext& mix_context, u32 mix_buffer_count, EffectContext& effect_context,
|
||||
SplitterContext& splitter_context);
|
||||
|
||||
/**
|
||||
* Update sinks.
|
||||
*
|
||||
* @param sink_context - Sink context to update.
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
|
||||
u32 memory_pool_count);
|
||||
|
||||
/**
|
||||
* Update memory pools.
|
||||
*
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
|
||||
|
||||
/**
|
||||
* Update the performance buffer.
|
||||
*
|
||||
* @param output - Output buffer for performance metrics.
|
||||
* @param output_size - Output buffer size.
|
||||
* @param performance_manager - Performance manager..
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdatePerformanceBuffer(std::span<u8> output, u64 output_size,
|
||||
PerformanceManager* performance_manager);
|
||||
|
||||
/**
|
||||
* Update behaviour.
|
||||
*
|
||||
* @param behaviour - Behaviour to update.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateBehaviorInfo(BehaviorInfo& behaviour);
|
||||
|
||||
/**
|
||||
* Update errors.
|
||||
*
|
||||
* @param behaviour - Behaviour to update.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateErrorInfo(const BehaviorInfo& behaviour);
|
||||
|
||||
/**
|
||||
* Update splitter.
|
||||
*
|
||||
* @param splitter_context - Splitter context to update.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateSplitterInfo(SplitterContext& splitter_context);
|
||||
|
||||
/**
|
||||
* Update renderer info.
|
||||
*
|
||||
* @param elapsed_frames - Number of elapsed frames.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateRendererInfo(u64 elapsed_frames);
|
||||
|
||||
/**
|
||||
* Check that the input.output sizes match their expected values.
|
||||
*
|
||||
* @return Result code.
|
||||
*/
|
||||
Result CheckConsumedSize();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Update effects version 1.
|
||||
*
|
||||
* @param effect_context - Effect context to update.
|
||||
* @param renderer_active - Is the AudioRenderer active?
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateEffectsVersion1(EffectContext& effect_context, bool renderer_active,
|
||||
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
|
||||
|
||||
/**
|
||||
* Update effects version 2.
|
||||
*
|
||||
* @param effect_context - Effect context to update.
|
||||
* @param renderer_active - Is the AudioRenderer active?
|
||||
* @param memory_pools - Memory pools to use for these voices.
|
||||
* @param memory_pool_count - Number of memory pools.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result UpdateEffectsVersion2(EffectContext& effect_context, bool renderer_active,
|
||||
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
|
||||
|
||||
/// Input buffer
|
||||
u8 const* input;
|
||||
/// Input buffer start
|
||||
std::span<const u8> input_origin;
|
||||
/// Output buffer start
|
||||
u8* output;
|
||||
/// Output buffer start
|
||||
std::span<u8> output_origin;
|
||||
/// Input header
|
||||
const UpdateDataHeader* in_header;
|
||||
/// Output header
|
||||
UpdateDataHeader* out_header;
|
||||
/// Expected input size, see CheckConsumedSize
|
||||
u64 expected_input_size;
|
||||
/// Expected output size, see CheckConsumedSize
|
||||
u64 expected_output_size;
|
||||
/// Unused
|
||||
u32 process_handle;
|
||||
/// Behaviour
|
||||
BehaviorInfo& behaviour;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,468 +1,468 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/command/commands.h"
|
||||
#include "audio_core/renderer/effect/light_limiter.h"
|
||||
#include "audio_core/renderer/performance/performance_manager.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
struct UpsamplerInfo;
|
||||
struct VoiceState;
|
||||
class EffectInfoBase;
|
||||
class ICommandProcessingTimeEstimator;
|
||||
class MixInfo;
|
||||
class MemoryPoolInfo;
|
||||
class SinkInfoBase;
|
||||
class VoiceInfo;
|
||||
|
||||
/**
|
||||
* Utility functions to generate and add commands into the current command list.
|
||||
*/
|
||||
class CommandBuffer {
|
||||
public:
|
||||
/**
|
||||
* Generate a PCM s16 version 1 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
|
||||
VoiceInfo& voice_info, const VoiceState& voice_state,
|
||||
s16 buffer_count, s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a PCM s16 version 2 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info,
|
||||
const VoiceState& voice_state, s16 buffer_count,
|
||||
s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a PCM f32 version 1 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
|
||||
VoiceInfo& voice_info, const VoiceState& voice_state,
|
||||
s16 buffer_count, s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a PCM f32 version 2 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info,
|
||||
const VoiceState& voice_state, s16 buffer_count,
|
||||
s8 channel);
|
||||
|
||||
/**
|
||||
* Generate an ADPCM version 1 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
|
||||
VoiceInfo& voice_info, const VoiceState& voice_state,
|
||||
s16 buffer_count, s8 channel);
|
||||
|
||||
/**
|
||||
* Generate an ADPCM version 2 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info,
|
||||
const VoiceState& voice_state, s16 buffer_count, s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a volume command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer index to generate this command at.
|
||||
* @param input_index - Channel index and mix buffer offset for this command.
|
||||
* @param volume - Mix volume added to the input samples.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume,
|
||||
u8 precision);
|
||||
|
||||
/**
|
||||
* Generate a volume ramp command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command takes its volumes from.
|
||||
* @param buffer_count - Number of active mix buffers, command will generate at this index.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count,
|
||||
u8 precision);
|
||||
|
||||
/**
|
||||
* Generate a biquad filter command from a voice, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command takes biquad parameters from.
|
||||
* @param voice_state - Used by the AudioRenderer to track previous samples.
|
||||
* @param buffer_count - Number of active mix buffers,
|
||||
* command will generate at this index + channel.
|
||||
* @param channel - Channel index for this filter to work on.
|
||||
* @param biquad_index - Which biquad filter to use for this command (0-1).
|
||||
* @param use_float_processing - Should int or float processing be used?
|
||||
*/
|
||||
void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
|
||||
const VoiceState& voice_state, s16 buffer_count, s8 channel,
|
||||
u32 biquad_index, bool use_float_processing);
|
||||
|
||||
/**
|
||||
* Generate a biquad filter effect command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - The effect info this command takes biquad parameters from.
|
||||
* @param buffer_offset - Mix buffer offset this command will use,
|
||||
* command will generate at this index + channel.
|
||||
* @param channel - Channel index for this filter to work on.
|
||||
* @param needs_init - True if the biquad state needs initialisation.
|
||||
* @param use_float_processing - Should int or float processing be used?
|
||||
*/
|
||||
void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
|
||||
s8 channel, bool needs_init, bool use_float_processing);
|
||||
|
||||
/**
|
||||
* Generate a mix command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param input_index - Input mix buffer index for this command.
|
||||
* Added to the buffer offset.
|
||||
* @param output_index - Output mix buffer index for this command.
|
||||
* Added to the buffer offset.
|
||||
* @param buffer_offset - Mix buffer offset this command will use.
|
||||
* @param volume - Volume to be applied to the input.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset,
|
||||
f32 volume, u8 precision);
|
||||
|
||||
/**
|
||||
* Generate a mix ramp command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param input_index - Input mix buffer index for this command.
|
||||
* Added to buffer_count.
|
||||
* @param output_index - Output mix buffer index for this command.
|
||||
* Added to buffer_count.
|
||||
* @param volume - Current mix volume used for calculating the ramp.
|
||||
* @param prev_volume - Previous mix volume, used for calculating the ramp,
|
||||
* also applied to the input.
|
||||
* @param prev_samples - Previous sample buffer. Used for depopping.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
|
||||
f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision);
|
||||
|
||||
/**
|
||||
* Generate a mix ramp grouped command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param input_index - Input mix buffer index for this command.
|
||||
* Added to buffer_count.
|
||||
* @param output_index - Output mix buffer index for this command.
|
||||
* Added to buffer_count.
|
||||
* @param volumes - Current mix volumes used for calculating the ramp.
|
||||
* @param prev_volumes - Previous mix volumes, used for calculating the ramp,
|
||||
* also applied to the input.
|
||||
* @param prev_samples - Previous sample buffer. Used for depopping.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
|
||||
s16 output_index, std::span<const f32> volumes,
|
||||
std::span<const f32> prev_volumes, CpuAddr prev_samples,
|
||||
u8 precision);
|
||||
|
||||
/**
|
||||
* Generate a depop prepare command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_state - State to track the previous depop samples for each mix buffer.
|
||||
* @param buffer - State to track the current depop samples for each mix buffer.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param buffer_offset - Base mix buffer index to generate the channel depops at.
|
||||
* @param was_playing - Command only needs to work if the voice was previously playing.
|
||||
*/
|
||||
void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state,
|
||||
std::span<const s32> buffer, s16 buffer_count,
|
||||
s16 buffer_offset, bool was_playing);
|
||||
|
||||
/**
|
||||
* Generate a depop command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param mix_info - Mix info to get the buffer count and base offsets from.
|
||||
* @param depop_buffer - Buffer of current depop sample values to be added to the input
|
||||
* channels.
|
||||
*/
|
||||
void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info,
|
||||
std::span<const s32> depop_buffer);
|
||||
|
||||
/**
|
||||
* Generate a delay command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - Delay effect info to generate this command from.
|
||||
* @param buffer_offset - Base mix buffer offset to apply the apply the delay.
|
||||
*/
|
||||
void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
|
||||
|
||||
/**
|
||||
* Generate an upsample command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to upsample.
|
||||
* @param upsampler_info - Upsampler info to control the upsampling.
|
||||
* @param input_count - Number of input channels to upsample.
|
||||
* @param inputs - Input mix buffer indexes.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param sample_count - Source sample count of the input.
|
||||
* @param sample_rate - Source sample rate of the input.
|
||||
*/
|
||||
void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info,
|
||||
u32 input_count, std::span<const s8> inputs, s16 buffer_count,
|
||||
u32 sample_count, u32 sample_rate);
|
||||
|
||||
/**
|
||||
* Generate a downmix 6 -> 2 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param inputs - Input mix buffer indexes.
|
||||
* @param buffer_offset - Base mix buffer offset of the channels to downmix.
|
||||
* @param downmix_coeff - Downmixing coefficients.
|
||||
*/
|
||||
void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset,
|
||||
std::span<const f32> downmix_coeff);
|
||||
|
||||
/**
|
||||
* Generate an aux buffer command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - Aux effect info to generate this command from.
|
||||
* @param input_index - Input mix buffer index for this command.
|
||||
* Added to buffer_offset.
|
||||
* @param output_index - Output mix buffer index for this command.
|
||||
* Added to buffer_offset.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param update_count - Number of samples to write back to the game as updated, can be 0.
|
||||
* @param count_max - Maximum number of samples to read or write.
|
||||
* @param write_offset - Current read or write offset within the buffer.
|
||||
*/
|
||||
void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
|
||||
s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max,
|
||||
u32 write_offset);
|
||||
|
||||
/**
|
||||
* Generate a device sink command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param sink_info - The sink_info to generate this command from.
|
||||
* @param session_id - System session id this command is generated from.
|
||||
* @param samples_buffer - The buffer to be sent to the sink if upsampling is not used.
|
||||
*/
|
||||
void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
|
||||
u32 session_id, std::span<s32> samples_buffer);
|
||||
|
||||
/**
|
||||
* Generate a circular buffer sink command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param sink_info - The sink_info to generate this command from.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
*/
|
||||
void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset);
|
||||
|
||||
/**
|
||||
* Generate a reverb command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - Reverb effect info to generate this command from.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb
|
||||
* begins?
|
||||
*/
|
||||
void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
|
||||
bool long_size_pre_delay_supported);
|
||||
|
||||
/**
|
||||
* Generate an I3DL2 reverb command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - I3DL2Reverb effect info to generate this command from.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
*/
|
||||
void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
|
||||
|
||||
/**
|
||||
* Generate a performance command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param state - State of the performance.
|
||||
* @param entry_addresses - The addresses to be filled in by the AudioRenderer.
|
||||
*/
|
||||
void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
|
||||
const PerformanceEntryAddresses& entry_addresses);
|
||||
|
||||
/**
|
||||
* Generate a clear mix command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
*/
|
||||
void GenerateClearMixCommand(s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a copy mix command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - BiquadFilter effect info to generate this command from.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param channel - Index to the effect's parameters input indexes for this command.
|
||||
*/
|
||||
void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
|
||||
s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a light limiter version 1 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param parameter - Effect parameter to generate from.
|
||||
* @param state - State used by the AudioRenderer between commands.
|
||||
* @param enabled - Is this command enabled?
|
||||
* @param workbuffer - Game-supplied memory for the state.
|
||||
*/
|
||||
void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
|
||||
const LightLimiterInfo::ParameterVersion1& parameter,
|
||||
const LightLimiterInfo::State& state, bool enabled,
|
||||
CpuAddr workbuffer);
|
||||
|
||||
/**
|
||||
* Generate a light limiter version 2 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param parameter - Effect parameter to generate from.
|
||||
* @param statistics - Statistics reported by the AudioRenderer on the limiter's state.
|
||||
* @param state - State used by the AudioRenderer between commands.
|
||||
* @param enabled - Is this command enabled?
|
||||
* @param workbuffer - Game-supplied memory for the state.
|
||||
*/
|
||||
void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
|
||||
const LightLimiterInfo::ParameterVersion2& parameter,
|
||||
const LightLimiterInfo::StatisticsInternal& statistics,
|
||||
const LightLimiterInfo::State& state, bool enabled,
|
||||
CpuAddr workbuffer);
|
||||
|
||||
/**
|
||||
* Generate a multitap biquad filter command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command takes biquad parameters from.
|
||||
* @param voice_state - Used by the AudioRenderer to track previous samples.
|
||||
* @param buffer_count - Number of active mix buffers,
|
||||
* command will generate at this index + channel.
|
||||
* @param channel - Channel index for this filter to work on.
|
||||
*/
|
||||
void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
|
||||
const VoiceState& voice_state, s16 buffer_count,
|
||||
s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a capture command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - Capture effect info to generate this command from.
|
||||
* @param input_index - Input mix buffer index for this command.
|
||||
* Added to buffer_offset.
|
||||
* @param output_index - Output mix buffer index for this command (unused).
|
||||
* Added to buffer_offset.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param update_count - Number of samples to write back to the game as updated, can be 0.
|
||||
* @param count_max - Maximum number of samples to read or write.
|
||||
* @param write_offset - Current read or write offset within the buffer.
|
||||
*/
|
||||
void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
|
||||
s16 output_index, s16 buffer_offset, u32 update_count,
|
||||
u32 count_max, u32 write_offset);
|
||||
|
||||
/**
|
||||
* Generate a compressor command, adding it to the command list.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Capture effect info to generate this command from.
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
*/
|
||||
void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
|
||||
|
||||
/// Command list buffer generated commands will be added to
|
||||
std::span<u8> command_list{};
|
||||
/// Input sample count, unused
|
||||
u32 sample_count{};
|
||||
/// Input sample rate, unused
|
||||
u32 sample_rate{};
|
||||
/// Current size of the command buffer
|
||||
u64 size{};
|
||||
/// Current number of commands added
|
||||
u32 count{};
|
||||
/// Current estimated processing time for all commands
|
||||
u32 estimated_process_time{};
|
||||
/// Used for mapping buffers for the AudioRenderer
|
||||
MemoryPoolInfo* memory_pool{};
|
||||
/// Used for estimating command process times
|
||||
ICommandProcessingTimeEstimator* time_estimator{};
|
||||
/// Used to check which rendering features are currently enabled
|
||||
BehaviorInfo* behavior{};
|
||||
|
||||
private:
|
||||
template <typename T, CommandId Id>
|
||||
T& GenerateStart(const s32 node_id);
|
||||
template <typename T>
|
||||
void GenerateEnd(T& cmd);
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/command/commands.h"
|
||||
#include "audio_core/renderer/effect/light_limiter.h"
|
||||
#include "audio_core/renderer/performance/performance_manager.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
struct UpsamplerInfo;
|
||||
struct VoiceState;
|
||||
class EffectInfoBase;
|
||||
class ICommandProcessingTimeEstimator;
|
||||
class MixInfo;
|
||||
class MemoryPoolInfo;
|
||||
class SinkInfoBase;
|
||||
class VoiceInfo;
|
||||
|
||||
/**
|
||||
* Utility functions to generate and add commands into the current command list.
|
||||
*/
|
||||
class CommandBuffer {
|
||||
public:
|
||||
/**
|
||||
* Generate a PCM s16 version 1 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
|
||||
VoiceInfo& voice_info, const VoiceState& voice_state,
|
||||
s16 buffer_count, s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a PCM s16 version 2 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info,
|
||||
const VoiceState& voice_state, s16 buffer_count,
|
||||
s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a PCM f32 version 1 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
|
||||
VoiceInfo& voice_info, const VoiceState& voice_state,
|
||||
s16 buffer_count, s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a PCM f32 version 2 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info,
|
||||
const VoiceState& voice_state, s16 buffer_count,
|
||||
s8 channel);
|
||||
|
||||
/**
|
||||
* Generate an ADPCM version 1 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
|
||||
VoiceInfo& voice_info, const VoiceState& voice_state,
|
||||
s16 buffer_count, s8 channel);
|
||||
|
||||
/**
|
||||
* Generate an ADPCM version 2 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command is generated from.
|
||||
* @param voice_state - The voice state the DSP will use for this command.
|
||||
* @param buffer_count - Number of mix buffers in use,
|
||||
* data will be read into this index + channel.
|
||||
* @param channel - Channel index for this command.
|
||||
*/
|
||||
void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info,
|
||||
const VoiceState& voice_state, s16 buffer_count, s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a volume command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer index to generate this command at.
|
||||
* @param input_index - Channel index and mix buffer offset for this command.
|
||||
* @param volume - Mix volume added to the input samples.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume,
|
||||
u8 precision);
|
||||
|
||||
/**
|
||||
* Generate a volume ramp command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command takes its volumes from.
|
||||
* @param buffer_count - Number of active mix buffers, command will generate at this index.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count,
|
||||
u8 precision);
|
||||
|
||||
/**
|
||||
* Generate a biquad filter command from a voice, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command takes biquad parameters from.
|
||||
* @param voice_state - Used by the AudioRenderer to track previous samples.
|
||||
* @param buffer_count - Number of active mix buffers,
|
||||
* command will generate at this index + channel.
|
||||
* @param channel - Channel index for this filter to work on.
|
||||
* @param biquad_index - Which biquad filter to use for this command (0-1).
|
||||
* @param use_float_processing - Should int or float processing be used?
|
||||
*/
|
||||
void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
|
||||
const VoiceState& voice_state, s16 buffer_count, s8 channel,
|
||||
u32 biquad_index, bool use_float_processing);
|
||||
|
||||
/**
|
||||
* Generate a biquad filter effect command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - The effect info this command takes biquad parameters from.
|
||||
* @param buffer_offset - Mix buffer offset this command will use,
|
||||
* command will generate at this index + channel.
|
||||
* @param channel - Channel index for this filter to work on.
|
||||
* @param needs_init - True if the biquad state needs initialisation.
|
||||
* @param use_float_processing - Should int or float processing be used?
|
||||
*/
|
||||
void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
|
||||
s8 channel, bool needs_init, bool use_float_processing);
|
||||
|
||||
/**
|
||||
* Generate a mix command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param input_index - Input mix buffer index for this command.
|
||||
* Added to the buffer offset.
|
||||
* @param output_index - Output mix buffer index for this command.
|
||||
* Added to the buffer offset.
|
||||
* @param buffer_offset - Mix buffer offset this command will use.
|
||||
* @param volume - Volume to be applied to the input.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset,
|
||||
f32 volume, u8 precision);
|
||||
|
||||
/**
|
||||
* Generate a mix ramp command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param input_index - Input mix buffer index for this command.
|
||||
* Added to buffer_count.
|
||||
* @param output_index - Output mix buffer index for this command.
|
||||
* Added to buffer_count.
|
||||
* @param volume - Current mix volume used for calculating the ramp.
|
||||
* @param prev_volume - Previous mix volume, used for calculating the ramp,
|
||||
* also applied to the input.
|
||||
* @param prev_samples - Previous sample buffer. Used for depopping.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
|
||||
f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision);
|
||||
|
||||
/**
|
||||
* Generate a mix ramp grouped command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param input_index - Input mix buffer index for this command.
|
||||
* Added to buffer_count.
|
||||
* @param output_index - Output mix buffer index for this command.
|
||||
* Added to buffer_count.
|
||||
* @param volumes - Current mix volumes used for calculating the ramp.
|
||||
* @param prev_volumes - Previous mix volumes, used for calculating the ramp,
|
||||
* also applied to the input.
|
||||
* @param prev_samples - Previous sample buffer. Used for depopping.
|
||||
* @param precision - Number of decimal bits for fixed point operations.
|
||||
*/
|
||||
void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
|
||||
s16 output_index, std::span<const f32> volumes,
|
||||
std::span<const f32> prev_volumes, CpuAddr prev_samples,
|
||||
u8 precision);
|
||||
|
||||
/**
|
||||
* Generate a depop prepare command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_state - State to track the previous depop samples for each mix buffer.
|
||||
* @param buffer - State to track the current depop samples for each mix buffer.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param buffer_offset - Base mix buffer index to generate the channel depops at.
|
||||
* @param was_playing - Command only needs to work if the voice was previously playing.
|
||||
*/
|
||||
void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state,
|
||||
std::span<const s32> buffer, s16 buffer_count,
|
||||
s16 buffer_offset, bool was_playing);
|
||||
|
||||
/**
|
||||
* Generate a depop command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param mix_info - Mix info to get the buffer count and base offsets from.
|
||||
* @param depop_buffer - Buffer of current depop sample values to be added to the input
|
||||
* channels.
|
||||
*/
|
||||
void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info,
|
||||
std::span<const s32> depop_buffer);
|
||||
|
||||
/**
|
||||
* Generate a delay command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - Delay effect info to generate this command from.
|
||||
* @param buffer_offset - Base mix buffer offset to apply the apply the delay.
|
||||
*/
|
||||
void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
|
||||
|
||||
/**
|
||||
* Generate an upsample command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to upsample.
|
||||
* @param upsampler_info - Upsampler info to control the upsampling.
|
||||
* @param input_count - Number of input channels to upsample.
|
||||
* @param inputs - Input mix buffer indexes.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param sample_count - Source sample count of the input.
|
||||
* @param sample_rate - Source sample rate of the input.
|
||||
*/
|
||||
void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info,
|
||||
u32 input_count, std::span<const s8> inputs, s16 buffer_count,
|
||||
u32 sample_count, u32 sample_rate);
|
||||
|
||||
/**
|
||||
* Generate a downmix 6 -> 2 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param inputs - Input mix buffer indexes.
|
||||
* @param buffer_offset - Base mix buffer offset of the channels to downmix.
|
||||
* @param downmix_coeff - Downmixing coefficients.
|
||||
*/
|
||||
void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset,
|
||||
std::span<const f32> downmix_coeff);
|
||||
|
||||
/**
|
||||
* Generate an aux buffer command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - Aux effect info to generate this command from.
|
||||
* @param input_index - Input mix buffer index for this command.
|
||||
* Added to buffer_offset.
|
||||
* @param output_index - Output mix buffer index for this command.
|
||||
* Added to buffer_offset.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param update_count - Number of samples to write back to the game as updated, can be 0.
|
||||
* @param count_max - Maximum number of samples to read or write.
|
||||
* @param write_offset - Current read or write offset within the buffer.
|
||||
*/
|
||||
void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
|
||||
s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max,
|
||||
u32 write_offset);
|
||||
|
||||
/**
|
||||
* Generate a device sink command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param sink_info - The sink_info to generate this command from.
|
||||
* @param session_id - System session id this command is generated from.
|
||||
* @param samples_buffer - The buffer to be sent to the sink if upsampling is not used.
|
||||
*/
|
||||
void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
|
||||
u32 session_id, std::span<s32> samples_buffer);
|
||||
|
||||
/**
|
||||
* Generate a circular buffer sink command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param sink_info - The sink_info to generate this command from.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
*/
|
||||
void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset);
|
||||
|
||||
/**
|
||||
* Generate a reverb command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - Reverb effect info to generate this command from.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb
|
||||
* begins?
|
||||
*/
|
||||
void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
|
||||
bool long_size_pre_delay_supported);
|
||||
|
||||
/**
|
||||
* Generate an I3DL2 reverb command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - I3DL2Reverb effect info to generate this command from.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
*/
|
||||
void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
|
||||
|
||||
/**
|
||||
* Generate a performance command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param state - State of the performance.
|
||||
* @param entry_addresses - The addresses to be filled in by the AudioRenderer.
|
||||
*/
|
||||
void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
|
||||
const PerformanceEntryAddresses& entry_addresses);
|
||||
|
||||
/**
|
||||
* Generate a clear mix command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
*/
|
||||
void GenerateClearMixCommand(s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a copy mix command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - BiquadFilter effect info to generate this command from.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param channel - Index to the effect's parameters input indexes for this command.
|
||||
*/
|
||||
void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
|
||||
s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a light limiter version 1 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param parameter - Effect parameter to generate from.
|
||||
* @param state - State used by the AudioRenderer between commands.
|
||||
* @param enabled - Is this command enabled?
|
||||
* @param workbuffer - Game-supplied memory for the state.
|
||||
*/
|
||||
void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
|
||||
const LightLimiterInfo::ParameterVersion1& parameter,
|
||||
const LightLimiterInfo::State& state, bool enabled,
|
||||
CpuAddr workbuffer);
|
||||
|
||||
/**
|
||||
* Generate a light limiter version 2 command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param parameter - Effect parameter to generate from.
|
||||
* @param statistics - Statistics reported by the AudioRenderer on the limiter's state.
|
||||
* @param state - State used by the AudioRenderer between commands.
|
||||
* @param enabled - Is this command enabled?
|
||||
* @param workbuffer - Game-supplied memory for the state.
|
||||
*/
|
||||
void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
|
||||
const LightLimiterInfo::ParameterVersion2& parameter,
|
||||
const LightLimiterInfo::StatisticsInternal& statistics,
|
||||
const LightLimiterInfo::State& state, bool enabled,
|
||||
CpuAddr workbuffer);
|
||||
|
||||
/**
|
||||
* Generate a multitap biquad filter command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param voice_info - The voice info this command takes biquad parameters from.
|
||||
* @param voice_state - Used by the AudioRenderer to track previous samples.
|
||||
* @param buffer_count - Number of active mix buffers,
|
||||
* command will generate at this index + channel.
|
||||
* @param channel - Channel index for this filter to work on.
|
||||
*/
|
||||
void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
|
||||
const VoiceState& voice_state, s16 buffer_count,
|
||||
s8 channel);
|
||||
|
||||
/**
|
||||
* Generate a capture command, adding it to the command list.
|
||||
*
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
* @param effect_info - Capture effect info to generate this command from.
|
||||
* @param input_index - Input mix buffer index for this command.
|
||||
* Added to buffer_offset.
|
||||
* @param output_index - Output mix buffer index for this command (unused).
|
||||
* Added to buffer_offset.
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param update_count - Number of samples to write back to the game as updated, can be 0.
|
||||
* @param count_max - Maximum number of samples to read or write.
|
||||
* @param write_offset - Current read or write offset within the buffer.
|
||||
*/
|
||||
void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
|
||||
s16 output_index, s16 buffer_offset, u32 update_count,
|
||||
u32 count_max, u32 write_offset);
|
||||
|
||||
/**
|
||||
* Generate a compressor command, adding it to the command list.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Capture effect info to generate this command from.
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
*/
|
||||
void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
|
||||
|
||||
/// Command list buffer generated commands will be added to
|
||||
std::span<u8> command_list{};
|
||||
/// Input sample count, unused
|
||||
u32 sample_count{};
|
||||
/// Input sample rate, unused
|
||||
u32 sample_rate{};
|
||||
/// Current size of the command buffer
|
||||
u64 size{};
|
||||
/// Current number of commands added
|
||||
u32 count{};
|
||||
/// Current estimated processing time for all commands
|
||||
u32 estimated_process_time{};
|
||||
/// Used for mapping buffers for the AudioRenderer
|
||||
MemoryPoolInfo* memory_pool{};
|
||||
/// Used for estimating command process times
|
||||
ICommandProcessingTimeEstimator* time_estimator{};
|
||||
/// Used to check which rendering features are currently enabled
|
||||
BehaviorInfo* behavior{};
|
||||
|
||||
private:
|
||||
template <typename T, CommandId Id>
|
||||
T& GenerateStart(const s32 node_id);
|
||||
template <typename T>
|
||||
void GenerateEnd(T& cmd);
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,349 +1,349 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/command/commands.h"
|
||||
#include "audio_core/renderer/performance/performance_manager.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
struct AudioRendererSystemContext;
|
||||
|
||||
namespace AudioRenderer {
|
||||
class CommandBuffer;
|
||||
struct CommandListHeader;
|
||||
class VoiceContext;
|
||||
class MixContext;
|
||||
class EffectContext;
|
||||
class SplitterContext;
|
||||
class SinkContext;
|
||||
class BehaviorInfo;
|
||||
class VoiceInfo;
|
||||
struct VoiceState;
|
||||
class MixInfo;
|
||||
class SinkInfoBase;
|
||||
|
||||
/**
|
||||
* Generates all commands to build up a command list, which are sent to the AudioRender for
|
||||
* processing.
|
||||
*/
|
||||
class CommandGenerator {
|
||||
public:
|
||||
explicit CommandGenerator(CommandBuffer& command_buffer,
|
||||
const CommandListHeader& command_list_header,
|
||||
const AudioRendererSystemContext& render_context,
|
||||
VoiceContext& voice_context, MixContext& mix_context,
|
||||
EffectContext& effect_context, SinkContext& sink_context,
|
||||
SplitterContext& splitter_context,
|
||||
PerformanceManager* performance_manager);
|
||||
|
||||
/**
|
||||
* Calculate the buffer size needed for commands.
|
||||
*
|
||||
* @param behavior - Used to check what features are enabled.
|
||||
* @param params - Input rendering parameters for numbers of voices/mixes/sinks etc.
|
||||
*/
|
||||
static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior,
|
||||
const AudioRendererParameterInternal& params) {
|
||||
u64 size{0};
|
||||
|
||||
// Effects
|
||||
size += params.effects * sizeof(EffectInfoBase);
|
||||
|
||||
// Voices
|
||||
u64 voice_size{0};
|
||||
if (behavior.IsWaveBufferVer2Supported()) {
|
||||
voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command),
|
||||
sizeof(PcmInt16DataSourceVersion2Command)),
|
||||
sizeof(PcmFloatDataSourceVersion2Command));
|
||||
} else {
|
||||
voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command),
|
||||
sizeof(PcmInt16DataSourceVersion1Command)),
|
||||
sizeof(PcmFloatDataSourceVersion1Command));
|
||||
}
|
||||
voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters;
|
||||
voice_size += sizeof(VolumeRampCommand);
|
||||
voice_size += sizeof(MixRampGroupedCommand);
|
||||
|
||||
size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size);
|
||||
|
||||
// Sub mixes
|
||||
size += sizeof(DepopForMixBuffersCommand) +
|
||||
(sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers;
|
||||
|
||||
// Final mix
|
||||
size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers;
|
||||
|
||||
// Splitters
|
||||
size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers;
|
||||
|
||||
// Sinks
|
||||
size +=
|
||||
params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand));
|
||||
|
||||
// Performance
|
||||
size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 +
|
||||
PerformanceManager::MaxDetailEntries) *
|
||||
sizeof(PerformanceCommand);
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current command buffer used to generate commands.
|
||||
*
|
||||
* @return The command buffer.
|
||||
*/
|
||||
CommandBuffer& GetCommandBuffer() {
|
||||
return command_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current performance manager,
|
||||
*
|
||||
* @return The performance manager. May be nullptr.
|
||||
*/
|
||||
PerformanceManager* GetPerformanceManager() {
|
||||
return performance_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a data source command.
|
||||
* These are the basis for all audio output.
|
||||
*
|
||||
* @param voice_info - Generate the command from this voice.
|
||||
* @param voice_state - State used by the AudioRenderer across calls.
|
||||
* @param channel - Channel index to generate the command into.
|
||||
*/
|
||||
void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state,
|
||||
s8 channel);
|
||||
|
||||
/**
|
||||
* Generate voice mixing commands.
|
||||
* These are used to mix buffers together, to mix one input to many outputs,
|
||||
* and also used as copy commands to move data around and prevent it being accidentally
|
||||
* overwritten, e.g by another data source command into the same channel.
|
||||
*
|
||||
* @param mix_volumes - Current volumes of the mix.
|
||||
* @param prev_mix_volumes - Previous volumes of the mix.
|
||||
* @param voice_state - State used by the AudioRenderer across calls.
|
||||
* @param output_index - Output mix buffer index.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param input_index - Input mix buffer index.
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
*/
|
||||
void GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
|
||||
std::span<const f32> prev_mix_volumes,
|
||||
const VoiceState& voice_state, s16 output_index, s16 buffer_count,
|
||||
s16 input_index, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a biquad filter command for a voice.
|
||||
*
|
||||
* @param voice_info - Voice info this command is generated from.
|
||||
* @param voice_state - State used by the AudioRenderer across calls.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param channel - Channel index of this command.
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
*/
|
||||
void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state,
|
||||
s16 buffer_count, s8 channel, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate commands for a voice.
|
||||
* Includes a data source, biquad filter, volume and mixing.
|
||||
*
|
||||
* @param voice_info - Voice info these commands are generated from.
|
||||
*/
|
||||
void GenerateVoiceCommand(VoiceInfo& voice_info);
|
||||
|
||||
/**
|
||||
* Generate commands for all voices.
|
||||
*/
|
||||
void GenerateVoiceCommands();
|
||||
|
||||
/**
|
||||
* Generate a mixing command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - BufferMixer effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base,
|
||||
s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a delay effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - Delay effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a reverb effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - Reverb effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
* @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts.
|
||||
*/
|
||||
void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id,
|
||||
bool long_size_pre_delay_supported);
|
||||
|
||||
/**
|
||||
* Generate an I3DL2 reverb effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - I3DL2Reverb effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
|
||||
s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate an aux effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Aux effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a biquad filter effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Aux effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
|
||||
s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a light limiter effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Limiter effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
* @param effect_index - Index for the statistics state.
|
||||
*/
|
||||
void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
|
||||
s32 node_id, u32 effect_index);
|
||||
|
||||
/**
|
||||
* Generate a capture effect command.
|
||||
* Writes a mix buffer back to game memory.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Capture effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a compressor effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Compressor effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate all effect commands for a mix.
|
||||
*
|
||||
* @param mix_info - Mix to generate effects from.
|
||||
*/
|
||||
void GenerateEffectCommand(MixInfo& mix_info);
|
||||
|
||||
/**
|
||||
* Generate all mix commands.
|
||||
*
|
||||
* @param mix_info - Mix to generate effects from.
|
||||
*/
|
||||
void GenerateMixCommands(MixInfo& mix_info);
|
||||
|
||||
/**
|
||||
* Generate a submix command.
|
||||
* Generates all effects and all mixing commands.
|
||||
*
|
||||
* @param mix_info - Mix to generate effects from.
|
||||
*/
|
||||
void GenerateSubMixCommand(MixInfo& mix_info);
|
||||
|
||||
/**
|
||||
* Generate all submix command.
|
||||
*/
|
||||
void GenerateSubMixCommands();
|
||||
|
||||
/**
|
||||
* Generate the final mix.
|
||||
*/
|
||||
void GenerateFinalMixCommand();
|
||||
|
||||
/**
|
||||
* Generate the final mix commands.
|
||||
*/
|
||||
void GenerateFinalMixCommands();
|
||||
|
||||
/**
|
||||
* Generate all sink commands.
|
||||
*/
|
||||
void GenerateSinkCommands();
|
||||
|
||||
/**
|
||||
* Generate a sink command.
|
||||
* Sends samples out to the backend, or a game-supplied circular buffer.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param sink_info - Sink info to generate the commands from.
|
||||
*/
|
||||
void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
|
||||
|
||||
/**
|
||||
* Generate a device sink command.
|
||||
* Sends samples out to the backend.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param sink_info - Sink info to generate the commands from.
|
||||
*/
|
||||
void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
|
||||
|
||||
/**
|
||||
* Generate a performance command.
|
||||
* Used to report performance metrics of the AudioRenderer back to the game.
|
||||
*
|
||||
* @param node_id - Node ID of the mix this command is generated for
|
||||
* @param state - Output state of the generated performance command
|
||||
* @param entry_addresses - Addresses to be written
|
||||
*/
|
||||
void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
|
||||
const PerformanceEntryAddresses& entry_addresses);
|
||||
|
||||
private:
|
||||
/// Commands will be written by this buffer
|
||||
CommandBuffer& command_buffer;
|
||||
/// Header information for the commands generated
|
||||
const CommandListHeader& command_header;
|
||||
/// Various things to control generation
|
||||
const AudioRendererSystemContext& render_context;
|
||||
/// Used for generating voices
|
||||
VoiceContext& voice_context;
|
||||
/// Used for generating mixes
|
||||
MixContext& mix_context;
|
||||
/// Used for generating effects
|
||||
EffectContext& effect_context;
|
||||
/// Used for generating sinks
|
||||
SinkContext& sink_context;
|
||||
/// Used for generating submixes
|
||||
SplitterContext& splitter_context;
|
||||
/// Used for generating performance
|
||||
PerformanceManager* performance_manager;
|
||||
};
|
||||
|
||||
} // namespace AudioRenderer
|
||||
} // namespace AudioCore
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/command/commands.h"
|
||||
#include "audio_core/renderer/performance/performance_manager.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
struct AudioRendererSystemContext;
|
||||
|
||||
namespace AudioRenderer {
|
||||
class CommandBuffer;
|
||||
struct CommandListHeader;
|
||||
class VoiceContext;
|
||||
class MixContext;
|
||||
class EffectContext;
|
||||
class SplitterContext;
|
||||
class SinkContext;
|
||||
class BehaviorInfo;
|
||||
class VoiceInfo;
|
||||
struct VoiceState;
|
||||
class MixInfo;
|
||||
class SinkInfoBase;
|
||||
|
||||
/**
|
||||
* Generates all commands to build up a command list, which are sent to the AudioRender for
|
||||
* processing.
|
||||
*/
|
||||
class CommandGenerator {
|
||||
public:
|
||||
explicit CommandGenerator(CommandBuffer& command_buffer,
|
||||
const CommandListHeader& command_list_header,
|
||||
const AudioRendererSystemContext& render_context,
|
||||
VoiceContext& voice_context, MixContext& mix_context,
|
||||
EffectContext& effect_context, SinkContext& sink_context,
|
||||
SplitterContext& splitter_context,
|
||||
PerformanceManager* performance_manager);
|
||||
|
||||
/**
|
||||
* Calculate the buffer size needed for commands.
|
||||
*
|
||||
* @param behavior - Used to check what features are enabled.
|
||||
* @param params - Input rendering parameters for numbers of voices/mixes/sinks etc.
|
||||
*/
|
||||
static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior,
|
||||
const AudioRendererParameterInternal& params) {
|
||||
u64 size{0};
|
||||
|
||||
// Effects
|
||||
size += params.effects * sizeof(EffectInfoBase);
|
||||
|
||||
// Voices
|
||||
u64 voice_size{0};
|
||||
if (behavior.IsWaveBufferVer2Supported()) {
|
||||
voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command),
|
||||
sizeof(PcmInt16DataSourceVersion2Command)),
|
||||
sizeof(PcmFloatDataSourceVersion2Command));
|
||||
} else {
|
||||
voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command),
|
||||
sizeof(PcmInt16DataSourceVersion1Command)),
|
||||
sizeof(PcmFloatDataSourceVersion1Command));
|
||||
}
|
||||
voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters;
|
||||
voice_size += sizeof(VolumeRampCommand);
|
||||
voice_size += sizeof(MixRampGroupedCommand);
|
||||
|
||||
size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size);
|
||||
|
||||
// Sub mixes
|
||||
size += sizeof(DepopForMixBuffersCommand) +
|
||||
(sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers;
|
||||
|
||||
// Final mix
|
||||
size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers;
|
||||
|
||||
// Splitters
|
||||
size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers;
|
||||
|
||||
// Sinks
|
||||
size +=
|
||||
params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand));
|
||||
|
||||
// Performance
|
||||
size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 +
|
||||
PerformanceManager::MaxDetailEntries) *
|
||||
sizeof(PerformanceCommand);
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current command buffer used to generate commands.
|
||||
*
|
||||
* @return The command buffer.
|
||||
*/
|
||||
CommandBuffer& GetCommandBuffer() {
|
||||
return command_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current performance manager,
|
||||
*
|
||||
* @return The performance manager. May be nullptr.
|
||||
*/
|
||||
PerformanceManager* GetPerformanceManager() {
|
||||
return performance_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a data source command.
|
||||
* These are the basis for all audio output.
|
||||
*
|
||||
* @param voice_info - Generate the command from this voice.
|
||||
* @param voice_state - State used by the AudioRenderer across calls.
|
||||
* @param channel - Channel index to generate the command into.
|
||||
*/
|
||||
void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state,
|
||||
s8 channel);
|
||||
|
||||
/**
|
||||
* Generate voice mixing commands.
|
||||
* These are used to mix buffers together, to mix one input to many outputs,
|
||||
* and also used as copy commands to move data around and prevent it being accidentally
|
||||
* overwritten, e.g by another data source command into the same channel.
|
||||
*
|
||||
* @param mix_volumes - Current volumes of the mix.
|
||||
* @param prev_mix_volumes - Previous volumes of the mix.
|
||||
* @param voice_state - State used by the AudioRenderer across calls.
|
||||
* @param output_index - Output mix buffer index.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param input_index - Input mix buffer index.
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
*/
|
||||
void GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
|
||||
std::span<const f32> prev_mix_volumes,
|
||||
const VoiceState& voice_state, s16 output_index, s16 buffer_count,
|
||||
s16 input_index, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a biquad filter command for a voice.
|
||||
*
|
||||
* @param voice_info - Voice info this command is generated from.
|
||||
* @param voice_state - State used by the AudioRenderer across calls.
|
||||
* @param buffer_count - Number of active mix buffers.
|
||||
* @param channel - Channel index of this command.
|
||||
* @param node_id - Node id of the voice this command is generated for.
|
||||
*/
|
||||
void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state,
|
||||
s16 buffer_count, s8 channel, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate commands for a voice.
|
||||
* Includes a data source, biquad filter, volume and mixing.
|
||||
*
|
||||
* @param voice_info - Voice info these commands are generated from.
|
||||
*/
|
||||
void GenerateVoiceCommand(VoiceInfo& voice_info);
|
||||
|
||||
/**
|
||||
* Generate commands for all voices.
|
||||
*/
|
||||
void GenerateVoiceCommands();
|
||||
|
||||
/**
|
||||
* Generate a mixing command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - BufferMixer effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base,
|
||||
s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a delay effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - Delay effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a reverb effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info_base - Reverb effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
* @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts.
|
||||
*/
|
||||
void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id,
|
||||
bool long_size_pre_delay_supported);
|
||||
|
||||
/**
|
||||
* Generate an I3DL2 reverb effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - I3DL2Reverb effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
|
||||
s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate an aux effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Aux effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a biquad filter effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Aux effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
|
||||
s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a light limiter effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Limiter effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
* @param effect_index - Index for the statistics state.
|
||||
*/
|
||||
void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
|
||||
s32 node_id, u32 effect_index);
|
||||
|
||||
/**
|
||||
* Generate a capture effect command.
|
||||
* Writes a mix buffer back to game memory.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Capture effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate a compressor effect command.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param effect_info - Compressor effect info.
|
||||
* @param node_id - Node id of the mix this command is generated for.
|
||||
*/
|
||||
void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
|
||||
|
||||
/**
|
||||
* Generate all effect commands for a mix.
|
||||
*
|
||||
* @param mix_info - Mix to generate effects from.
|
||||
*/
|
||||
void GenerateEffectCommand(MixInfo& mix_info);
|
||||
|
||||
/**
|
||||
* Generate all mix commands.
|
||||
*
|
||||
* @param mix_info - Mix to generate effects from.
|
||||
*/
|
||||
void GenerateMixCommands(MixInfo& mix_info);
|
||||
|
||||
/**
|
||||
* Generate a submix command.
|
||||
* Generates all effects and all mixing commands.
|
||||
*
|
||||
* @param mix_info - Mix to generate effects from.
|
||||
*/
|
||||
void GenerateSubMixCommand(MixInfo& mix_info);
|
||||
|
||||
/**
|
||||
* Generate all submix command.
|
||||
*/
|
||||
void GenerateSubMixCommands();
|
||||
|
||||
/**
|
||||
* Generate the final mix.
|
||||
*/
|
||||
void GenerateFinalMixCommand();
|
||||
|
||||
/**
|
||||
* Generate the final mix commands.
|
||||
*/
|
||||
void GenerateFinalMixCommands();
|
||||
|
||||
/**
|
||||
* Generate all sink commands.
|
||||
*/
|
||||
void GenerateSinkCommands();
|
||||
|
||||
/**
|
||||
* Generate a sink command.
|
||||
* Sends samples out to the backend, or a game-supplied circular buffer.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param sink_info - Sink info to generate the commands from.
|
||||
*/
|
||||
void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
|
||||
|
||||
/**
|
||||
* Generate a device sink command.
|
||||
* Sends samples out to the backend.
|
||||
*
|
||||
* @param buffer_offset - Base mix buffer offset to use.
|
||||
* @param sink_info - Sink info to generate the commands from.
|
||||
*/
|
||||
void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
|
||||
|
||||
/**
|
||||
* Generate a performance command.
|
||||
* Used to report performance metrics of the AudioRenderer back to the game.
|
||||
*
|
||||
* @param node_id - Node ID of the mix this command is generated for
|
||||
* @param state - Output state of the generated performance command
|
||||
* @param entry_addresses - Addresses to be written
|
||||
*/
|
||||
void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
|
||||
const PerformanceEntryAddresses& entry_addresses);
|
||||
|
||||
private:
|
||||
/// Commands will be written by this buffer
|
||||
CommandBuffer& command_buffer;
|
||||
/// Header information for the commands generated
|
||||
const CommandListHeader& command_header;
|
||||
/// Various things to control generation
|
||||
const AudioRendererSystemContext& render_context;
|
||||
/// Used for generating voices
|
||||
VoiceContext& voice_context;
|
||||
/// Used for generating mixes
|
||||
MixContext& mix_context;
|
||||
/// Used for generating effects
|
||||
EffectContext& effect_context;
|
||||
/// Used for generating sinks
|
||||
SinkContext& sink_context;
|
||||
/// Used for generating submixes
|
||||
SplitterContext& splitter_context;
|
||||
/// Used for generating performance
|
||||
PerformanceManager* performance_manager;
|
||||
};
|
||||
|
||||
} // namespace AudioRenderer
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
struct CommandListHeader {
|
||||
u64 buffer_size;
|
||||
u32 command_count;
|
||||
std::span<s32> samples_buffer;
|
||||
s16 buffer_count;
|
||||
u32 sample_count;
|
||||
u32 sample_rate;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
struct CommandListHeader {
|
||||
u64 buffer_size;
|
||||
u32 command_count;
|
||||
std::span<s32> samples_buffer;
|
||||
s16 buffer_count;
|
||||
u32 sample_count;
|
||||
u32 sample_rate;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,254 +1,254 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/renderer/command/commands.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Estimate the processing time required for all commands.
|
||||
*/
|
||||
class ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
virtual ~ICommandProcessingTimeEstimator() = default;
|
||||
|
||||
virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0;
|
||||
virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0;
|
||||
virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0;
|
||||
virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0;
|
||||
virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0;
|
||||
virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0;
|
||||
virtual u32 Estimate(const VolumeCommand& command) const = 0;
|
||||
virtual u32 Estimate(const VolumeRampCommand& command) const = 0;
|
||||
virtual u32 Estimate(const BiquadFilterCommand& command) const = 0;
|
||||
virtual u32 Estimate(const MixCommand& command) const = 0;
|
||||
virtual u32 Estimate(const MixRampCommand& command) const = 0;
|
||||
virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0;
|
||||
virtual u32 Estimate(const DepopPrepareCommand& command) const = 0;
|
||||
virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0;
|
||||
virtual u32 Estimate(const DelayCommand& command) const = 0;
|
||||
virtual u32 Estimate(const UpsampleCommand& command) const = 0;
|
||||
virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0;
|
||||
virtual u32 Estimate(const AuxCommand& command) const = 0;
|
||||
virtual u32 Estimate(const DeviceSinkCommand& command) const = 0;
|
||||
virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0;
|
||||
virtual u32 Estimate(const ReverbCommand& command) const = 0;
|
||||
virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0;
|
||||
virtual u32 Estimate(const PerformanceCommand& command) const = 0;
|
||||
virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0;
|
||||
virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0;
|
||||
virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0;
|
||||
virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0;
|
||||
virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0;
|
||||
virtual u32 Estimate(const CaptureCommand& command) const = 0;
|
||||
virtual u32 Estimate(const CompressorCommand& command) const = 0;
|
||||
};
|
||||
|
||||
class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_)
|
||||
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
|
||||
|
||||
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const VolumeCommand& command) const override;
|
||||
u32 Estimate(const VolumeRampCommand& command) const override;
|
||||
u32 Estimate(const BiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const MixCommand& command) const override;
|
||||
u32 Estimate(const MixRampCommand& command) const override;
|
||||
u32 Estimate(const MixRampGroupedCommand& command) const override;
|
||||
u32 Estimate(const DepopPrepareCommand& command) const override;
|
||||
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
|
||||
u32 Estimate(const DelayCommand& command) const override;
|
||||
u32 Estimate(const UpsampleCommand& command) const override;
|
||||
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
|
||||
u32 Estimate(const AuxCommand& command) const override;
|
||||
u32 Estimate(const DeviceSinkCommand& command) const override;
|
||||
u32 Estimate(const CircularBufferSinkCommand& command) const override;
|
||||
u32 Estimate(const ReverbCommand& command) const override;
|
||||
u32 Estimate(const I3dl2ReverbCommand& command) const override;
|
||||
u32 Estimate(const PerformanceCommand& command) const override;
|
||||
u32 Estimate(const ClearMixBufferCommand& command) const override;
|
||||
u32 Estimate(const CopyMixBufferCommand& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion1Command& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion2Command& command) const override;
|
||||
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const CaptureCommand& command) const override;
|
||||
u32 Estimate(const CompressorCommand& command) const override;
|
||||
|
||||
private:
|
||||
u32 sample_count{};
|
||||
u32 buffer_count{};
|
||||
};
|
||||
|
||||
class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_)
|
||||
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
|
||||
|
||||
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const VolumeCommand& command) const override;
|
||||
u32 Estimate(const VolumeRampCommand& command) const override;
|
||||
u32 Estimate(const BiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const MixCommand& command) const override;
|
||||
u32 Estimate(const MixRampCommand& command) const override;
|
||||
u32 Estimate(const MixRampGroupedCommand& command) const override;
|
||||
u32 Estimate(const DepopPrepareCommand& command) const override;
|
||||
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
|
||||
u32 Estimate(const DelayCommand& command) const override;
|
||||
u32 Estimate(const UpsampleCommand& command) const override;
|
||||
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
|
||||
u32 Estimate(const AuxCommand& command) const override;
|
||||
u32 Estimate(const DeviceSinkCommand& command) const override;
|
||||
u32 Estimate(const CircularBufferSinkCommand& command) const override;
|
||||
u32 Estimate(const ReverbCommand& command) const override;
|
||||
u32 Estimate(const I3dl2ReverbCommand& command) const override;
|
||||
u32 Estimate(const PerformanceCommand& command) const override;
|
||||
u32 Estimate(const ClearMixBufferCommand& command) const override;
|
||||
u32 Estimate(const CopyMixBufferCommand& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion1Command& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion2Command& command) const override;
|
||||
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const CaptureCommand& command) const override;
|
||||
u32 Estimate(const CompressorCommand& command) const override;
|
||||
|
||||
private:
|
||||
u32 sample_count{};
|
||||
u32 buffer_count{};
|
||||
};
|
||||
|
||||
class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_)
|
||||
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
|
||||
|
||||
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const VolumeCommand& command) const override;
|
||||
u32 Estimate(const VolumeRampCommand& command) const override;
|
||||
u32 Estimate(const BiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const MixCommand& command) const override;
|
||||
u32 Estimate(const MixRampCommand& command) const override;
|
||||
u32 Estimate(const MixRampGroupedCommand& command) const override;
|
||||
u32 Estimate(const DepopPrepareCommand& command) const override;
|
||||
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
|
||||
u32 Estimate(const DelayCommand& command) const override;
|
||||
u32 Estimate(const UpsampleCommand& command) const override;
|
||||
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
|
||||
u32 Estimate(const AuxCommand& command) const override;
|
||||
u32 Estimate(const DeviceSinkCommand& command) const override;
|
||||
u32 Estimate(const CircularBufferSinkCommand& command) const override;
|
||||
u32 Estimate(const ReverbCommand& command) const override;
|
||||
u32 Estimate(const I3dl2ReverbCommand& command) const override;
|
||||
u32 Estimate(const PerformanceCommand& command) const override;
|
||||
u32 Estimate(const ClearMixBufferCommand& command) const override;
|
||||
u32 Estimate(const CopyMixBufferCommand& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion1Command& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion2Command& command) const override;
|
||||
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const CaptureCommand& command) const override;
|
||||
u32 Estimate(const CompressorCommand& command) const override;
|
||||
|
||||
private:
|
||||
u32 sample_count{};
|
||||
u32 buffer_count{};
|
||||
};
|
||||
|
||||
class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_)
|
||||
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
|
||||
|
||||
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const VolumeCommand& command) const override;
|
||||
u32 Estimate(const VolumeRampCommand& command) const override;
|
||||
u32 Estimate(const BiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const MixCommand& command) const override;
|
||||
u32 Estimate(const MixRampCommand& command) const override;
|
||||
u32 Estimate(const MixRampGroupedCommand& command) const override;
|
||||
u32 Estimate(const DepopPrepareCommand& command) const override;
|
||||
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
|
||||
u32 Estimate(const DelayCommand& command) const override;
|
||||
u32 Estimate(const UpsampleCommand& command) const override;
|
||||
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
|
||||
u32 Estimate(const AuxCommand& command) const override;
|
||||
u32 Estimate(const DeviceSinkCommand& command) const override;
|
||||
u32 Estimate(const CircularBufferSinkCommand& command) const override;
|
||||
u32 Estimate(const ReverbCommand& command) const override;
|
||||
u32 Estimate(const I3dl2ReverbCommand& command) const override;
|
||||
u32 Estimate(const PerformanceCommand& command) const override;
|
||||
u32 Estimate(const ClearMixBufferCommand& command) const override;
|
||||
u32 Estimate(const CopyMixBufferCommand& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion1Command& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion2Command& command) const override;
|
||||
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const CaptureCommand& command) const override;
|
||||
u32 Estimate(const CompressorCommand& command) const override;
|
||||
|
||||
private:
|
||||
u32 sample_count{};
|
||||
u32 buffer_count{};
|
||||
};
|
||||
|
||||
class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_)
|
||||
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
|
||||
|
||||
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const VolumeCommand& command) const override;
|
||||
u32 Estimate(const VolumeRampCommand& command) const override;
|
||||
u32 Estimate(const BiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const MixCommand& command) const override;
|
||||
u32 Estimate(const MixRampCommand& command) const override;
|
||||
u32 Estimate(const MixRampGroupedCommand& command) const override;
|
||||
u32 Estimate(const DepopPrepareCommand& command) const override;
|
||||
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
|
||||
u32 Estimate(const DelayCommand& command) const override;
|
||||
u32 Estimate(const UpsampleCommand& command) const override;
|
||||
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
|
||||
u32 Estimate(const AuxCommand& command) const override;
|
||||
u32 Estimate(const DeviceSinkCommand& command) const override;
|
||||
u32 Estimate(const CircularBufferSinkCommand& command) const override;
|
||||
u32 Estimate(const ReverbCommand& command) const override;
|
||||
u32 Estimate(const I3dl2ReverbCommand& command) const override;
|
||||
u32 Estimate(const PerformanceCommand& command) const override;
|
||||
u32 Estimate(const ClearMixBufferCommand& command) const override;
|
||||
u32 Estimate(const CopyMixBufferCommand& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion1Command& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion2Command& command) const override;
|
||||
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const CaptureCommand& command) const override;
|
||||
u32 Estimate(const CompressorCommand& command) const override;
|
||||
|
||||
private:
|
||||
u32 sample_count{};
|
||||
u32 buffer_count{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/renderer/command/commands.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Estimate the processing time required for all commands.
|
||||
*/
|
||||
class ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
virtual ~ICommandProcessingTimeEstimator() = default;
|
||||
|
||||
virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0;
|
||||
virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0;
|
||||
virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0;
|
||||
virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0;
|
||||
virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0;
|
||||
virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0;
|
||||
virtual u32 Estimate(const VolumeCommand& command) const = 0;
|
||||
virtual u32 Estimate(const VolumeRampCommand& command) const = 0;
|
||||
virtual u32 Estimate(const BiquadFilterCommand& command) const = 0;
|
||||
virtual u32 Estimate(const MixCommand& command) const = 0;
|
||||
virtual u32 Estimate(const MixRampCommand& command) const = 0;
|
||||
virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0;
|
||||
virtual u32 Estimate(const DepopPrepareCommand& command) const = 0;
|
||||
virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0;
|
||||
virtual u32 Estimate(const DelayCommand& command) const = 0;
|
||||
virtual u32 Estimate(const UpsampleCommand& command) const = 0;
|
||||
virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0;
|
||||
virtual u32 Estimate(const AuxCommand& command) const = 0;
|
||||
virtual u32 Estimate(const DeviceSinkCommand& command) const = 0;
|
||||
virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0;
|
||||
virtual u32 Estimate(const ReverbCommand& command) const = 0;
|
||||
virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0;
|
||||
virtual u32 Estimate(const PerformanceCommand& command) const = 0;
|
||||
virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0;
|
||||
virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0;
|
||||
virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0;
|
||||
virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0;
|
||||
virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0;
|
||||
virtual u32 Estimate(const CaptureCommand& command) const = 0;
|
||||
virtual u32 Estimate(const CompressorCommand& command) const = 0;
|
||||
};
|
||||
|
||||
class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_)
|
||||
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
|
||||
|
||||
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const VolumeCommand& command) const override;
|
||||
u32 Estimate(const VolumeRampCommand& command) const override;
|
||||
u32 Estimate(const BiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const MixCommand& command) const override;
|
||||
u32 Estimate(const MixRampCommand& command) const override;
|
||||
u32 Estimate(const MixRampGroupedCommand& command) const override;
|
||||
u32 Estimate(const DepopPrepareCommand& command) const override;
|
||||
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
|
||||
u32 Estimate(const DelayCommand& command) const override;
|
||||
u32 Estimate(const UpsampleCommand& command) const override;
|
||||
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
|
||||
u32 Estimate(const AuxCommand& command) const override;
|
||||
u32 Estimate(const DeviceSinkCommand& command) const override;
|
||||
u32 Estimate(const CircularBufferSinkCommand& command) const override;
|
||||
u32 Estimate(const ReverbCommand& command) const override;
|
||||
u32 Estimate(const I3dl2ReverbCommand& command) const override;
|
||||
u32 Estimate(const PerformanceCommand& command) const override;
|
||||
u32 Estimate(const ClearMixBufferCommand& command) const override;
|
||||
u32 Estimate(const CopyMixBufferCommand& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion1Command& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion2Command& command) const override;
|
||||
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const CaptureCommand& command) const override;
|
||||
u32 Estimate(const CompressorCommand& command) const override;
|
||||
|
||||
private:
|
||||
u32 sample_count{};
|
||||
u32 buffer_count{};
|
||||
};
|
||||
|
||||
class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_)
|
||||
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
|
||||
|
||||
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const VolumeCommand& command) const override;
|
||||
u32 Estimate(const VolumeRampCommand& command) const override;
|
||||
u32 Estimate(const BiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const MixCommand& command) const override;
|
||||
u32 Estimate(const MixRampCommand& command) const override;
|
||||
u32 Estimate(const MixRampGroupedCommand& command) const override;
|
||||
u32 Estimate(const DepopPrepareCommand& command) const override;
|
||||
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
|
||||
u32 Estimate(const DelayCommand& command) const override;
|
||||
u32 Estimate(const UpsampleCommand& command) const override;
|
||||
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
|
||||
u32 Estimate(const AuxCommand& command) const override;
|
||||
u32 Estimate(const DeviceSinkCommand& command) const override;
|
||||
u32 Estimate(const CircularBufferSinkCommand& command) const override;
|
||||
u32 Estimate(const ReverbCommand& command) const override;
|
||||
u32 Estimate(const I3dl2ReverbCommand& command) const override;
|
||||
u32 Estimate(const PerformanceCommand& command) const override;
|
||||
u32 Estimate(const ClearMixBufferCommand& command) const override;
|
||||
u32 Estimate(const CopyMixBufferCommand& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion1Command& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion2Command& command) const override;
|
||||
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const CaptureCommand& command) const override;
|
||||
u32 Estimate(const CompressorCommand& command) const override;
|
||||
|
||||
private:
|
||||
u32 sample_count{};
|
||||
u32 buffer_count{};
|
||||
};
|
||||
|
||||
class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_)
|
||||
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
|
||||
|
||||
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const VolumeCommand& command) const override;
|
||||
u32 Estimate(const VolumeRampCommand& command) const override;
|
||||
u32 Estimate(const BiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const MixCommand& command) const override;
|
||||
u32 Estimate(const MixRampCommand& command) const override;
|
||||
u32 Estimate(const MixRampGroupedCommand& command) const override;
|
||||
u32 Estimate(const DepopPrepareCommand& command) const override;
|
||||
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
|
||||
u32 Estimate(const DelayCommand& command) const override;
|
||||
u32 Estimate(const UpsampleCommand& command) const override;
|
||||
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
|
||||
u32 Estimate(const AuxCommand& command) const override;
|
||||
u32 Estimate(const DeviceSinkCommand& command) const override;
|
||||
u32 Estimate(const CircularBufferSinkCommand& command) const override;
|
||||
u32 Estimate(const ReverbCommand& command) const override;
|
||||
u32 Estimate(const I3dl2ReverbCommand& command) const override;
|
||||
u32 Estimate(const PerformanceCommand& command) const override;
|
||||
u32 Estimate(const ClearMixBufferCommand& command) const override;
|
||||
u32 Estimate(const CopyMixBufferCommand& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion1Command& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion2Command& command) const override;
|
||||
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const CaptureCommand& command) const override;
|
||||
u32 Estimate(const CompressorCommand& command) const override;
|
||||
|
||||
private:
|
||||
u32 sample_count{};
|
||||
u32 buffer_count{};
|
||||
};
|
||||
|
||||
class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_)
|
||||
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
|
||||
|
||||
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const VolumeCommand& command) const override;
|
||||
u32 Estimate(const VolumeRampCommand& command) const override;
|
||||
u32 Estimate(const BiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const MixCommand& command) const override;
|
||||
u32 Estimate(const MixRampCommand& command) const override;
|
||||
u32 Estimate(const MixRampGroupedCommand& command) const override;
|
||||
u32 Estimate(const DepopPrepareCommand& command) const override;
|
||||
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
|
||||
u32 Estimate(const DelayCommand& command) const override;
|
||||
u32 Estimate(const UpsampleCommand& command) const override;
|
||||
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
|
||||
u32 Estimate(const AuxCommand& command) const override;
|
||||
u32 Estimate(const DeviceSinkCommand& command) const override;
|
||||
u32 Estimate(const CircularBufferSinkCommand& command) const override;
|
||||
u32 Estimate(const ReverbCommand& command) const override;
|
||||
u32 Estimate(const I3dl2ReverbCommand& command) const override;
|
||||
u32 Estimate(const PerformanceCommand& command) const override;
|
||||
u32 Estimate(const ClearMixBufferCommand& command) const override;
|
||||
u32 Estimate(const CopyMixBufferCommand& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion1Command& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion2Command& command) const override;
|
||||
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const CaptureCommand& command) const override;
|
||||
u32 Estimate(const CompressorCommand& command) const override;
|
||||
|
||||
private:
|
||||
u32 sample_count{};
|
||||
u32 buffer_count{};
|
||||
};
|
||||
|
||||
class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator {
|
||||
public:
|
||||
CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_)
|
||||
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
|
||||
|
||||
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
|
||||
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
|
||||
u32 Estimate(const VolumeCommand& command) const override;
|
||||
u32 Estimate(const VolumeRampCommand& command) const override;
|
||||
u32 Estimate(const BiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const MixCommand& command) const override;
|
||||
u32 Estimate(const MixRampCommand& command) const override;
|
||||
u32 Estimate(const MixRampGroupedCommand& command) const override;
|
||||
u32 Estimate(const DepopPrepareCommand& command) const override;
|
||||
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
|
||||
u32 Estimate(const DelayCommand& command) const override;
|
||||
u32 Estimate(const UpsampleCommand& command) const override;
|
||||
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
|
||||
u32 Estimate(const AuxCommand& command) const override;
|
||||
u32 Estimate(const DeviceSinkCommand& command) const override;
|
||||
u32 Estimate(const CircularBufferSinkCommand& command) const override;
|
||||
u32 Estimate(const ReverbCommand& command) const override;
|
||||
u32 Estimate(const I3dl2ReverbCommand& command) const override;
|
||||
u32 Estimate(const PerformanceCommand& command) const override;
|
||||
u32 Estimate(const ClearMixBufferCommand& command) const override;
|
||||
u32 Estimate(const CopyMixBufferCommand& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion1Command& command) const override;
|
||||
u32 Estimate(const LightLimiterVersion2Command& command) const override;
|
||||
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
|
||||
u32 Estimate(const CaptureCommand& command) const override;
|
||||
u32 Estimate(const CompressorCommand& command) const override;
|
||||
|
||||
private:
|
||||
u32 sample_count{};
|
||||
u32 buffer_count{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/renderer/command/data_source/adpcm.h"
|
||||
#include "audio_core/renderer/command/data_source/pcm_float.h"
|
||||
#include "audio_core/renderer/command/data_source/pcm_int16.h"
|
||||
#include "audio_core/renderer/command/effect/aux_.h"
|
||||
#include "audio_core/renderer/command/effect/biquad_filter.h"
|
||||
#include "audio_core/renderer/command/effect/capture.h"
|
||||
#include "audio_core/renderer/command/effect/compressor.h"
|
||||
#include "audio_core/renderer/command/effect/delay.h"
|
||||
#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
|
||||
#include "audio_core/renderer/command/effect/light_limiter.h"
|
||||
#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
|
||||
#include "audio_core/renderer/command/effect/reverb.h"
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/command/mix/clear_mix.h"
|
||||
#include "audio_core/renderer/command/mix/copy_mix.h"
|
||||
#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
|
||||
#include "audio_core/renderer/command/mix/depop_prepare.h"
|
||||
#include "audio_core/renderer/command/mix/mix.h"
|
||||
#include "audio_core/renderer/command/mix/mix_ramp.h"
|
||||
#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
|
||||
#include "audio_core/renderer/command/mix/volume.h"
|
||||
#include "audio_core/renderer/command/mix/volume_ramp.h"
|
||||
#include "audio_core/renderer/command/performance/performance.h"
|
||||
#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
|
||||
#include "audio_core/renderer/command/resample/upsample.h"
|
||||
#include "audio_core/renderer/command/sink/circular_buffer.h"
|
||||
#include "audio_core/renderer/command/sink/device.h"
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/renderer/command/data_source/adpcm.h"
|
||||
#include "audio_core/renderer/command/data_source/pcm_float.h"
|
||||
#include "audio_core/renderer/command/data_source/pcm_int16.h"
|
||||
#include "audio_core/renderer/command/effect/aux_.h"
|
||||
#include "audio_core/renderer/command/effect/biquad_filter.h"
|
||||
#include "audio_core/renderer/command/effect/capture.h"
|
||||
#include "audio_core/renderer/command/effect/compressor.h"
|
||||
#include "audio_core/renderer/command/effect/delay.h"
|
||||
#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
|
||||
#include "audio_core/renderer/command/effect/light_limiter.h"
|
||||
#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
|
||||
#include "audio_core/renderer/command/effect/reverb.h"
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/command/mix/clear_mix.h"
|
||||
#include "audio_core/renderer/command/mix/copy_mix.h"
|
||||
#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
|
||||
#include "audio_core/renderer/command/mix/depop_prepare.h"
|
||||
#include "audio_core/renderer/command/mix/mix.h"
|
||||
#include "audio_core/renderer/command/mix/mix_ramp.h"
|
||||
#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
|
||||
#include "audio_core/renderer/command/mix/volume.h"
|
||||
#include "audio_core/renderer/command/mix/volume_ramp.h"
|
||||
#include "audio_core/renderer/command/performance/performance.h"
|
||||
#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
|
||||
#include "audio_core/renderer/command/resample/upsample.h"
|
||||
#include "audio_core/renderer/command/sink/circular_buffer.h"
|
||||
#include "audio_core/renderer/command/sink/device.h"
|
||||
|
||||
@@ -1,84 +1,84 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/data_source/adpcm.h"
|
||||
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample "
|
||||
"rate {} target sample rate {} src quality {}\n",
|
||||
output_index, sample_rate, processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::Adpcm},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{0},
|
||||
.channel_count{1},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{data_address},
|
||||
.data_size{data_size},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample "
|
||||
"rate {} target sample rate {} src quality {}\n",
|
||||
output_index, sample_rate, processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::Adpcm},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{0},
|
||||
.channel_count{1},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{data_address},
|
||||
.data_size{data_size},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/data_source/adpcm.h"
|
||||
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample "
|
||||
"rate {} target sample rate {} src quality {}\n",
|
||||
output_index, sample_rate, processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::Adpcm},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{0},
|
||||
.channel_count{1},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{data_address},
|
||||
.data_size{data_size},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample "
|
||||
"rate {} target sample rate {} src quality {}\n",
|
||||
output_index, sample_rate, processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::Adpcm},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{0},
|
||||
.channel_count{1},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{data_address},
|
||||
.data_size{data_size},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,119 +1,119 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/common/wave_buffer.h"
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct AdpcmDataSourceVersion1Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
/// Coefficients data address
|
||||
CpuAddr data_address;
|
||||
/// Coefficients data size
|
||||
u64 data_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct AdpcmDataSourceVersion2Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Target channel to read within the wavebuffer
|
||||
s8 channel_index;
|
||||
/// Number of channels within the wavebuffer
|
||||
s8 channel_count;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
/// Coefficients data address
|
||||
CpuAddr data_address;
|
||||
/// Coefficients data size
|
||||
u64 data_size;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/common/wave_buffer.h"
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct AdpcmDataSourceVersion1Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
/// Coefficients data address
|
||||
CpuAddr data_address;
|
||||
/// Coefficients data size
|
||||
u64 data_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct AdpcmDataSourceVersion2Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Target channel to read within the wavebuffer
|
||||
s8 channel_index;
|
||||
/// Number of channels within the wavebuffer
|
||||
s8 channel_count;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
/// Coefficients data address
|
||||
CpuAddr data_address;
|
||||
/// Coefficients data size
|
||||
u64 data_size;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,428 +1,428 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
#include "audio_core/renderer/command/resample/resample.h"
|
||||
#include "common/fixed_point.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
constexpr u32 TempBufferSize = 0x3F00;
|
||||
constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
|
||||
|
||||
/**
|
||||
* Decode PCM data. Only s16 or f32 is supported.
|
||||
*
|
||||
* @tparam T - Type to decode. Only s16 and f32 are supported.
|
||||
* @param memory - Core memory for reading samples.
|
||||
* @param out_buffer - Output mix buffer to receive the samples.
|
||||
* @param req - Information for how to decode.
|
||||
* @return Number of samples decoded.
|
||||
*/
|
||||
template <typename T>
|
||||
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
constexpr s32 min{std::numeric_limits<s16>::min()};
|
||||
constexpr s32 max{std::numeric_limits<s16>::max()};
|
||||
|
||||
if (req.buffer == 0 || req.buffer_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (req.start_offset >= req.end_offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_decode{
|
||||
std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)};
|
||||
u32 channel_count{static_cast<u32>(req.channel_count)};
|
||||
|
||||
switch (req.channel_count) {
|
||||
default: {
|
||||
const VAddr source{req.buffer +
|
||||
(((req.start_offset + req.offset) * channel_count) * sizeof(T))};
|
||||
const u64 size{channel_count * samples_to_decode};
|
||||
const u64 size_bytes{size * sizeof(T)};
|
||||
|
||||
std::vector<T> samples(size);
|
||||
memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
|
||||
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
|
||||
std::numeric_limits<s16>::max())};
|
||||
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
|
||||
}
|
||||
} else {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
out_buffer[i] = samples[i * channel_count + req.target_channel];
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case 1:
|
||||
if (req.target_channel != 0) {
|
||||
LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}",
|
||||
req.target_channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
|
||||
std::vector<T> samples(samples_to_decode);
|
||||
memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
|
||||
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
|
||||
std::numeric_limits<s16>::max())};
|
||||
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
|
||||
}
|
||||
} else {
|
||||
std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return samples_to_decode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode ADPCM data.
|
||||
*
|
||||
* @param memory - Core memory for reading samples.
|
||||
* @param out_buffer - Output mix buffer to receive the samples.
|
||||
* @param req - Information for how to decode.
|
||||
* @return Number of samples decoded.
|
||||
*/
|
||||
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
constexpr u32 SamplesPerFrame{14};
|
||||
constexpr u32 NibblesPerFrame{16};
|
||||
|
||||
if (req.buffer == 0 || req.buffer_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (req.end_offset < req.start_offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto end{(req.end_offset % SamplesPerFrame) +
|
||||
NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
|
||||
if (req.end_offset % SamplesPerFrame) {
|
||||
end += 3;
|
||||
} else {
|
||||
end += 1;
|
||||
}
|
||||
|
||||
if (req.buffer_size < end / 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_process{
|
||||
std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
|
||||
|
||||
auto samples_to_read{samples_to_process};
|
||||
auto start_pos{req.start_offset + req.offset};
|
||||
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
|
||||
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
|
||||
samples_remaining_in_frame};
|
||||
|
||||
if (samples_remaining_in_frame) {
|
||||
position_in_frame += 2;
|
||||
}
|
||||
|
||||
const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
|
||||
std::vector<u8> wavebuffer(size);
|
||||
memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
|
||||
wavebuffer.size());
|
||||
|
||||
auto context{req.adpcm_context};
|
||||
auto header{context->header};
|
||||
u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
|
||||
u8 scale{static_cast<u8>(header & 0xFU)};
|
||||
s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
|
||||
s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
|
||||
|
||||
auto yn0{context->yn0};
|
||||
auto yn1{context->yn1};
|
||||
|
||||
static constexpr std::array<s32, 16> Steps{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
|
||||
};
|
||||
|
||||
const auto decode_sample = [&](const s32 code) -> s16 {
|
||||
const auto xn = code * (1 << scale);
|
||||
const auto prediction = coeff0 * yn0 + coeff1 * yn1;
|
||||
const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
|
||||
const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
|
||||
yn1 = yn0;
|
||||
yn0 = static_cast<s16>(saturated);
|
||||
return yn0;
|
||||
};
|
||||
|
||||
u32 read_index{0};
|
||||
u32 write_index{0};
|
||||
|
||||
while (samples_to_read > 0) {
|
||||
// Are we at a new frame?
|
||||
if ((position_in_frame % NibblesPerFrame) == 0) {
|
||||
header = wavebuffer[read_index++];
|
||||
coeff_index = (header >> 4) & 0xF;
|
||||
scale = header & 0xF;
|
||||
coeff0 = req.coefficients[coeff_index * 2 + 0];
|
||||
coeff1 = req.coefficients[coeff_index * 2 + 1];
|
||||
position_in_frame += 2;
|
||||
|
||||
// Can we consume all of this frame's samples?
|
||||
if (samples_to_read >= SamplesPerFrame) {
|
||||
// Can grab all samples until the next header
|
||||
for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
|
||||
auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
|
||||
auto code1{Steps[wavebuffer[read_index] & 0xF]};
|
||||
read_index++;
|
||||
|
||||
out_buffer[write_index++] = decode_sample(code0);
|
||||
out_buffer[write_index++] = decode_sample(code1);
|
||||
}
|
||||
|
||||
position_in_frame += SamplesPerFrame;
|
||||
samples_to_read -= SamplesPerFrame;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a single sample
|
||||
auto code{wavebuffer[read_index]};
|
||||
if (position_in_frame & 1) {
|
||||
code &= 0xF;
|
||||
read_index++;
|
||||
} else {
|
||||
code >>= 4;
|
||||
}
|
||||
|
||||
out_buffer[write_index++] = decode_sample(Steps[code]);
|
||||
|
||||
position_in_frame++;
|
||||
samples_to_read--;
|
||||
}
|
||||
|
||||
context->header = header;
|
||||
context->yn0 = yn0;
|
||||
context->yn1 = yn1;
|
||||
|
||||
return samples_to_process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode implementation.
|
||||
* Decode wavebuffers according to the given args.
|
||||
*
|
||||
* @param memory - Core memory to read data from.
|
||||
* @param args - The wavebuffer data, and information for how to decode it.
|
||||
*/
|
||||
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
|
||||
auto& voice_state{*args.voice_state};
|
||||
auto remaining_sample_count{args.sample_count};
|
||||
auto fraction{voice_state.fraction};
|
||||
|
||||
const auto sample_rate_ratio{
|
||||
(Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
|
||||
args.pitch};
|
||||
const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
|
||||
|
||||
if (size_required < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
|
||||
if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto max_remaining_sample_count{
|
||||
((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio)
|
||||
.to_uint_floor()};
|
||||
max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count);
|
||||
|
||||
auto wavebuffers_consumed{voice_state.wave_buffers_consumed};
|
||||
auto wavebuffer_index{voice_state.wave_buffer_index};
|
||||
auto played_sample_count{voice_state.played_sample_count};
|
||||
|
||||
bool is_buffer_starved{false};
|
||||
u32 offset{voice_state.offset};
|
||||
|
||||
auto output_buffer{args.output};
|
||||
std::vector<s16> temp_buffer(TempBufferSize, 0);
|
||||
|
||||
while (remaining_sample_count > 0) {
|
||||
const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};
|
||||
const auto samples_to_read{
|
||||
(fraction + samples_to_write * sample_rate_ratio).to_uint_floor()};
|
||||
|
||||
u32 temp_buffer_pos{0};
|
||||
|
||||
if (!args.IsVoicePitchAndSrcSkippedSupported) {
|
||||
for (u32 i = 0; i < pitch; i++) {
|
||||
temp_buffer[i] = voice_state.sample_history[i];
|
||||
}
|
||||
temp_buffer_pos = pitch;
|
||||
}
|
||||
|
||||
u32 samples_read{0};
|
||||
while (samples_read < samples_to_read) {
|
||||
if (wavebuffer_index >= MaxWaveBuffers) {
|
||||
LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
|
||||
wavebuffer_index = 0;
|
||||
voice_state.wave_buffer_valid.fill(false);
|
||||
wavebuffers_consumed = MaxWaveBuffers;
|
||||
}
|
||||
|
||||
if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
|
||||
is_buffer_starved = true;
|
||||
break;
|
||||
}
|
||||
|
||||
auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
|
||||
|
||||
if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
|
||||
wavebuffer.context != 0) {
|
||||
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
|
||||
wavebuffer.context_size);
|
||||
}
|
||||
|
||||
auto start_offset{wavebuffer.start_offset};
|
||||
auto end_offset{wavebuffer.end_offset};
|
||||
|
||||
if (wavebuffer.loop && voice_state.loop_count > 0 &&
|
||||
wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
|
||||
wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
|
||||
start_offset = wavebuffer.loop_start_offset;
|
||||
end_offset = wavebuffer.loop_end_offset;
|
||||
}
|
||||
|
||||
DecodeArg decode_arg{.buffer{wavebuffer.buffer},
|
||||
.buffer_size{wavebuffer.buffer_size},
|
||||
.start_offset{start_offset},
|
||||
.end_offset{end_offset},
|
||||
.channel_count{args.channel_count},
|
||||
.coefficients{},
|
||||
.adpcm_context{nullptr},
|
||||
.target_channel{args.channel},
|
||||
.offset{offset},
|
||||
.samples_to_read{samples_to_read - samples_read}};
|
||||
|
||||
s32 samples_decoded{0};
|
||||
|
||||
switch (args.sample_format) {
|
||||
case SampleFormat::PcmInt16:
|
||||
samples_decoded = DecodePcm<s16>(
|
||||
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
|
||||
decode_arg);
|
||||
break;
|
||||
|
||||
case SampleFormat::PcmFloat:
|
||||
samples_decoded = DecodePcm<f32>(
|
||||
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
|
||||
decode_arg);
|
||||
break;
|
||||
|
||||
case SampleFormat::Adpcm: {
|
||||
decode_arg.adpcm_context = &voice_state.adpcm_context;
|
||||
memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
|
||||
samples_decoded = DecodeAdpcm(
|
||||
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
|
||||
decode_arg);
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid sample format to decode {}",
|
||||
static_cast<u32>(args.sample_format));
|
||||
samples_decoded = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
played_sample_count += samples_decoded;
|
||||
samples_read += samples_decoded;
|
||||
temp_buffer_pos += samples_decoded;
|
||||
offset += samples_decoded;
|
||||
|
||||
if (samples_decoded == 0 || offset >= end_offset - start_offset) {
|
||||
offset = 0;
|
||||
if (!wavebuffer.loop) {
|
||||
voice_state.wave_buffer_valid[wavebuffer_index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
|
||||
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
|
||||
wavebuffers_consumed++;
|
||||
} else {
|
||||
voice_state.loop_count++;
|
||||
if (wavebuffer.loop_count > 0 &&
|
||||
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
|
||||
voice_state.wave_buffer_valid[wavebuffer_index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
|
||||
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
|
||||
wavebuffers_consumed++;
|
||||
}
|
||||
|
||||
if (samples_decoded == 0) {
|
||||
is_buffer_starved = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args.IsVoicePitchAndSrcSkippedSupported) {
|
||||
if (samples_read > output_buffer.size()) {
|
||||
LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!");
|
||||
}
|
||||
for (u32 i = 0; i < samples_read; i++) {
|
||||
output_buffer[i] = temp_buffer[i];
|
||||
}
|
||||
} else {
|
||||
std::memset(&temp_buffer[temp_buffer_pos], 0,
|
||||
(samples_to_read - samples_read) * sizeof(s16));
|
||||
|
||||
Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write,
|
||||
args.src_quality);
|
||||
|
||||
std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read],
|
||||
pitch * sizeof(s16));
|
||||
}
|
||||
|
||||
remaining_sample_count -= samples_to_write;
|
||||
if (remaining_sample_count != 0 && is_buffer_starved) {
|
||||
LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??");
|
||||
break;
|
||||
}
|
||||
|
||||
output_buffer = output_buffer.subspan(samples_to_write);
|
||||
}
|
||||
|
||||
voice_state.wave_buffers_consumed = wavebuffers_consumed;
|
||||
voice_state.played_sample_count = played_sample_count;
|
||||
voice_state.wave_buffer_index = wavebuffer_index;
|
||||
voice_state.offset = offset;
|
||||
voice_state.fraction = fraction;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
#include "audio_core/renderer/command/resample/resample.h"
|
||||
#include "common/fixed_point.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
constexpr u32 TempBufferSize = 0x3F00;
|
||||
constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
|
||||
|
||||
/**
|
||||
* Decode PCM data. Only s16 or f32 is supported.
|
||||
*
|
||||
* @tparam T - Type to decode. Only s16 and f32 are supported.
|
||||
* @param memory - Core memory for reading samples.
|
||||
* @param out_buffer - Output mix buffer to receive the samples.
|
||||
* @param req - Information for how to decode.
|
||||
* @return Number of samples decoded.
|
||||
*/
|
||||
template <typename T>
|
||||
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
constexpr s32 min{std::numeric_limits<s16>::min()};
|
||||
constexpr s32 max{std::numeric_limits<s16>::max()};
|
||||
|
||||
if (req.buffer == 0 || req.buffer_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (req.start_offset >= req.end_offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_decode{
|
||||
std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)};
|
||||
u32 channel_count{static_cast<u32>(req.channel_count)};
|
||||
|
||||
switch (req.channel_count) {
|
||||
default: {
|
||||
const VAddr source{req.buffer +
|
||||
(((req.start_offset + req.offset) * channel_count) * sizeof(T))};
|
||||
const u64 size{channel_count * samples_to_decode};
|
||||
const u64 size_bytes{size * sizeof(T)};
|
||||
|
||||
std::vector<T> samples(size);
|
||||
memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
|
||||
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
|
||||
std::numeric_limits<s16>::max())};
|
||||
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
|
||||
}
|
||||
} else {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
out_buffer[i] = samples[i * channel_count + req.target_channel];
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case 1:
|
||||
if (req.target_channel != 0) {
|
||||
LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}",
|
||||
req.target_channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
|
||||
std::vector<T> samples(samples_to_decode);
|
||||
memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
|
||||
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
|
||||
std::numeric_limits<s16>::max())};
|
||||
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
|
||||
}
|
||||
} else {
|
||||
std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return samples_to_decode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode ADPCM data.
|
||||
*
|
||||
* @param memory - Core memory for reading samples.
|
||||
* @param out_buffer - Output mix buffer to receive the samples.
|
||||
* @param req - Information for how to decode.
|
||||
* @return Number of samples decoded.
|
||||
*/
|
||||
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
constexpr u32 SamplesPerFrame{14};
|
||||
constexpr u32 NibblesPerFrame{16};
|
||||
|
||||
if (req.buffer == 0 || req.buffer_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (req.end_offset < req.start_offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto end{(req.end_offset % SamplesPerFrame) +
|
||||
NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
|
||||
if (req.end_offset % SamplesPerFrame) {
|
||||
end += 3;
|
||||
} else {
|
||||
end += 1;
|
||||
}
|
||||
|
||||
if (req.buffer_size < end / 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_process{
|
||||
std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
|
||||
|
||||
auto samples_to_read{samples_to_process};
|
||||
auto start_pos{req.start_offset + req.offset};
|
||||
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
|
||||
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
|
||||
samples_remaining_in_frame};
|
||||
|
||||
if (samples_remaining_in_frame) {
|
||||
position_in_frame += 2;
|
||||
}
|
||||
|
||||
const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
|
||||
std::vector<u8> wavebuffer(size);
|
||||
memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
|
||||
wavebuffer.size());
|
||||
|
||||
auto context{req.adpcm_context};
|
||||
auto header{context->header};
|
||||
u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
|
||||
u8 scale{static_cast<u8>(header & 0xFU)};
|
||||
s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
|
||||
s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
|
||||
|
||||
auto yn0{context->yn0};
|
||||
auto yn1{context->yn1};
|
||||
|
||||
static constexpr std::array<s32, 16> Steps{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
|
||||
};
|
||||
|
||||
const auto decode_sample = [&](const s32 code) -> s16 {
|
||||
const auto xn = code * (1 << scale);
|
||||
const auto prediction = coeff0 * yn0 + coeff1 * yn1;
|
||||
const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
|
||||
const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
|
||||
yn1 = yn0;
|
||||
yn0 = static_cast<s16>(saturated);
|
||||
return yn0;
|
||||
};
|
||||
|
||||
u32 read_index{0};
|
||||
u32 write_index{0};
|
||||
|
||||
while (samples_to_read > 0) {
|
||||
// Are we at a new frame?
|
||||
if ((position_in_frame % NibblesPerFrame) == 0) {
|
||||
header = wavebuffer[read_index++];
|
||||
coeff_index = (header >> 4) & 0xF;
|
||||
scale = header & 0xF;
|
||||
coeff0 = req.coefficients[coeff_index * 2 + 0];
|
||||
coeff1 = req.coefficients[coeff_index * 2 + 1];
|
||||
position_in_frame += 2;
|
||||
|
||||
// Can we consume all of this frame's samples?
|
||||
if (samples_to_read >= SamplesPerFrame) {
|
||||
// Can grab all samples until the next header
|
||||
for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
|
||||
auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
|
||||
auto code1{Steps[wavebuffer[read_index] & 0xF]};
|
||||
read_index++;
|
||||
|
||||
out_buffer[write_index++] = decode_sample(code0);
|
||||
out_buffer[write_index++] = decode_sample(code1);
|
||||
}
|
||||
|
||||
position_in_frame += SamplesPerFrame;
|
||||
samples_to_read -= SamplesPerFrame;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a single sample
|
||||
auto code{wavebuffer[read_index]};
|
||||
if (position_in_frame & 1) {
|
||||
code &= 0xF;
|
||||
read_index++;
|
||||
} else {
|
||||
code >>= 4;
|
||||
}
|
||||
|
||||
out_buffer[write_index++] = decode_sample(Steps[code]);
|
||||
|
||||
position_in_frame++;
|
||||
samples_to_read--;
|
||||
}
|
||||
|
||||
context->header = header;
|
||||
context->yn0 = yn0;
|
||||
context->yn1 = yn1;
|
||||
|
||||
return samples_to_process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode implementation.
|
||||
* Decode wavebuffers according to the given args.
|
||||
*
|
||||
* @param memory - Core memory to read data from.
|
||||
* @param args - The wavebuffer data, and information for how to decode it.
|
||||
*/
|
||||
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
|
||||
auto& voice_state{*args.voice_state};
|
||||
auto remaining_sample_count{args.sample_count};
|
||||
auto fraction{voice_state.fraction};
|
||||
|
||||
const auto sample_rate_ratio{
|
||||
(Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
|
||||
args.pitch};
|
||||
const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
|
||||
|
||||
if (size_required < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
|
||||
if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto max_remaining_sample_count{
|
||||
((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio)
|
||||
.to_uint_floor()};
|
||||
max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count);
|
||||
|
||||
auto wavebuffers_consumed{voice_state.wave_buffers_consumed};
|
||||
auto wavebuffer_index{voice_state.wave_buffer_index};
|
||||
auto played_sample_count{voice_state.played_sample_count};
|
||||
|
||||
bool is_buffer_starved{false};
|
||||
u32 offset{voice_state.offset};
|
||||
|
||||
auto output_buffer{args.output};
|
||||
std::vector<s16> temp_buffer(TempBufferSize, 0);
|
||||
|
||||
while (remaining_sample_count > 0) {
|
||||
const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};
|
||||
const auto samples_to_read{
|
||||
(fraction + samples_to_write * sample_rate_ratio).to_uint_floor()};
|
||||
|
||||
u32 temp_buffer_pos{0};
|
||||
|
||||
if (!args.IsVoicePitchAndSrcSkippedSupported) {
|
||||
for (u32 i = 0; i < pitch; i++) {
|
||||
temp_buffer[i] = voice_state.sample_history[i];
|
||||
}
|
||||
temp_buffer_pos = pitch;
|
||||
}
|
||||
|
||||
u32 samples_read{0};
|
||||
while (samples_read < samples_to_read) {
|
||||
if (wavebuffer_index >= MaxWaveBuffers) {
|
||||
LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
|
||||
wavebuffer_index = 0;
|
||||
voice_state.wave_buffer_valid.fill(false);
|
||||
wavebuffers_consumed = MaxWaveBuffers;
|
||||
}
|
||||
|
||||
if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
|
||||
is_buffer_starved = true;
|
||||
break;
|
||||
}
|
||||
|
||||
auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
|
||||
|
||||
if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
|
||||
wavebuffer.context != 0) {
|
||||
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
|
||||
wavebuffer.context_size);
|
||||
}
|
||||
|
||||
auto start_offset{wavebuffer.start_offset};
|
||||
auto end_offset{wavebuffer.end_offset};
|
||||
|
||||
if (wavebuffer.loop && voice_state.loop_count > 0 &&
|
||||
wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
|
||||
wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
|
||||
start_offset = wavebuffer.loop_start_offset;
|
||||
end_offset = wavebuffer.loop_end_offset;
|
||||
}
|
||||
|
||||
DecodeArg decode_arg{.buffer{wavebuffer.buffer},
|
||||
.buffer_size{wavebuffer.buffer_size},
|
||||
.start_offset{start_offset},
|
||||
.end_offset{end_offset},
|
||||
.channel_count{args.channel_count},
|
||||
.coefficients{},
|
||||
.adpcm_context{nullptr},
|
||||
.target_channel{args.channel},
|
||||
.offset{offset},
|
||||
.samples_to_read{samples_to_read - samples_read}};
|
||||
|
||||
s32 samples_decoded{0};
|
||||
|
||||
switch (args.sample_format) {
|
||||
case SampleFormat::PcmInt16:
|
||||
samples_decoded = DecodePcm<s16>(
|
||||
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
|
||||
decode_arg);
|
||||
break;
|
||||
|
||||
case SampleFormat::PcmFloat:
|
||||
samples_decoded = DecodePcm<f32>(
|
||||
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
|
||||
decode_arg);
|
||||
break;
|
||||
|
||||
case SampleFormat::Adpcm: {
|
||||
decode_arg.adpcm_context = &voice_state.adpcm_context;
|
||||
memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
|
||||
samples_decoded = DecodeAdpcm(
|
||||
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
|
||||
decode_arg);
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid sample format to decode {}",
|
||||
static_cast<u32>(args.sample_format));
|
||||
samples_decoded = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
played_sample_count += samples_decoded;
|
||||
samples_read += samples_decoded;
|
||||
temp_buffer_pos += samples_decoded;
|
||||
offset += samples_decoded;
|
||||
|
||||
if (samples_decoded == 0 || offset >= end_offset - start_offset) {
|
||||
offset = 0;
|
||||
if (!wavebuffer.loop) {
|
||||
voice_state.wave_buffer_valid[wavebuffer_index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
|
||||
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
|
||||
wavebuffers_consumed++;
|
||||
} else {
|
||||
voice_state.loop_count++;
|
||||
if (wavebuffer.loop_count > 0 &&
|
||||
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
|
||||
voice_state.wave_buffer_valid[wavebuffer_index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
|
||||
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
|
||||
wavebuffers_consumed++;
|
||||
}
|
||||
|
||||
if (samples_decoded == 0) {
|
||||
is_buffer_starved = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args.IsVoicePitchAndSrcSkippedSupported) {
|
||||
if (samples_read > output_buffer.size()) {
|
||||
LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!");
|
||||
}
|
||||
for (u32 i = 0; i < samples_read; i++) {
|
||||
output_buffer[i] = temp_buffer[i];
|
||||
}
|
||||
} else {
|
||||
std::memset(&temp_buffer[temp_buffer_pos], 0,
|
||||
(samples_to_read - samples_read) * sizeof(s16));
|
||||
|
||||
Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write,
|
||||
args.src_quality);
|
||||
|
||||
std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read],
|
||||
pitch * sizeof(s16));
|
||||
}
|
||||
|
||||
remaining_sample_count -= samples_to_write;
|
||||
if (remaining_sample_count != 0 && is_buffer_starved) {
|
||||
LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??");
|
||||
break;
|
||||
}
|
||||
|
||||
output_buffer = output_buffer.subspan(samples_to_write);
|
||||
}
|
||||
|
||||
voice_state.wave_buffers_consumed = wavebuffers_consumed;
|
||||
voice_state.played_sample_count = played_sample_count;
|
||||
voice_state.wave_buffer_index = wavebuffer_index;
|
||||
voice_state.offset = offset;
|
||||
voice_state.fraction = fraction;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/common/wave_buffer.h"
|
||||
#include "audio_core/renderer/voice/voice_state.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
struct DecodeFromWaveBuffersArgs {
|
||||
SampleFormat sample_format;
|
||||
std::span<s32> output;
|
||||
VoiceState* voice_state;
|
||||
std::span<WaveBufferVersion2> wave_buffers;
|
||||
s8 channel;
|
||||
s8 channel_count;
|
||||
SrcQuality src_quality;
|
||||
f32 pitch;
|
||||
u32 source_sample_rate;
|
||||
u32 target_sample_rate;
|
||||
u32 sample_count;
|
||||
CpuAddr data_address;
|
||||
u64 data_size;
|
||||
bool IsVoicePlayedSampleCountResetAtLoopPointSupported;
|
||||
bool IsVoicePitchAndSrcSkippedSupported;
|
||||
};
|
||||
|
||||
struct DecodeArg {
|
||||
CpuAddr buffer;
|
||||
u64 buffer_size;
|
||||
u32 start_offset;
|
||||
u32 end_offset;
|
||||
s8 channel_count;
|
||||
std::array<s16, 16> coefficients;
|
||||
VoiceState::AdpcmContext* adpcm_context;
|
||||
s8 target_channel;
|
||||
u32 offset;
|
||||
u32 samples_to_read;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode wavebuffers according to the given args.
|
||||
*
|
||||
* @param memory - Core memory to read data from.
|
||||
* @param args - The wavebuffer data, and information for how to decode it.
|
||||
*/
|
||||
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args);
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/common/wave_buffer.h"
|
||||
#include "audio_core/renderer/voice/voice_state.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
struct DecodeFromWaveBuffersArgs {
|
||||
SampleFormat sample_format;
|
||||
std::span<s32> output;
|
||||
VoiceState* voice_state;
|
||||
std::span<WaveBufferVersion2> wave_buffers;
|
||||
s8 channel;
|
||||
s8 channel_count;
|
||||
SrcQuality src_quality;
|
||||
f32 pitch;
|
||||
u32 source_sample_rate;
|
||||
u32 target_sample_rate;
|
||||
u32 sample_count;
|
||||
CpuAddr data_address;
|
||||
u64 data_size;
|
||||
bool IsVoicePlayedSampleCountResetAtLoopPointSupported;
|
||||
bool IsVoicePitchAndSrcSkippedSupported;
|
||||
};
|
||||
|
||||
struct DecodeArg {
|
||||
CpuAddr buffer;
|
||||
u64 buffer_size;
|
||||
u32 start_offset;
|
||||
u32 end_offset;
|
||||
s8 channel_count;
|
||||
std::array<s16, 16> coefficients;
|
||||
VoiceState::AdpcmContext* adpcm_context;
|
||||
s8 target_channel;
|
||||
u32 offset;
|
||||
u32 samples_to_read;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode wavebuffers according to the given args.
|
||||
*
|
||||
* @param memory - Core memory to read data from.
|
||||
* @param args - The wavebuffer data, and information for how to decode it.
|
||||
*/
|
||||
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args);
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,86 +1,86 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
#include "audio_core/renderer/command/data_source/pcm_float.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string +=
|
||||
fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} "
|
||||
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
|
||||
output_index, channel_index, channel_count, sample_rate,
|
||||
processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmFloat},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{channel_index},
|
||||
.channel_count{channel_count},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{0},
|
||||
.data_size{0},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string +=
|
||||
fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} "
|
||||
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
|
||||
output_index, channel_index, channel_count, sample_rate,
|
||||
processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmFloat},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{channel_index},
|
||||
.channel_count{channel_count},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{0},
|
||||
.data_size{0},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
#include "audio_core/renderer/command/data_source/pcm_float.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string +=
|
||||
fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} "
|
||||
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
|
||||
output_index, channel_index, channel_count, sample_rate,
|
||||
processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmFloat},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{channel_index},
|
||||
.channel_count{channel_count},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{0},
|
||||
.data_size{0},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string +=
|
||||
fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} "
|
||||
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
|
||||
output_index, channel_index, channel_count, sample_rate,
|
||||
processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmFloat},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{channel_index},
|
||||
.channel_count{channel_count},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{0},
|
||||
.data_size{0},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,113 +1,113 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/common/wave_buffer.h"
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode PCM float-encoded version 1 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct PcmFloatDataSourceVersion1Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Target channel to read within the wavebuffer
|
||||
s8 channel_index;
|
||||
/// Number of channels within the wavebuffer
|
||||
s8 channel_count;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
};
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode PCM float-encoded version 2 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct PcmFloatDataSourceVersion2Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Target channel to read within the wavebuffer
|
||||
s8 channel_index;
|
||||
/// Number of channels within the wavebuffer
|
||||
s8 channel_count;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/common/wave_buffer.h"
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode PCM float-encoded version 1 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct PcmFloatDataSourceVersion1Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Target channel to read within the wavebuffer
|
||||
s8 channel_index;
|
||||
/// Number of channels within the wavebuffer
|
||||
s8 channel_count;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
};
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode PCM float-encoded version 2 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct PcmFloatDataSourceVersion2Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Target channel to read within the wavebuffer
|
||||
s8 channel_index;
|
||||
/// Number of channels within the wavebuffer
|
||||
s8 channel_count;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
#include "audio_core/renderer/command/data_source/pcm_int16.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string +=
|
||||
fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} "
|
||||
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
|
||||
output_index, channel_index, channel_count, sample_rate,
|
||||
processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmInt16},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{channel_index},
|
||||
.channel_count{channel_count},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{0},
|
||||
.data_size{0},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string +=
|
||||
fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} "
|
||||
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
|
||||
output_index, channel_index, channel_count, sample_rate,
|
||||
processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmInt16},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{channel_index},
|
||||
.channel_count{channel_count},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{0},
|
||||
.data_size{0},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
#include "audio_core/renderer/command/data_source/pcm_int16.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string +=
|
||||
fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} "
|
||||
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
|
||||
output_index, channel_index, channel_count, sample_rate,
|
||||
processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmInt16},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{channel_index},
|
||||
.channel_count{channel_count},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{0},
|
||||
.data_size{0},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string +=
|
||||
fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} "
|
||||
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
|
||||
output_index, channel_index, channel_count, sample_rate,
|
||||
processor.target_sample_rate, src_quality);
|
||||
}
|
||||
|
||||
void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmInt16},
|
||||
.output{out_buffer},
|
||||
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
|
||||
.wave_buffers{wave_buffers},
|
||||
.channel{channel_index},
|
||||
.channel_count{channel_count},
|
||||
.src_quality{src_quality},
|
||||
.pitch{pitch},
|
||||
.source_sample_rate{sample_rate},
|
||||
.target_sample_rate{processor.target_sample_rate},
|
||||
.sample_count{processor.sample_count},
|
||||
.data_address{0},
|
||||
.data_size{0},
|
||||
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
|
||||
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
|
||||
};
|
||||
|
||||
DecodeFromWaveBuffers(*processor.memory, args);
|
||||
}
|
||||
|
||||
bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,110 +1,110 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/common/wave_buffer.h"
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct PcmInt16DataSourceVersion1Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Target channel to read within the wavebuffer
|
||||
s8 channel_index;
|
||||
/// Number of channels within the wavebuffer
|
||||
s8 channel_count;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
};
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct PcmInt16DataSourceVersion2Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Target channel to read within the wavebuffer
|
||||
s8 channel_index;
|
||||
/// Number of channels within the wavebuffer
|
||||
s8 channel_count;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/common/wave_buffer.h"
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct PcmInt16DataSourceVersion1Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Target channel to read within the wavebuffer
|
||||
s8 channel_index;
|
||||
/// Number of channels within the wavebuffer
|
||||
s8 channel_count;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
};
|
||||
|
||||
/**
|
||||
* AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers
|
||||
* into the output_index mix buffer.
|
||||
*/
|
||||
struct PcmInt16DataSourceVersion2Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Quality used for sample rate conversion
|
||||
SrcQuality src_quality;
|
||||
/// Mix buffer index for decoded samples
|
||||
s16 output_index;
|
||||
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
|
||||
u16 flags;
|
||||
/// Wavebuffer sample rate
|
||||
u32 sample_rate;
|
||||
/// Pitch used for sample rate conversion
|
||||
f32 pitch;
|
||||
/// Target channel to read within the wavebuffer
|
||||
s8 channel_index;
|
||||
/// Number of channels within the wavebuffer
|
||||
s8 channel_count;
|
||||
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
|
||||
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
|
||||
/// Voice state, updated each call and written back to game
|
||||
CpuAddr voice_state;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,207 +1,207 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/aux_.h"
|
||||
#include "audio_core/renderer/effect/aux_.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Reset an AuxBuffer.
|
||||
*
|
||||
* @param memory - Core memory for writing.
|
||||
* @param aux_info - Memory address pointing to the AuxInfo to reset.
|
||||
*/
|
||||
static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
|
||||
if (aux_info == 0) {
|
||||
LOG_ERROR(Service_Audio, "Aux info is 0!");
|
||||
return;
|
||||
}
|
||||
|
||||
auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))};
|
||||
info->read_offset = 0;
|
||||
info->write_offset = 0;
|
||||
info->total_sample_count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
|
||||
* update_count is set, to notify the game that an update happened.
|
||||
*
|
||||
* @param memory - Core memory for writing.
|
||||
* @param send_info_ - Meta information for where to write the mix buffer.
|
||||
* @param sample_count - Unused.
|
||||
* @param send_buffer - Memory address to write the mix buffer to.
|
||||
* @param count_max - Maximum number of samples in the receiving buffer.
|
||||
* @param input - Input mix buffer to write.
|
||||
* @param write_count_ - Number of samples to write.
|
||||
* @param write_offset - Current offset to begin writing the receiving buffer at.
|
||||
* @param update_count - If non-zero, send_info_ will be updated.
|
||||
* @return Number of samples written.
|
||||
*/
|
||||
static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
|
||||
[[maybe_unused]] u32 sample_count, const CpuAddr send_buffer,
|
||||
const u32 count_max, std::span<const s32> input,
|
||||
const u32 write_count_, const u32 write_offset,
|
||||
const u32 update_count) {
|
||||
if (write_count_ > count_max) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"write_count must be smaller than count_max! write_count {}, count_max {}",
|
||||
write_count_, count_max);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (input.empty()) {
|
||||
LOG_ERROR(Service_Audio, "input buffer is empty!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (send_buffer == 0) {
|
||||
LOG_ERROR(Service_Audio, "send_buffer is 0!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count_max == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
AuxInfo::AuxInfoDsp send_info{};
|
||||
memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
|
||||
u32 target_write_offset{send_info.write_offset + write_offset};
|
||||
if (target_write_offset > count_max || write_count_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 write_count{write_count_};
|
||||
u32 write_pos{0};
|
||||
while (write_count > 0) {
|
||||
u32 to_write{std::min(count_max - target_write_offset, write_count)};
|
||||
|
||||
if (to_write > 0) {
|
||||
memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
|
||||
&input[write_pos], to_write * sizeof(s32));
|
||||
}
|
||||
|
||||
target_write_offset = (target_write_offset + to_write) % count_max;
|
||||
write_count -= to_write;
|
||||
write_pos += to_write;
|
||||
}
|
||||
|
||||
if (update_count) {
|
||||
send_info.write_offset = (send_info.write_offset + update_count) % count_max;
|
||||
}
|
||||
|
||||
memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
|
||||
return write_count_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the given memory at return_buffer into the output mix buffer, and update return_info_ if
|
||||
* update_count is set, to notify the game that an update happened.
|
||||
*
|
||||
* @param memory - Core memory for writing.
|
||||
* @param return_info_ - Meta information for where to read the mix buffer.
|
||||
* @param return_buffer - Memory address to read the samples from.
|
||||
* @param count_max - Maximum number of samples in the receiving buffer.
|
||||
* @param output - Output mix buffer which will receive the samples.
|
||||
* @param count_ - Number of samples to read.
|
||||
* @param read_offset - Current offset to begin reading the return_buffer at.
|
||||
* @param update_count - If non-zero, send_info_ will be updated.
|
||||
* @return Number of samples read.
|
||||
*/
|
||||
static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_,
|
||||
const CpuAddr return_buffer, const u32 count_max, std::span<s32> output,
|
||||
const u32 count_, const u32 read_offset, const u32 update_count) {
|
||||
if (count_max == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count_ > count_max) {
|
||||
LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}",
|
||||
count_, count_max);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (output.empty()) {
|
||||
LOG_ERROR(Service_Audio, "output buffer is empty!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (return_buffer == 0) {
|
||||
LOG_ERROR(Service_Audio, "return_buffer is 0!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
AuxInfo::AuxInfoDsp return_info{};
|
||||
memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
|
||||
u32 target_read_offset{return_info.read_offset + read_offset};
|
||||
if (target_read_offset > count_max) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 read_count{count_};
|
||||
u32 read_pos{0};
|
||||
while (read_count > 0) {
|
||||
u32 to_read{std::min(count_max - target_read_offset, read_count)};
|
||||
|
||||
if (to_read > 0) {
|
||||
memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32),
|
||||
&output[read_pos], to_read * sizeof(s32));
|
||||
}
|
||||
|
||||
target_read_offset = (target_read_offset + to_read) % count_max;
|
||||
read_count -= to_read;
|
||||
read_pos += to_read;
|
||||
}
|
||||
|
||||
if (update_count) {
|
||||
return_info.read_offset = (return_info.read_offset + update_count) % count_max;
|
||||
}
|
||||
|
||||
memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
|
||||
return count_;
|
||||
}
|
||||
|
||||
void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled,
|
||||
input, output);
|
||||
}
|
||||
|
||||
void AuxCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto input_buffer{
|
||||
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
|
||||
auto output_buffer{
|
||||
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
|
||||
|
||||
if (effect_enabled) {
|
||||
WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer,
|
||||
count_max, input_buffer, processor.sample_count, write_offset,
|
||||
update_count);
|
||||
|
||||
auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max,
|
||||
output_buffer, processor.sample_count, write_offset,
|
||||
update_count)};
|
||||
|
||||
if (read != processor.sample_count) {
|
||||
std::memset(&output_buffer[read], 0, processor.sample_count - read);
|
||||
}
|
||||
} else {
|
||||
ResetAuxBufferDsp(*processor.memory, send_buffer_info);
|
||||
ResetAuxBufferDsp(*processor.memory, return_buffer_info);
|
||||
if (input != output) {
|
||||
std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/aux_.h"
|
||||
#include "audio_core/renderer/effect/aux_.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Reset an AuxBuffer.
|
||||
*
|
||||
* @param memory - Core memory for writing.
|
||||
* @param aux_info - Memory address pointing to the AuxInfo to reset.
|
||||
*/
|
||||
static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
|
||||
if (aux_info == 0) {
|
||||
LOG_ERROR(Service_Audio, "Aux info is 0!");
|
||||
return;
|
||||
}
|
||||
|
||||
auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))};
|
||||
info->read_offset = 0;
|
||||
info->write_offset = 0;
|
||||
info->total_sample_count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
|
||||
* update_count is set, to notify the game that an update happened.
|
||||
*
|
||||
* @param memory - Core memory for writing.
|
||||
* @param send_info_ - Meta information for where to write the mix buffer.
|
||||
* @param sample_count - Unused.
|
||||
* @param send_buffer - Memory address to write the mix buffer to.
|
||||
* @param count_max - Maximum number of samples in the receiving buffer.
|
||||
* @param input - Input mix buffer to write.
|
||||
* @param write_count_ - Number of samples to write.
|
||||
* @param write_offset - Current offset to begin writing the receiving buffer at.
|
||||
* @param update_count - If non-zero, send_info_ will be updated.
|
||||
* @return Number of samples written.
|
||||
*/
|
||||
static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
|
||||
[[maybe_unused]] u32 sample_count, const CpuAddr send_buffer,
|
||||
const u32 count_max, std::span<const s32> input,
|
||||
const u32 write_count_, const u32 write_offset,
|
||||
const u32 update_count) {
|
||||
if (write_count_ > count_max) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"write_count must be smaller than count_max! write_count {}, count_max {}",
|
||||
write_count_, count_max);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (input.empty()) {
|
||||
LOG_ERROR(Service_Audio, "input buffer is empty!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (send_buffer == 0) {
|
||||
LOG_ERROR(Service_Audio, "send_buffer is 0!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count_max == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
AuxInfo::AuxInfoDsp send_info{};
|
||||
memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
|
||||
u32 target_write_offset{send_info.write_offset + write_offset};
|
||||
if (target_write_offset > count_max || write_count_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 write_count{write_count_};
|
||||
u32 write_pos{0};
|
||||
while (write_count > 0) {
|
||||
u32 to_write{std::min(count_max - target_write_offset, write_count)};
|
||||
|
||||
if (to_write > 0) {
|
||||
memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
|
||||
&input[write_pos], to_write * sizeof(s32));
|
||||
}
|
||||
|
||||
target_write_offset = (target_write_offset + to_write) % count_max;
|
||||
write_count -= to_write;
|
||||
write_pos += to_write;
|
||||
}
|
||||
|
||||
if (update_count) {
|
||||
send_info.write_offset = (send_info.write_offset + update_count) % count_max;
|
||||
}
|
||||
|
||||
memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
|
||||
return write_count_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the given memory at return_buffer into the output mix buffer, and update return_info_ if
|
||||
* update_count is set, to notify the game that an update happened.
|
||||
*
|
||||
* @param memory - Core memory for writing.
|
||||
* @param return_info_ - Meta information for where to read the mix buffer.
|
||||
* @param return_buffer - Memory address to read the samples from.
|
||||
* @param count_max - Maximum number of samples in the receiving buffer.
|
||||
* @param output - Output mix buffer which will receive the samples.
|
||||
* @param count_ - Number of samples to read.
|
||||
* @param read_offset - Current offset to begin reading the return_buffer at.
|
||||
* @param update_count - If non-zero, send_info_ will be updated.
|
||||
* @return Number of samples read.
|
||||
*/
|
||||
static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_,
|
||||
const CpuAddr return_buffer, const u32 count_max, std::span<s32> output,
|
||||
const u32 count_, const u32 read_offset, const u32 update_count) {
|
||||
if (count_max == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count_ > count_max) {
|
||||
LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}",
|
||||
count_, count_max);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (output.empty()) {
|
||||
LOG_ERROR(Service_Audio, "output buffer is empty!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (return_buffer == 0) {
|
||||
LOG_ERROR(Service_Audio, "return_buffer is 0!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
AuxInfo::AuxInfoDsp return_info{};
|
||||
memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
|
||||
u32 target_read_offset{return_info.read_offset + read_offset};
|
||||
if (target_read_offset > count_max) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 read_count{count_};
|
||||
u32 read_pos{0};
|
||||
while (read_count > 0) {
|
||||
u32 to_read{std::min(count_max - target_read_offset, read_count)};
|
||||
|
||||
if (to_read > 0) {
|
||||
memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32),
|
||||
&output[read_pos], to_read * sizeof(s32));
|
||||
}
|
||||
|
||||
target_read_offset = (target_read_offset + to_read) % count_max;
|
||||
read_count -= to_read;
|
||||
read_pos += to_read;
|
||||
}
|
||||
|
||||
if (update_count) {
|
||||
return_info.read_offset = (return_info.read_offset + update_count) % count_max;
|
||||
}
|
||||
|
||||
memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
|
||||
return count_;
|
||||
}
|
||||
|
||||
void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled,
|
||||
input, output);
|
||||
}
|
||||
|
||||
void AuxCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto input_buffer{
|
||||
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
|
||||
auto output_buffer{
|
||||
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
|
||||
|
||||
if (effect_enabled) {
|
||||
WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer,
|
||||
count_max, input_buffer, processor.sample_count, write_offset,
|
||||
update_count);
|
||||
|
||||
auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max,
|
||||
output_buffer, processor.sample_count, write_offset,
|
||||
update_count)};
|
||||
|
||||
if (read != processor.sample_count) {
|
||||
std::memset(&output_buffer[read], 0, processor.sample_count - read);
|
||||
}
|
||||
} else {
|
||||
ResetAuxBufferDsp(*processor.memory, send_buffer_info);
|
||||
ResetAuxBufferDsp(*processor.memory, return_buffer_info);
|
||||
if (input != output) {
|
||||
std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game
|
||||
* memory, and reading into the output buffer from game memory.
|
||||
*/
|
||||
struct AuxCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer index
|
||||
s16 input;
|
||||
/// Output mix buffer index
|
||||
s16 output;
|
||||
/// Meta info for writing
|
||||
CpuAddr send_buffer_info;
|
||||
/// Meta info for reading
|
||||
CpuAddr return_buffer_info;
|
||||
/// Game memory write buffer
|
||||
CpuAddr send_buffer;
|
||||
/// Game memory read buffer
|
||||
CpuAddr return_buffer;
|
||||
/// Max samples to read/write
|
||||
u32 count_max;
|
||||
/// Current read/write offset
|
||||
u32 write_offset;
|
||||
/// Number of samples to update per call
|
||||
u32 update_count;
|
||||
/// is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game
|
||||
* memory, and reading into the output buffer from game memory.
|
||||
*/
|
||||
struct AuxCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer index
|
||||
s16 input;
|
||||
/// Output mix buffer index
|
||||
s16 output;
|
||||
/// Meta info for writing
|
||||
CpuAddr send_buffer_info;
|
||||
/// Meta info for reading
|
||||
CpuAddr return_buffer_info;
|
||||
/// Game memory write buffer
|
||||
CpuAddr send_buffer;
|
||||
/// Game memory read buffer
|
||||
CpuAddr return_buffer;
|
||||
/// Max samples to read/write
|
||||
u32 count_max;
|
||||
/// Current read/write offset
|
||||
u32 write_offset;
|
||||
/// Number of samples to update per call
|
||||
u32 update_count;
|
||||
/// is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,118 +1,118 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/biquad_filter.h"
|
||||
#include "audio_core/renderer/voice/voice_state.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Biquad filter float implementation.
|
||||
*
|
||||
* @param output - Output container for filtered samples.
|
||||
* @param input - Input container for samples to be filtered.
|
||||
* @param b - Feedforward coefficients.
|
||||
* @param a - Feedback coefficients.
|
||||
* @param state - State to track previous samples between calls.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
|
||||
std::array<s16, 3>& b_, std::array<s16, 2>& a_,
|
||||
VoiceState::BiquadFilterState& state, const u32 sample_count) {
|
||||
constexpr s64 min{std::numeric_limits<s32>::min()};
|
||||
constexpr s64 max{std::numeric_limits<s32>::max()};
|
||||
std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(),
|
||||
Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(),
|
||||
Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()};
|
||||
std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(),
|
||||
Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()};
|
||||
std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(),
|
||||
state.s3.to_double()};
|
||||
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
f64 in_sample{static_cast<f64>(input[i])};
|
||||
auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]};
|
||||
|
||||
output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max));
|
||||
|
||||
s[1] = s[0];
|
||||
s[0] = in_sample;
|
||||
s[3] = s[2];
|
||||
s[2] = sample;
|
||||
}
|
||||
|
||||
state.s0 = s[0];
|
||||
state.s1 = s[1];
|
||||
state.s2 = s[2];
|
||||
state.s3 = s[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Biquad filter s32 implementation.
|
||||
*
|
||||
* @param output - Output container for filtered samples.
|
||||
* @param input - Input container for samples to be filtered.
|
||||
* @param b - Feedforward coefficients.
|
||||
* @param a - Feedback coefficients.
|
||||
* @param state - State to track previous samples between calls.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input,
|
||||
std::array<s16, 3>& b_, std::array<s16, 2>& a_,
|
||||
VoiceState::BiquadFilterState& state, const u32 sample_count) {
|
||||
constexpr s64 min{std::numeric_limits<s32>::min()};
|
||||
constexpr s64 max{std::numeric_limits<s32>::max()};
|
||||
std::array<Common::FixedPoint<50, 14>, 3> b{
|
||||
Common::FixedPoint<50, 14>::from_base(b_[0]),
|
||||
Common::FixedPoint<50, 14>::from_base(b_[1]),
|
||||
Common::FixedPoint<50, 14>::from_base(b_[2]),
|
||||
};
|
||||
std::array<Common::FixedPoint<50, 14>, 3> a{
|
||||
Common::FixedPoint<50, 14>::from_base(a_[0]),
|
||||
Common::FixedPoint<50, 14>::from_base(a_[1]),
|
||||
};
|
||||
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
s64 in_sample{input[i]};
|
||||
auto sample{in_sample * b[0] + state.s0};
|
||||
const auto out_sample{std::clamp(sample.to_long(), min, max)};
|
||||
|
||||
output[i] = static_cast<s32>(out_sample);
|
||||
|
||||
state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample;
|
||||
state.s1 = 0 + b[2] * in_sample + a[1] * out_sample;
|
||||
}
|
||||
}
|
||||
|
||||
void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format(
|
||||
"BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n",
|
||||
input, output, needs_init, use_float_processing);
|
||||
}
|
||||
|
||||
void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)};
|
||||
if (needs_init) {
|
||||
*state_ = {};
|
||||
}
|
||||
|
||||
auto input_buffer{
|
||||
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
|
||||
auto output_buffer{
|
||||
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
|
||||
|
||||
if (use_float_processing) {
|
||||
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
|
||||
processor.sample_count);
|
||||
} else {
|
||||
ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
|
||||
processor.sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/biquad_filter.h"
|
||||
#include "audio_core/renderer/voice/voice_state.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Biquad filter float implementation.
|
||||
*
|
||||
* @param output - Output container for filtered samples.
|
||||
* @param input - Input container for samples to be filtered.
|
||||
* @param b - Feedforward coefficients.
|
||||
* @param a - Feedback coefficients.
|
||||
* @param state - State to track previous samples between calls.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
|
||||
std::array<s16, 3>& b_, std::array<s16, 2>& a_,
|
||||
VoiceState::BiquadFilterState& state, const u32 sample_count) {
|
||||
constexpr s64 min{std::numeric_limits<s32>::min()};
|
||||
constexpr s64 max{std::numeric_limits<s32>::max()};
|
||||
std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(),
|
||||
Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(),
|
||||
Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()};
|
||||
std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(),
|
||||
Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()};
|
||||
std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(),
|
||||
state.s3.to_double()};
|
||||
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
f64 in_sample{static_cast<f64>(input[i])};
|
||||
auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]};
|
||||
|
||||
output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max));
|
||||
|
||||
s[1] = s[0];
|
||||
s[0] = in_sample;
|
||||
s[3] = s[2];
|
||||
s[2] = sample;
|
||||
}
|
||||
|
||||
state.s0 = s[0];
|
||||
state.s1 = s[1];
|
||||
state.s2 = s[2];
|
||||
state.s3 = s[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Biquad filter s32 implementation.
|
||||
*
|
||||
* @param output - Output container for filtered samples.
|
||||
* @param input - Input container for samples to be filtered.
|
||||
* @param b - Feedforward coefficients.
|
||||
* @param a - Feedback coefficients.
|
||||
* @param state - State to track previous samples between calls.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input,
|
||||
std::array<s16, 3>& b_, std::array<s16, 2>& a_,
|
||||
VoiceState::BiquadFilterState& state, const u32 sample_count) {
|
||||
constexpr s64 min{std::numeric_limits<s32>::min()};
|
||||
constexpr s64 max{std::numeric_limits<s32>::max()};
|
||||
std::array<Common::FixedPoint<50, 14>, 3> b{
|
||||
Common::FixedPoint<50, 14>::from_base(b_[0]),
|
||||
Common::FixedPoint<50, 14>::from_base(b_[1]),
|
||||
Common::FixedPoint<50, 14>::from_base(b_[2]),
|
||||
};
|
||||
std::array<Common::FixedPoint<50, 14>, 3> a{
|
||||
Common::FixedPoint<50, 14>::from_base(a_[0]),
|
||||
Common::FixedPoint<50, 14>::from_base(a_[1]),
|
||||
};
|
||||
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
s64 in_sample{input[i]};
|
||||
auto sample{in_sample * b[0] + state.s0};
|
||||
const auto out_sample{std::clamp(sample.to_long(), min, max)};
|
||||
|
||||
output[i] = static_cast<s32>(out_sample);
|
||||
|
||||
state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample;
|
||||
state.s1 = 0 + b[2] * in_sample + a[1] * out_sample;
|
||||
}
|
||||
}
|
||||
|
||||
void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format(
|
||||
"BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n",
|
||||
input, output, needs_init, use_float_processing);
|
||||
}
|
||||
|
||||
void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)};
|
||||
if (needs_init) {
|
||||
*state_ = {};
|
||||
}
|
||||
|
||||
auto input_buffer{
|
||||
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
|
||||
auto output_buffer{
|
||||
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
|
||||
|
||||
if (use_float_processing) {
|
||||
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
|
||||
processor.sample_count);
|
||||
} else {
|
||||
ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
|
||||
processor.sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,74 +1,74 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/voice/voice_info.h"
|
||||
#include "audio_core/renderer/voice/voice_state.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to
|
||||
* the output mix buffer.
|
||||
*/
|
||||
struct BiquadFilterCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer index
|
||||
s16 input;
|
||||
/// Output mix buffer index
|
||||
s16 output;
|
||||
/// Input parameters for biquad
|
||||
VoiceInfo::BiquadFilterParameter biquad;
|
||||
/// Biquad state, updated each call
|
||||
CpuAddr state;
|
||||
/// If true, reset the state
|
||||
bool needs_init;
|
||||
/// If true, use float processing rather than int
|
||||
bool use_float_processing;
|
||||
};
|
||||
|
||||
/**
|
||||
* Biquad filter float implementation.
|
||||
*
|
||||
* @param output - Output container for filtered samples.
|
||||
* @param input - Input container for samples to be filtered.
|
||||
* @param b - Feedforward coefficients.
|
||||
* @param a - Feedback coefficients.
|
||||
* @param state - State to track previous samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
|
||||
std::array<s16, 3>& b, std::array<s16, 2>& a,
|
||||
VoiceState::BiquadFilterState& state, const u32 sample_count);
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/voice/voice_info.h"
|
||||
#include "audio_core/renderer/voice/voice_state.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to
|
||||
* the output mix buffer.
|
||||
*/
|
||||
struct BiquadFilterCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer index
|
||||
s16 input;
|
||||
/// Output mix buffer index
|
||||
s16 output;
|
||||
/// Input parameters for biquad
|
||||
VoiceInfo::BiquadFilterParameter biquad;
|
||||
/// Biquad state, updated each call
|
||||
CpuAddr state;
|
||||
/// If true, reset the state
|
||||
bool needs_init;
|
||||
/// If true, use float processing rather than int
|
||||
bool use_float_processing;
|
||||
};
|
||||
|
||||
/**
|
||||
* Biquad filter float implementation.
|
||||
*
|
||||
* @param output - Output container for filtered samples.
|
||||
* @param input - Input container for samples to be filtered.
|
||||
* @param b - Feedforward coefficients.
|
||||
* @param a - Feedback coefficients.
|
||||
* @param state - State to track previous samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
|
||||
std::array<s16, 3>& b, std::array<s16, 2>& a,
|
||||
VoiceState::BiquadFilterState& state, const u32 sample_count);
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,142 +1,142 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/capture.h"
|
||||
#include "audio_core/renderer/effect/aux_.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Reset an AuxBuffer.
|
||||
*
|
||||
* @param memory - Core memory for writing.
|
||||
* @param aux_info - Memory address pointing to the AuxInfo to reset.
|
||||
*/
|
||||
static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
|
||||
if (aux_info == 0) {
|
||||
LOG_ERROR(Service_Audio, "Aux info is 0!");
|
||||
return;
|
||||
}
|
||||
|
||||
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0);
|
||||
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0);
|
||||
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
|
||||
* update_count is set, to notify the game that an update happened.
|
||||
*
|
||||
* @param memory - Core memory for writing.
|
||||
* @param send_info_ - Header information for where to write the mix buffer.
|
||||
* @param send_buffer - Memory address to write the mix buffer to.
|
||||
* @param count_max - Maximum number of samples in the receiving buffer.
|
||||
* @param input - Input mix buffer to write.
|
||||
* @param write_count_ - Number of samples to write.
|
||||
* @param write_offset - Current offset to begin writing the receiving buffer at.
|
||||
* @param update_count - If non-zero, send_info_ will be updated.
|
||||
* @return Number of samples written.
|
||||
*/
|
||||
static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
|
||||
const CpuAddr send_buffer, u32 count_max, std::span<const s32> input,
|
||||
const u32 write_count_, const u32 write_offset,
|
||||
const u32 update_count) {
|
||||
if (write_count_ > count_max) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"write_count must be smaller than count_max! write_count {}, count_max {}",
|
||||
write_count_, count_max);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (send_info_ == 0) {
|
||||
LOG_ERROR(Service_Audio, "send_info is 0!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (input.empty()) {
|
||||
LOG_ERROR(Service_Audio, "input buffer is empty!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (send_buffer == 0) {
|
||||
LOG_ERROR(Service_Audio, "send_buffer is 0!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count_max == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
AuxInfo::AuxBufferInfo send_info{};
|
||||
memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
|
||||
|
||||
u32 target_write_offset{send_info.dsp_info.write_offset + write_offset};
|
||||
if (target_write_offset > count_max || write_count_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 write_count{write_count_};
|
||||
u32 write_pos{0};
|
||||
while (write_count > 0) {
|
||||
u32 to_write{std::min(count_max - target_write_offset, write_count)};
|
||||
|
||||
if (to_write > 0) {
|
||||
memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
|
||||
&input[write_pos], to_write * sizeof(s32));
|
||||
}
|
||||
|
||||
target_write_offset = (target_write_offset + to_write) % count_max;
|
||||
write_count -= to_write;
|
||||
write_pos += to_write;
|
||||
}
|
||||
|
||||
if (update_count) {
|
||||
const auto count_diff{send_info.dsp_info.total_sample_count -
|
||||
send_info.cpu_info.total_sample_count};
|
||||
if (count_diff >= count_max) {
|
||||
auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count};
|
||||
if (dsp_lost_count - send_info.cpu_info.lost_sample_count <
|
||||
send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) {
|
||||
dsp_lost_count = send_info.cpu_info.lost_sample_count - 1;
|
||||
}
|
||||
send_info.dsp_info.lost_sample_count = dsp_lost_count;
|
||||
}
|
||||
|
||||
send_info.dsp_info.write_offset =
|
||||
(send_info.dsp_info.write_offset + update_count + count_max) % count_max;
|
||||
|
||||
auto new_sample_count{send_info.dsp_info.total_sample_count + update_count};
|
||||
if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) {
|
||||
new_sample_count = send_info.cpu_info.total_sample_count - 1;
|
||||
}
|
||||
send_info.dsp_info.total_sample_count = new_sample_count;
|
||||
}
|
||||
|
||||
memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
|
||||
|
||||
return write_count_;
|
||||
}
|
||||
|
||||
void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled,
|
||||
input, output);
|
||||
}
|
||||
|
||||
void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
if (effect_enabled) {
|
||||
auto input_buffer{
|
||||
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
|
||||
WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer,
|
||||
processor.sample_count, write_offset, update_count);
|
||||
} else {
|
||||
ResetAuxBufferDsp(*processor.memory, send_buffer_info);
|
||||
}
|
||||
}
|
||||
|
||||
bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/capture.h"
|
||||
#include "audio_core/renderer/effect/aux_.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Reset an AuxBuffer.
|
||||
*
|
||||
* @param memory - Core memory for writing.
|
||||
* @param aux_info - Memory address pointing to the AuxInfo to reset.
|
||||
*/
|
||||
static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
|
||||
if (aux_info == 0) {
|
||||
LOG_ERROR(Service_Audio, "Aux info is 0!");
|
||||
return;
|
||||
}
|
||||
|
||||
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0);
|
||||
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0);
|
||||
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
|
||||
* update_count is set, to notify the game that an update happened.
|
||||
*
|
||||
* @param memory - Core memory for writing.
|
||||
* @param send_info_ - Header information for where to write the mix buffer.
|
||||
* @param send_buffer - Memory address to write the mix buffer to.
|
||||
* @param count_max - Maximum number of samples in the receiving buffer.
|
||||
* @param input - Input mix buffer to write.
|
||||
* @param write_count_ - Number of samples to write.
|
||||
* @param write_offset - Current offset to begin writing the receiving buffer at.
|
||||
* @param update_count - If non-zero, send_info_ will be updated.
|
||||
* @return Number of samples written.
|
||||
*/
|
||||
static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
|
||||
const CpuAddr send_buffer, u32 count_max, std::span<const s32> input,
|
||||
const u32 write_count_, const u32 write_offset,
|
||||
const u32 update_count) {
|
||||
if (write_count_ > count_max) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"write_count must be smaller than count_max! write_count {}, count_max {}",
|
||||
write_count_, count_max);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (send_info_ == 0) {
|
||||
LOG_ERROR(Service_Audio, "send_info is 0!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (input.empty()) {
|
||||
LOG_ERROR(Service_Audio, "input buffer is empty!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (send_buffer == 0) {
|
||||
LOG_ERROR(Service_Audio, "send_buffer is 0!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count_max == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
AuxInfo::AuxBufferInfo send_info{};
|
||||
memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
|
||||
|
||||
u32 target_write_offset{send_info.dsp_info.write_offset + write_offset};
|
||||
if (target_write_offset > count_max || write_count_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 write_count{write_count_};
|
||||
u32 write_pos{0};
|
||||
while (write_count > 0) {
|
||||
u32 to_write{std::min(count_max - target_write_offset, write_count)};
|
||||
|
||||
if (to_write > 0) {
|
||||
memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
|
||||
&input[write_pos], to_write * sizeof(s32));
|
||||
}
|
||||
|
||||
target_write_offset = (target_write_offset + to_write) % count_max;
|
||||
write_count -= to_write;
|
||||
write_pos += to_write;
|
||||
}
|
||||
|
||||
if (update_count) {
|
||||
const auto count_diff{send_info.dsp_info.total_sample_count -
|
||||
send_info.cpu_info.total_sample_count};
|
||||
if (count_diff >= count_max) {
|
||||
auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count};
|
||||
if (dsp_lost_count - send_info.cpu_info.lost_sample_count <
|
||||
send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) {
|
||||
dsp_lost_count = send_info.cpu_info.lost_sample_count - 1;
|
||||
}
|
||||
send_info.dsp_info.lost_sample_count = dsp_lost_count;
|
||||
}
|
||||
|
||||
send_info.dsp_info.write_offset =
|
||||
(send_info.dsp_info.write_offset + update_count + count_max) % count_max;
|
||||
|
||||
auto new_sample_count{send_info.dsp_info.total_sample_count + update_count};
|
||||
if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) {
|
||||
new_sample_count = send_info.cpu_info.total_sample_count - 1;
|
||||
}
|
||||
send_info.dsp_info.total_sample_count = new_sample_count;
|
||||
}
|
||||
|
||||
memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
|
||||
|
||||
return write_count_;
|
||||
}
|
||||
|
||||
void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled,
|
||||
input, output);
|
||||
}
|
||||
|
||||
void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
if (effect_enabled) {
|
||||
auto input_buffer{
|
||||
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
|
||||
WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer,
|
||||
processor.sample_count, write_offset, update_count);
|
||||
} else {
|
||||
ResetAuxBufferDsp(*processor.memory, send_buffer_info);
|
||||
}
|
||||
}
|
||||
|
||||
bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory
|
||||
* address.
|
||||
*/
|
||||
struct CaptureCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer index
|
||||
s16 input;
|
||||
/// Output mix buffer index
|
||||
s16 output;
|
||||
/// Meta info for writing
|
||||
CpuAddr send_buffer_info;
|
||||
/// Game memory write buffer
|
||||
CpuAddr send_buffer;
|
||||
/// Max samples to read/write
|
||||
u32 count_max;
|
||||
/// Current read/write offset
|
||||
u32 write_offset;
|
||||
/// Number of samples to update per call
|
||||
u32 update_count;
|
||||
/// is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory
|
||||
* address.
|
||||
*/
|
||||
struct CaptureCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer index
|
||||
s16 input;
|
||||
/// Output mix buffer index
|
||||
s16 output;
|
||||
/// Meta info for writing
|
||||
CpuAddr send_buffer_info;
|
||||
/// Game memory write buffer
|
||||
CpuAddr send_buffer;
|
||||
/// Max samples to read/write
|
||||
u32 count_max;
|
||||
/// Current read/write offset
|
||||
u32 write_offset;
|
||||
/// Number of samples to update per call
|
||||
u32 update_count;
|
||||
/// is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,155 +1,155 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cmath>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/compressor.h"
|
||||
#include "audio_core/renderer/effect/compressor.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params,
|
||||
CompressorInfo::State& state) {
|
||||
const auto ratio{1.0f / params.compressor_ratio};
|
||||
auto makeup_gain{0.0f};
|
||||
if (params.makeup_gain_enabled) {
|
||||
makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f;
|
||||
}
|
||||
state.makeup_gain = makeup_gain;
|
||||
state.unk_18 = params.unk_28;
|
||||
|
||||
const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f};
|
||||
const auto b{(a - std::trunc(a)) * 0.69315f};
|
||||
const auto c{std::pow(2.0f, b)};
|
||||
|
||||
state.unk_0C = (1.0f - ratio) / 6.0f;
|
||||
state.unk_14 = params.threshold + 1.5f;
|
||||
state.unk_10 = params.threshold - 1.5f;
|
||||
state.unk_20 = c;
|
||||
}
|
||||
|
||||
static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params,
|
||||
CompressorInfo::State& state) {
|
||||
state = {};
|
||||
|
||||
state.unk_00 = 0;
|
||||
state.unk_04 = 1.0f;
|
||||
state.unk_08 = 1.0f;
|
||||
|
||||
SetCompressorEffectParameter(params, state);
|
||||
}
|
||||
|
||||
static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
|
||||
CompressorInfo::State& state, bool enabled,
|
||||
std::vector<std::span<const s32>> input_buffers,
|
||||
std::vector<std::span<s32>> output_buffers, u32 sample_count) {
|
||||
if (enabled) {
|
||||
auto state_00{state.unk_00};
|
||||
auto state_04{state.unk_04};
|
||||
auto state_08{state.unk_08};
|
||||
auto state_18{state.unk_18};
|
||||
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
auto a{0.0f};
|
||||
for (s16 channel = 0; channel < params.channel_count; channel++) {
|
||||
const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])};
|
||||
a += (input_sample * input_sample).to_float();
|
||||
}
|
||||
|
||||
state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00);
|
||||
|
||||
auto b{-100.0f};
|
||||
auto c{0.0f};
|
||||
if (state_00 >= 1.0e-10) {
|
||||
b = std::log10(state_00) * 10.0f;
|
||||
c = 1.0f;
|
||||
}
|
||||
|
||||
if (b >= state.unk_10) {
|
||||
const auto d{b >= state.unk_14
|
||||
? ((1.0f / params.compressor_ratio) - 1.0f) *
|
||||
(b - params.threshold)
|
||||
: (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C};
|
||||
const auto e{d / 20.0f * 3.3219f};
|
||||
const auto f{(e - std::trunc(e)) * 0.69315f};
|
||||
c = std::pow(2.0f, f);
|
||||
}
|
||||
|
||||
state_18 = params.unk_28;
|
||||
auto tmp{c};
|
||||
if ((state_04 - c) <= 0.08f) {
|
||||
state_18 = params.unk_2C;
|
||||
if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) {
|
||||
tmp = state_04;
|
||||
}
|
||||
}
|
||||
|
||||
state_04 = tmp;
|
||||
state_08 += (c - state_08) * state_18;
|
||||
|
||||
for (s16 channel = 0; channel < params.channel_count; channel++) {
|
||||
output_buffers[channel][i] = static_cast<s32>(
|
||||
static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20);
|
||||
}
|
||||
}
|
||||
|
||||
state.unk_00 = state_00;
|
||||
state.unk_04 = state_04;
|
||||
state.unk_08 = state_08;
|
||||
state.unk_18 = state_18;
|
||||
} else {
|
||||
for (s16 channel = 0; channel < params.channel_count; channel++) {
|
||||
if (params.inputs[channel] != params.outputs[channel]) {
|
||||
std::memcpy(output_buffers[channel].data(), input_buffers[channel].data(),
|
||||
output_buffers[channel].size_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<CompressorInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == CompressorInfo::ParameterState::Updating) {
|
||||
SetCompressorEffectParameter(parameter, *state_);
|
||||
} else if (parameter.state == CompressorInfo::ParameterState::Initialized) {
|
||||
InitializeCompressorEffect(parameter, *state_);
|
||||
}
|
||||
}
|
||||
|
||||
ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cmath>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/compressor.h"
|
||||
#include "audio_core/renderer/effect/compressor.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params,
|
||||
CompressorInfo::State& state) {
|
||||
const auto ratio{1.0f / params.compressor_ratio};
|
||||
auto makeup_gain{0.0f};
|
||||
if (params.makeup_gain_enabled) {
|
||||
makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f;
|
||||
}
|
||||
state.makeup_gain = makeup_gain;
|
||||
state.unk_18 = params.unk_28;
|
||||
|
||||
const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f};
|
||||
const auto b{(a - std::trunc(a)) * 0.69315f};
|
||||
const auto c{std::pow(2.0f, b)};
|
||||
|
||||
state.unk_0C = (1.0f - ratio) / 6.0f;
|
||||
state.unk_14 = params.threshold + 1.5f;
|
||||
state.unk_10 = params.threshold - 1.5f;
|
||||
state.unk_20 = c;
|
||||
}
|
||||
|
||||
static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params,
|
||||
CompressorInfo::State& state) {
|
||||
state = {};
|
||||
|
||||
state.unk_00 = 0;
|
||||
state.unk_04 = 1.0f;
|
||||
state.unk_08 = 1.0f;
|
||||
|
||||
SetCompressorEffectParameter(params, state);
|
||||
}
|
||||
|
||||
static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
|
||||
CompressorInfo::State& state, bool enabled,
|
||||
std::vector<std::span<const s32>> input_buffers,
|
||||
std::vector<std::span<s32>> output_buffers, u32 sample_count) {
|
||||
if (enabled) {
|
||||
auto state_00{state.unk_00};
|
||||
auto state_04{state.unk_04};
|
||||
auto state_08{state.unk_08};
|
||||
auto state_18{state.unk_18};
|
||||
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
auto a{0.0f};
|
||||
for (s16 channel = 0; channel < params.channel_count; channel++) {
|
||||
const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])};
|
||||
a += (input_sample * input_sample).to_float();
|
||||
}
|
||||
|
||||
state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00);
|
||||
|
||||
auto b{-100.0f};
|
||||
auto c{0.0f};
|
||||
if (state_00 >= 1.0e-10) {
|
||||
b = std::log10(state_00) * 10.0f;
|
||||
c = 1.0f;
|
||||
}
|
||||
|
||||
if (b >= state.unk_10) {
|
||||
const auto d{b >= state.unk_14
|
||||
? ((1.0f / params.compressor_ratio) - 1.0f) *
|
||||
(b - params.threshold)
|
||||
: (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C};
|
||||
const auto e{d / 20.0f * 3.3219f};
|
||||
const auto f{(e - std::trunc(e)) * 0.69315f};
|
||||
c = std::pow(2.0f, f);
|
||||
}
|
||||
|
||||
state_18 = params.unk_28;
|
||||
auto tmp{c};
|
||||
if ((state_04 - c) <= 0.08f) {
|
||||
state_18 = params.unk_2C;
|
||||
if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) {
|
||||
tmp = state_04;
|
||||
}
|
||||
}
|
||||
|
||||
state_04 = tmp;
|
||||
state_08 += (c - state_08) * state_18;
|
||||
|
||||
for (s16 channel = 0; channel < params.channel_count; channel++) {
|
||||
output_buffers[channel][i] = static_cast<s32>(
|
||||
static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20);
|
||||
}
|
||||
}
|
||||
|
||||
state.unk_00 = state_00;
|
||||
state.unk_04 = state_04;
|
||||
state.unk_08 = state_08;
|
||||
state.unk_18 = state_18;
|
||||
} else {
|
||||
for (s16 channel = 0; channel < params.channel_count; channel++) {
|
||||
if (params.inputs[channel] != params.outputs[channel]) {
|
||||
std::memcpy(output_buffers[channel].data(), input_buffers[channel].data(),
|
||||
output_buffers[channel].size_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<CompressorInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == CompressorInfo::ParameterState::Updating) {
|
||||
SetCompressorEffectParameter(parameter, *state_);
|
||||
} else if (parameter.state == CompressorInfo::ParameterState::Initialized) {
|
||||
InitializeCompressorEffect(parameter, *state_);
|
||||
}
|
||||
}
|
||||
|
||||
ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/effect/compressor.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for limiting volume between a high and low threshold.
|
||||
* Version 1.
|
||||
*/
|
||||
struct CompressorCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
CompressorInfo::ParameterVersion2 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/effect/compressor.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for limiting volume between a high and low threshold.
|
||||
* Version 1.
|
||||
*/
|
||||
struct CompressorCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
CompressorInfo::ParameterVersion2 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,238 +1,238 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/delay.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Update the DelayInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
*/
|
||||
static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params,
|
||||
DelayInfo::State& state) {
|
||||
auto channel_spread{params.channel_spread};
|
||||
state.feedback_gain = params.feedback_gain * 0.97998046875f;
|
||||
state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread);
|
||||
if (params.channel_count == 4 || params.channel_count == 6) {
|
||||
channel_spread >>= 1;
|
||||
}
|
||||
state.delay_feedback_cross_gain = channel_spread * state.feedback_gain;
|
||||
state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f;
|
||||
state.lowpass_gain = 1.0f - state.lowpass_feedback_gain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new DelayInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
* @param workbuffer - Game-supplied memory for the state. (Unused)
|
||||
*/
|
||||
static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
|
||||
DelayInfo::State& state,
|
||||
[[maybe_unused]] const CpuAddr workbuffer) {
|
||||
state = {};
|
||||
|
||||
for (u32 channel = 0; channel < params.channel_count; channel++) {
|
||||
Common::FixedPoint<32, 32> sample_count_max{0.064f};
|
||||
sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max;
|
||||
|
||||
Common::FixedPoint<18, 14> delay_time{params.delay_time};
|
||||
delay_time *= params.sample_rate / 1000;
|
||||
Common::FixedPoint<32, 32> sample_count{delay_time};
|
||||
|
||||
if (sample_count > sample_count_max) {
|
||||
sample_count = sample_count_max;
|
||||
}
|
||||
|
||||
state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor();
|
||||
state.delay_lines[channel].sample_count = sample_count.to_int_floor();
|
||||
state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0);
|
||||
if (state.delay_lines[channel].buffer.size() == 0) {
|
||||
state.delay_lines[channel].buffer.push_back(0);
|
||||
}
|
||||
state.delay_lines[channel].buffer_pos = 0;
|
||||
state.delay_lines[channel].decay_rate = 1.0f;
|
||||
}
|
||||
|
||||
SetDelayEffectParameter(params, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delay effect impl, according to the parameters and current state, on the input mix buffers,
|
||||
* saving the results to the output mix buffers.
|
||||
*
|
||||
* @tparam NumChannels - Number of channels to process. 1-6.
|
||||
* @param params - Input parameters to use.
|
||||
* @param state - State to use, must be initialized (see InitializeDelayEffect).
|
||||
* @param inputs - Input mix buffers to performan the delay on.
|
||||
* @param outputs - Output mix buffers to receive the delayed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t NumChannels>
|
||||
static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{};
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
input_samples[channel] = inputs[channel][sample_index] * 64;
|
||||
}
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{};
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
delay_samples[channel] = state.delay_lines[channel].Read();
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{};
|
||||
if constexpr (NumChannels == 1) {
|
||||
matrix = {{
|
||||
{state.feedback_gain},
|
||||
}};
|
||||
} else if constexpr (NumChannels == 2) {
|
||||
matrix = {{
|
||||
{state.delay_feedback_gain, state.delay_feedback_cross_gain},
|
||||
{state.delay_feedback_cross_gain, state.delay_feedback_gain},
|
||||
}};
|
||||
} else if constexpr (NumChannels == 4) {
|
||||
matrix = {{
|
||||
{state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f},
|
||||
{state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain},
|
||||
{state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
|
||||
{0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain},
|
||||
}};
|
||||
} else if constexpr (NumChannels == 6) {
|
||||
matrix = {{
|
||||
{state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f},
|
||||
{0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain},
|
||||
{state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f},
|
||||
{0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f},
|
||||
{state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
|
||||
{0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain},
|
||||
}};
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{};
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
Common::FixedPoint<50, 14> delay{};
|
||||
for (u32 j = 0; j < NumChannels; j++) {
|
||||
delay += delay_samples[j] * matrix[j][channel];
|
||||
}
|
||||
gained_samples[channel] = input_samples[channel] * params.in_gain + delay;
|
||||
}
|
||||
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain +
|
||||
state.lowpass_z[channel] * state.lowpass_feedback_gain;
|
||||
state.delay_lines[channel].Write(state.lowpass_z[channel]);
|
||||
}
|
||||
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain +
|
||||
delay_samples[channel] * params.wet_gain)
|
||||
.to_int_floor() /
|
||||
64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a delay effect if enabled, according to the parameters and current state, on the input mix
|
||||
* buffers, saving the results to the output mix buffers.
|
||||
*
|
||||
* @param params - Input parameters to use.
|
||||
* @param state - State to use, must be initialized (see InitializeDelayEffect).
|
||||
* @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
|
||||
* @param inputs - Input mix buffers to performan the delay on.
|
||||
* @param outputs - Output mix buffers to receive the delayed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
|
||||
const bool enabled, std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
|
||||
if (!IsChannelCountValid(params.channel_count)) {
|
||||
LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
switch (params.channel_count) {
|
||||
case 1:
|
||||
ApplyDelay<1>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 2:
|
||||
ApplyDelay<2>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 4:
|
||||
ApplyDelay<4>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 6:
|
||||
ApplyDelay<6>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
default:
|
||||
for (u32 channel = 0; channel < params.channel_count; channel++) {
|
||||
if (inputs[channel].data() != outputs[channel].data()) {
|
||||
std::memcpy(outputs[channel].data(), inputs[channel].data(),
|
||||
sample_count * sizeof(s32));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
for (u32 channel = 0; channel < params.channel_count; channel++) {
|
||||
if (inputs[channel].data() != outputs[channel].data()) {
|
||||
std::memcpy(outputs[channel].data(), inputs[channel].data(),
|
||||
sample_count * sizeof(s32));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<DelayInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == DelayInfo::ParameterState::Updating) {
|
||||
SetDelayEffectParameter(parameter, *state_);
|
||||
} else if (parameter.state == DelayInfo::ParameterState::Initialized) {
|
||||
InitializeDelayEffect(parameter, *state_, workbuffer);
|
||||
}
|
||||
}
|
||||
ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/delay.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Update the DelayInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
*/
|
||||
static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params,
|
||||
DelayInfo::State& state) {
|
||||
auto channel_spread{params.channel_spread};
|
||||
state.feedback_gain = params.feedback_gain * 0.97998046875f;
|
||||
state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread);
|
||||
if (params.channel_count == 4 || params.channel_count == 6) {
|
||||
channel_spread >>= 1;
|
||||
}
|
||||
state.delay_feedback_cross_gain = channel_spread * state.feedback_gain;
|
||||
state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f;
|
||||
state.lowpass_gain = 1.0f - state.lowpass_feedback_gain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new DelayInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
* @param workbuffer - Game-supplied memory for the state. (Unused)
|
||||
*/
|
||||
static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
|
||||
DelayInfo::State& state,
|
||||
[[maybe_unused]] const CpuAddr workbuffer) {
|
||||
state = {};
|
||||
|
||||
for (u32 channel = 0; channel < params.channel_count; channel++) {
|
||||
Common::FixedPoint<32, 32> sample_count_max{0.064f};
|
||||
sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max;
|
||||
|
||||
Common::FixedPoint<18, 14> delay_time{params.delay_time};
|
||||
delay_time *= params.sample_rate / 1000;
|
||||
Common::FixedPoint<32, 32> sample_count{delay_time};
|
||||
|
||||
if (sample_count > sample_count_max) {
|
||||
sample_count = sample_count_max;
|
||||
}
|
||||
|
||||
state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor();
|
||||
state.delay_lines[channel].sample_count = sample_count.to_int_floor();
|
||||
state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0);
|
||||
if (state.delay_lines[channel].buffer.size() == 0) {
|
||||
state.delay_lines[channel].buffer.push_back(0);
|
||||
}
|
||||
state.delay_lines[channel].buffer_pos = 0;
|
||||
state.delay_lines[channel].decay_rate = 1.0f;
|
||||
}
|
||||
|
||||
SetDelayEffectParameter(params, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delay effect impl, according to the parameters and current state, on the input mix buffers,
|
||||
* saving the results to the output mix buffers.
|
||||
*
|
||||
* @tparam NumChannels - Number of channels to process. 1-6.
|
||||
* @param params - Input parameters to use.
|
||||
* @param state - State to use, must be initialized (see InitializeDelayEffect).
|
||||
* @param inputs - Input mix buffers to performan the delay on.
|
||||
* @param outputs - Output mix buffers to receive the delayed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t NumChannels>
|
||||
static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{};
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
input_samples[channel] = inputs[channel][sample_index] * 64;
|
||||
}
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{};
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
delay_samples[channel] = state.delay_lines[channel].Read();
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{};
|
||||
if constexpr (NumChannels == 1) {
|
||||
matrix = {{
|
||||
{state.feedback_gain},
|
||||
}};
|
||||
} else if constexpr (NumChannels == 2) {
|
||||
matrix = {{
|
||||
{state.delay_feedback_gain, state.delay_feedback_cross_gain},
|
||||
{state.delay_feedback_cross_gain, state.delay_feedback_gain},
|
||||
}};
|
||||
} else if constexpr (NumChannels == 4) {
|
||||
matrix = {{
|
||||
{state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f},
|
||||
{state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain},
|
||||
{state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
|
||||
{0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain},
|
||||
}};
|
||||
} else if constexpr (NumChannels == 6) {
|
||||
matrix = {{
|
||||
{state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f},
|
||||
{0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain},
|
||||
{state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f},
|
||||
{0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f},
|
||||
{state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
|
||||
{0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain},
|
||||
}};
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{};
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
Common::FixedPoint<50, 14> delay{};
|
||||
for (u32 j = 0; j < NumChannels; j++) {
|
||||
delay += delay_samples[j] * matrix[j][channel];
|
||||
}
|
||||
gained_samples[channel] = input_samples[channel] * params.in_gain + delay;
|
||||
}
|
||||
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain +
|
||||
state.lowpass_z[channel] * state.lowpass_feedback_gain;
|
||||
state.delay_lines[channel].Write(state.lowpass_z[channel]);
|
||||
}
|
||||
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain +
|
||||
delay_samples[channel] * params.wet_gain)
|
||||
.to_int_floor() /
|
||||
64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a delay effect if enabled, according to the parameters and current state, on the input mix
|
||||
* buffers, saving the results to the output mix buffers.
|
||||
*
|
||||
* @param params - Input parameters to use.
|
||||
* @param state - State to use, must be initialized (see InitializeDelayEffect).
|
||||
* @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
|
||||
* @param inputs - Input mix buffers to performan the delay on.
|
||||
* @param outputs - Output mix buffers to receive the delayed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
|
||||
const bool enabled, std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
|
||||
if (!IsChannelCountValid(params.channel_count)) {
|
||||
LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
switch (params.channel_count) {
|
||||
case 1:
|
||||
ApplyDelay<1>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 2:
|
||||
ApplyDelay<2>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 4:
|
||||
ApplyDelay<4>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 6:
|
||||
ApplyDelay<6>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
default:
|
||||
for (u32 channel = 0; channel < params.channel_count; channel++) {
|
||||
if (inputs[channel].data() != outputs[channel].data()) {
|
||||
std::memcpy(outputs[channel].data(), inputs[channel].data(),
|
||||
sample_count * sizeof(s32));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
for (u32 channel = 0; channel < params.channel_count; channel++) {
|
||||
if (inputs[channel].data() != outputs[channel].data()) {
|
||||
std::memcpy(outputs[channel].data(), inputs[channel].data(),
|
||||
sample_count * sizeof(s32));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<DelayInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == DelayInfo::ParameterState::Updating) {
|
||||
SetDelayEffectParameter(parameter, *state_);
|
||||
} else if (parameter.state == DelayInfo::ParameterState::Initialized) {
|
||||
InitializeDelayEffect(parameter, *state_, workbuffer);
|
||||
}
|
||||
}
|
||||
ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/effect/delay.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters
|
||||
* and state, outputs receives the delayed samples.
|
||||
*/
|
||||
struct DelayCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
DelayInfo::ParameterVersion1 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/effect/delay.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters
|
||||
* and state, outputs receives the delayed samples.
|
||||
*/
|
||||
struct DelayCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
DelayInfo::ParameterVersion1 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,437 +1,437 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <numbers>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{
|
||||
5.0f,
|
||||
6.0f,
|
||||
13.0f,
|
||||
14.0f,
|
||||
};
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MaxDelayLineTimes{
|
||||
45.7042007446f,
|
||||
82.7817001343f,
|
||||
149.938293457f,
|
||||
271.575805664f,
|
||||
};
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay0MaxDelayLineTimes{17.0f, 13.0f,
|
||||
9.0f, 7.0f};
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay1MaxDelayLineTimes{19.0f, 11.0f,
|
||||
10.0f, 6.0f};
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyTapTimes{
|
||||
0.0171360000968f,
|
||||
0.0591540001333f,
|
||||
0.161733001471f,
|
||||
0.390186011791f,
|
||||
0.425262004137f,
|
||||
0.455410987139f,
|
||||
0.689737021923f,
|
||||
0.74590998888f,
|
||||
0.833844006062f,
|
||||
0.859502017498f,
|
||||
0.0f,
|
||||
0.0750240013003f,
|
||||
0.168788000941f,
|
||||
0.299901008606f,
|
||||
0.337442994118f,
|
||||
0.371903002262f,
|
||||
0.599011003971f,
|
||||
0.716741025448f,
|
||||
0.817858994007f,
|
||||
0.85166400671f,
|
||||
};
|
||||
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyGains{
|
||||
0.67096f, 0.61027f, 1.0f, 0.3568f, 0.68361f, 0.65978f, 0.51939f,
|
||||
0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.3827f,
|
||||
0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
|
||||
|
||||
/**
|
||||
* Update the I3dl2ReverbInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
* @param reset - If enabled, the state buffers will be reset. Only set this on initialize.
|
||||
*/
|
||||
static void UpdateI3dl2ReverbEffectParameter(const I3dl2ReverbInfo::ParameterVersion1& params,
|
||||
I3dl2ReverbInfo::State& state, const bool reset) {
|
||||
const auto pow_10 = [](f32 val) -> f32 {
|
||||
return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
|
||||
};
|
||||
const auto sin = [](f32 degrees) -> f32 {
|
||||
return std::sin(degrees * std::numbers::pi_v<f32> / 180.0f);
|
||||
};
|
||||
const auto cos = [](f32 degrees) -> f32 {
|
||||
return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
|
||||
};
|
||||
|
||||
Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000.0f};
|
||||
|
||||
state.dry_gain = params.dry_gain;
|
||||
Common::FixedPoint<50, 14> early_gain{
|
||||
std::min(params.room_gain + params.reflection_gain, 5000.0f) / 2000.0f};
|
||||
state.early_gain = pow_10(early_gain.to_float());
|
||||
Common::FixedPoint<50, 14> late_gain{std::min(params.room_gain + params.reverb_gain, 5000.0f) /
|
||||
2000.0f};
|
||||
state.late_gain = pow_10(late_gain.to_float());
|
||||
|
||||
Common::FixedPoint<50, 14> hf_gain{pow_10(params.room_HF_gain / 2000.0f)};
|
||||
if (hf_gain >= 1.0f) {
|
||||
state.lowpass_1 = 0.0f;
|
||||
state.lowpass_2 = 1.0f;
|
||||
} else {
|
||||
const auto reference_hf{(params.reference_HF * 256.0f) /
|
||||
static_cast<f32>(params.sample_rate)};
|
||||
const Common::FixedPoint<50, 14> a{1.0f - hf_gain.to_float()};
|
||||
const Common::FixedPoint<50, 14> b{2.0f + (-cos(reference_hf) * (hf_gain * 2.0f))};
|
||||
const Common::FixedPoint<50, 14> c{
|
||||
std::sqrt(std::pow(b.to_float(), 2.0f) + (std::pow(a.to_float(), 2.0f) * -4.0f))};
|
||||
|
||||
state.lowpass_1 = std::min(((b - c) / (a * 2.0f)).to_float(), 0.99723f);
|
||||
state.lowpass_2 = 1.0f - state.lowpass_1;
|
||||
}
|
||||
|
||||
state.early_to_late_taps =
|
||||
(((params.reflection_delay + params.late_reverb_delay_time) * 1000.0f) * delay).to_int();
|
||||
state.last_reverb_echo = params.late_reverb_diffusion * 0.6f * 0.01f;
|
||||
|
||||
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
|
||||
auto curr_delay{
|
||||
((MinDelayLineTimes[i] + (params.late_reverb_density / 100.0f) *
|
||||
(MaxDelayLineTimes[i] - MinDelayLineTimes[i])) *
|
||||
delay)
|
||||
.to_int()};
|
||||
state.fdn_delay_lines[i].SetDelay(curr_delay);
|
||||
|
||||
const auto a{
|
||||
(static_cast<f32>(state.fdn_delay_lines[i].delay + state.decay_delay_lines0[i].delay +
|
||||
state.decay_delay_lines1[i].delay) *
|
||||
-60.0f) /
|
||||
(params.late_reverb_decay_time * static_cast<f32>(params.sample_rate))};
|
||||
const auto b{a / params.late_reverb_HF_decay_ratio};
|
||||
const auto c{
|
||||
cos(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate)) /
|
||||
sin(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate))};
|
||||
const auto d{pow_10((b - a) / 40.0f)};
|
||||
const auto e{pow_10((b + a) / 40.0f) * 0.7071f};
|
||||
|
||||
state.lowpass_coeff[i][0] = ((c * d + 1.0f) * e) / (c + d);
|
||||
state.lowpass_coeff[i][1] = ((1.0f - (c * d)) * e) / (c + d);
|
||||
state.lowpass_coeff[i][2] = (c - d) / (c + d);
|
||||
|
||||
state.decay_delay_lines0[i].wet_gain = state.last_reverb_echo;
|
||||
state.decay_delay_lines1[i].wet_gain = state.last_reverb_echo * -0.9f;
|
||||
}
|
||||
|
||||
if (reset) {
|
||||
state.shelf_filter.fill(0.0f);
|
||||
state.lowpass_0 = 0.0f;
|
||||
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
|
||||
std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
|
||||
std::ranges::fill(state.decay_delay_lines0[i].buffer, 0);
|
||||
std::ranges::fill(state.decay_delay_lines1[i].buffer, 0);
|
||||
}
|
||||
std::ranges::fill(state.center_delay_line.buffer, 0);
|
||||
std::ranges::fill(state.early_delay_line.buffer, 0);
|
||||
}
|
||||
|
||||
const auto reflection_time{(params.late_reverb_delay_time * 0.9998f + 0.02f) * 1000.0f};
|
||||
const auto reflection_delay{params.reflection_delay * 1000.0f};
|
||||
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayTaps; i++) {
|
||||
auto length{((reflection_delay + reflection_time * EarlyTapTimes[i]) * delay).to_int()};
|
||||
if (length >= state.early_delay_line.max_delay) {
|
||||
length = state.early_delay_line.max_delay;
|
||||
}
|
||||
state.early_tap_steps[i] = length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new I3dl2ReverbInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
* @param workbuffer - Game-supplied memory for the state. (Unused)
|
||||
*/
|
||||
static void InitializeI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
|
||||
I3dl2ReverbInfo::State& state, const CpuAddr workbuffer) {
|
||||
state = {};
|
||||
Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000};
|
||||
|
||||
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
|
||||
auto fdn_delay_time{(MaxDelayLineTimes[i] * delay).to_uint_floor()};
|
||||
state.fdn_delay_lines[i].Initialize(fdn_delay_time);
|
||||
|
||||
auto decay0_delay_time{(Decay0MaxDelayLineTimes[i] * delay).to_uint_floor()};
|
||||
state.decay_delay_lines0[i].Initialize(decay0_delay_time);
|
||||
|
||||
auto decay1_delay_time{(Decay1MaxDelayLineTimes[i] * delay).to_uint_floor()};
|
||||
state.decay_delay_lines1[i].Initialize(decay1_delay_time);
|
||||
}
|
||||
|
||||
const auto center_delay_time{(5 * delay).to_uint_floor()};
|
||||
state.center_delay_line.Initialize(center_delay_time);
|
||||
|
||||
const auto early_delay_time{(400 * delay).to_uint_floor()};
|
||||
state.early_delay_line.Initialize(early_delay_time);
|
||||
|
||||
UpdateI3dl2ReverbEffectParameter(params, state, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass-through the effect, copying input to output directly, with no reverb applied.
|
||||
*
|
||||
* @param inputs - Array of input mix buffers to copy.
|
||||
* @param outputs - Array of output mix buffers to receive copy.
|
||||
* @param channel_count - Number of channels in inputs and outputs.
|
||||
* @param sample_count - Number of samples within each channel (unused).
|
||||
*/
|
||||
static void ApplyI3dl2ReverbEffectBypass(std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 channel_count,
|
||||
[[maybe_unused]] const u32 sample_count) {
|
||||
for (u32 i = 0; i < channel_count; i++) {
|
||||
if (inputs[i].data() != outputs[i].data()) {
|
||||
std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick the delay lines, reading and returning their current output, and writing a new decaying
|
||||
* sample (mix).
|
||||
*
|
||||
* @param decay0 - The first decay line.
|
||||
* @param decay1 - The second decay line.
|
||||
* @param fdn - Feedback delay network.
|
||||
* @param mix - The new calculated sample to be written and decayed.
|
||||
* @return The next delayed and decayed sample.
|
||||
*/
|
||||
static Common::FixedPoint<50, 14> Axfx2AllPassTick(I3dl2ReverbInfo::I3dl2DelayLine& decay0,
|
||||
I3dl2ReverbInfo::I3dl2DelayLine& decay1,
|
||||
I3dl2ReverbInfo::I3dl2DelayLine& fdn,
|
||||
const Common::FixedPoint<50, 14> mix) {
|
||||
auto val{decay0.Read()};
|
||||
auto mixed{mix - (val * decay0.wet_gain)};
|
||||
auto out{decay0.Tick(mixed) + (mixed * decay0.wet_gain)};
|
||||
|
||||
val = decay1.Read();
|
||||
mixed = out - (val * decay1.wet_gain);
|
||||
out = decay1.Tick(mixed) + (mixed * decay1.wet_gain);
|
||||
|
||||
fdn.Tick(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Impl. Apply a I3DL2 reverb according to the current state, on the input mix buffers,
|
||||
* saving the results to the output mix buffers.
|
||||
*
|
||||
* @tparam NumChannels - Number of channels to process. 1-6.
|
||||
Inputs/outputs should have this many buffers.
|
||||
* @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
|
||||
* @param inputs - Input mix buffers to perform the reverb on.
|
||||
* @param outputs - Output mix buffers to receive the reverbed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t NumChannels>
|
||||
static void ApplyI3dl2ReverbEffect(I3dl2ReverbInfo::State& state,
|
||||
std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count) {
|
||||
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
|
||||
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
|
||||
};
|
||||
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
|
||||
0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3,
|
||||
};
|
||||
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
|
||||
2, 0, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5,
|
||||
};
|
||||
|
||||
std::span<const u8> tap_indexes{};
|
||||
if constexpr (NumChannels == 1) {
|
||||
tap_indexes = OutTapIndexes1Ch;
|
||||
} else if constexpr (NumChannels == 2) {
|
||||
tap_indexes = OutTapIndexes2Ch;
|
||||
} else if constexpr (NumChannels == 4) {
|
||||
tap_indexes = OutTapIndexes4Ch;
|
||||
} else if constexpr (NumChannels == 6) {
|
||||
tap_indexes = OutTapIndexes6Ch;
|
||||
}
|
||||
|
||||
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
|
||||
Common::FixedPoint<50, 14> early_to_late_tap{
|
||||
state.early_delay_line.TapOut(state.early_to_late_taps)};
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
|
||||
|
||||
for (u32 early_tap = 0; early_tap < I3dl2ReverbInfo::MaxDelayTaps; early_tap++) {
|
||||
output_samples[tap_indexes[early_tap]] +=
|
||||
state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
|
||||
EarlyGains[early_tap];
|
||||
if constexpr (NumChannels == 6) {
|
||||
output_samples[static_cast<u32>(Channels::LFE)] +=
|
||||
state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
|
||||
EarlyGains[early_tap];
|
||||
}
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> current_sample{};
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
current_sample += inputs[channel][sample_index];
|
||||
}
|
||||
|
||||
state.lowpass_0 =
|
||||
(current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1).to_float();
|
||||
state.early_delay_line.Tick(state.lowpass_0);
|
||||
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
output_samples[channel] *= state.early_gain;
|
||||
}
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> filtered_samples{};
|
||||
for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
|
||||
filtered_samples[delay_line] =
|
||||
state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][0] +
|
||||
state.shelf_filter[delay_line];
|
||||
state.shelf_filter[delay_line] =
|
||||
(filtered_samples[delay_line] * state.lowpass_coeff[delay_line][2] +
|
||||
state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][1])
|
||||
.to_float();
|
||||
}
|
||||
|
||||
const std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> mix_matrix{
|
||||
filtered_samples[1] + filtered_samples[2] + early_to_late_tap * state.late_gain,
|
||||
-filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
|
||||
filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
|
||||
filtered_samples[1] - filtered_samples[2] + early_to_late_tap * state.late_gain,
|
||||
};
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> allpass_samples{};
|
||||
for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
|
||||
allpass_samples[delay_line] = Axfx2AllPassTick(
|
||||
state.decay_delay_lines0[delay_line], state.decay_delay_lines1[delay_line],
|
||||
state.fdn_delay_lines[delay_line], mix_matrix[delay_line]);
|
||||
}
|
||||
|
||||
if constexpr (NumChannels == 6) {
|
||||
const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
|
||||
allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
|
||||
allpass_samples[3], allpass_samples[2], allpass_samples[3],
|
||||
};
|
||||
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
Common::FixedPoint<50, 14> allpass{};
|
||||
|
||||
if (channel == static_cast<u32>(Channels::Center)) {
|
||||
allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
|
||||
} else {
|
||||
allpass = allpass_outputs[channel];
|
||||
}
|
||||
|
||||
auto out_sample{output_samples[channel] + allpass +
|
||||
state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
|
||||
|
||||
outputs[channel][sample_index] =
|
||||
static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
|
||||
}
|
||||
} else {
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
auto out_sample{output_samples[channel] + allpass_samples[channel] +
|
||||
state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
|
||||
outputs[channel][sample_index] =
|
||||
static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a I3DL2 reverb if enabled, according to the current state, on the input mix buffers,
|
||||
* saving the results to the output mix buffers.
|
||||
*
|
||||
* @param params - Input parameters to use.
|
||||
* @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
|
||||
* @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
|
||||
* @param inputs - Input mix buffers to performan the delay on.
|
||||
* @param outputs - Output mix buffers to receive the delayed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
|
||||
I3dl2ReverbInfo::State& state, const bool enabled,
|
||||
std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count) {
|
||||
if (enabled) {
|
||||
switch (params.channel_count) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
ApplyI3dl2ReverbEffect<1>(state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 2:
|
||||
ApplyI3dl2ReverbEffect<2>(state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 4:
|
||||
ApplyI3dl2ReverbEffect<4>(state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 6:
|
||||
ApplyI3dl2ReverbEffect<6>(state, inputs, outputs, sample_count);
|
||||
break;
|
||||
default:
|
||||
ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<I3dl2ReverbInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == I3dl2ReverbInfo::ParameterState::Updating) {
|
||||
UpdateI3dl2ReverbEffectParameter(parameter, *state_, false);
|
||||
} else if (parameter.state == I3dl2ReverbInfo::ParameterState::Initialized) {
|
||||
InitializeI3dl2ReverbEffect(parameter, *state_, workbuffer);
|
||||
}
|
||||
}
|
||||
ApplyI3dl2ReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <numbers>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{
|
||||
5.0f,
|
||||
6.0f,
|
||||
13.0f,
|
||||
14.0f,
|
||||
};
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MaxDelayLineTimes{
|
||||
45.7042007446f,
|
||||
82.7817001343f,
|
||||
149.938293457f,
|
||||
271.575805664f,
|
||||
};
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay0MaxDelayLineTimes{17.0f, 13.0f,
|
||||
9.0f, 7.0f};
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay1MaxDelayLineTimes{19.0f, 11.0f,
|
||||
10.0f, 6.0f};
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyTapTimes{
|
||||
0.0171360000968f,
|
||||
0.0591540001333f,
|
||||
0.161733001471f,
|
||||
0.390186011791f,
|
||||
0.425262004137f,
|
||||
0.455410987139f,
|
||||
0.689737021923f,
|
||||
0.74590998888f,
|
||||
0.833844006062f,
|
||||
0.859502017498f,
|
||||
0.0f,
|
||||
0.0750240013003f,
|
||||
0.168788000941f,
|
||||
0.299901008606f,
|
||||
0.337442994118f,
|
||||
0.371903002262f,
|
||||
0.599011003971f,
|
||||
0.716741025448f,
|
||||
0.817858994007f,
|
||||
0.85166400671f,
|
||||
};
|
||||
|
||||
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyGains{
|
||||
0.67096f, 0.61027f, 1.0f, 0.3568f, 0.68361f, 0.65978f, 0.51939f,
|
||||
0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.3827f,
|
||||
0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
|
||||
|
||||
/**
|
||||
* Update the I3dl2ReverbInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
* @param reset - If enabled, the state buffers will be reset. Only set this on initialize.
|
||||
*/
|
||||
static void UpdateI3dl2ReverbEffectParameter(const I3dl2ReverbInfo::ParameterVersion1& params,
|
||||
I3dl2ReverbInfo::State& state, const bool reset) {
|
||||
const auto pow_10 = [](f32 val) -> f32 {
|
||||
return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
|
||||
};
|
||||
const auto sin = [](f32 degrees) -> f32 {
|
||||
return std::sin(degrees * std::numbers::pi_v<f32> / 180.0f);
|
||||
};
|
||||
const auto cos = [](f32 degrees) -> f32 {
|
||||
return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
|
||||
};
|
||||
|
||||
Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000.0f};
|
||||
|
||||
state.dry_gain = params.dry_gain;
|
||||
Common::FixedPoint<50, 14> early_gain{
|
||||
std::min(params.room_gain + params.reflection_gain, 5000.0f) / 2000.0f};
|
||||
state.early_gain = pow_10(early_gain.to_float());
|
||||
Common::FixedPoint<50, 14> late_gain{std::min(params.room_gain + params.reverb_gain, 5000.0f) /
|
||||
2000.0f};
|
||||
state.late_gain = pow_10(late_gain.to_float());
|
||||
|
||||
Common::FixedPoint<50, 14> hf_gain{pow_10(params.room_HF_gain / 2000.0f)};
|
||||
if (hf_gain >= 1.0f) {
|
||||
state.lowpass_1 = 0.0f;
|
||||
state.lowpass_2 = 1.0f;
|
||||
} else {
|
||||
const auto reference_hf{(params.reference_HF * 256.0f) /
|
||||
static_cast<f32>(params.sample_rate)};
|
||||
const Common::FixedPoint<50, 14> a{1.0f - hf_gain.to_float()};
|
||||
const Common::FixedPoint<50, 14> b{2.0f + (-cos(reference_hf) * (hf_gain * 2.0f))};
|
||||
const Common::FixedPoint<50, 14> c{
|
||||
std::sqrt(std::pow(b.to_float(), 2.0f) + (std::pow(a.to_float(), 2.0f) * -4.0f))};
|
||||
|
||||
state.lowpass_1 = std::min(((b - c) / (a * 2.0f)).to_float(), 0.99723f);
|
||||
state.lowpass_2 = 1.0f - state.lowpass_1;
|
||||
}
|
||||
|
||||
state.early_to_late_taps =
|
||||
(((params.reflection_delay + params.late_reverb_delay_time) * 1000.0f) * delay).to_int();
|
||||
state.last_reverb_echo = params.late_reverb_diffusion * 0.6f * 0.01f;
|
||||
|
||||
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
|
||||
auto curr_delay{
|
||||
((MinDelayLineTimes[i] + (params.late_reverb_density / 100.0f) *
|
||||
(MaxDelayLineTimes[i] - MinDelayLineTimes[i])) *
|
||||
delay)
|
||||
.to_int()};
|
||||
state.fdn_delay_lines[i].SetDelay(curr_delay);
|
||||
|
||||
const auto a{
|
||||
(static_cast<f32>(state.fdn_delay_lines[i].delay + state.decay_delay_lines0[i].delay +
|
||||
state.decay_delay_lines1[i].delay) *
|
||||
-60.0f) /
|
||||
(params.late_reverb_decay_time * static_cast<f32>(params.sample_rate))};
|
||||
const auto b{a / params.late_reverb_HF_decay_ratio};
|
||||
const auto c{
|
||||
cos(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate)) /
|
||||
sin(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate))};
|
||||
const auto d{pow_10((b - a) / 40.0f)};
|
||||
const auto e{pow_10((b + a) / 40.0f) * 0.7071f};
|
||||
|
||||
state.lowpass_coeff[i][0] = ((c * d + 1.0f) * e) / (c + d);
|
||||
state.lowpass_coeff[i][1] = ((1.0f - (c * d)) * e) / (c + d);
|
||||
state.lowpass_coeff[i][2] = (c - d) / (c + d);
|
||||
|
||||
state.decay_delay_lines0[i].wet_gain = state.last_reverb_echo;
|
||||
state.decay_delay_lines1[i].wet_gain = state.last_reverb_echo * -0.9f;
|
||||
}
|
||||
|
||||
if (reset) {
|
||||
state.shelf_filter.fill(0.0f);
|
||||
state.lowpass_0 = 0.0f;
|
||||
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
|
||||
std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
|
||||
std::ranges::fill(state.decay_delay_lines0[i].buffer, 0);
|
||||
std::ranges::fill(state.decay_delay_lines1[i].buffer, 0);
|
||||
}
|
||||
std::ranges::fill(state.center_delay_line.buffer, 0);
|
||||
std::ranges::fill(state.early_delay_line.buffer, 0);
|
||||
}
|
||||
|
||||
const auto reflection_time{(params.late_reverb_delay_time * 0.9998f + 0.02f) * 1000.0f};
|
||||
const auto reflection_delay{params.reflection_delay * 1000.0f};
|
||||
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayTaps; i++) {
|
||||
auto length{((reflection_delay + reflection_time * EarlyTapTimes[i]) * delay).to_int()};
|
||||
if (length >= state.early_delay_line.max_delay) {
|
||||
length = state.early_delay_line.max_delay;
|
||||
}
|
||||
state.early_tap_steps[i] = length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new I3dl2ReverbInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
* @param workbuffer - Game-supplied memory for the state. (Unused)
|
||||
*/
|
||||
static void InitializeI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
|
||||
I3dl2ReverbInfo::State& state, const CpuAddr workbuffer) {
|
||||
state = {};
|
||||
Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000};
|
||||
|
||||
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
|
||||
auto fdn_delay_time{(MaxDelayLineTimes[i] * delay).to_uint_floor()};
|
||||
state.fdn_delay_lines[i].Initialize(fdn_delay_time);
|
||||
|
||||
auto decay0_delay_time{(Decay0MaxDelayLineTimes[i] * delay).to_uint_floor()};
|
||||
state.decay_delay_lines0[i].Initialize(decay0_delay_time);
|
||||
|
||||
auto decay1_delay_time{(Decay1MaxDelayLineTimes[i] * delay).to_uint_floor()};
|
||||
state.decay_delay_lines1[i].Initialize(decay1_delay_time);
|
||||
}
|
||||
|
||||
const auto center_delay_time{(5 * delay).to_uint_floor()};
|
||||
state.center_delay_line.Initialize(center_delay_time);
|
||||
|
||||
const auto early_delay_time{(400 * delay).to_uint_floor()};
|
||||
state.early_delay_line.Initialize(early_delay_time);
|
||||
|
||||
UpdateI3dl2ReverbEffectParameter(params, state, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass-through the effect, copying input to output directly, with no reverb applied.
|
||||
*
|
||||
* @param inputs - Array of input mix buffers to copy.
|
||||
* @param outputs - Array of output mix buffers to receive copy.
|
||||
* @param channel_count - Number of channels in inputs and outputs.
|
||||
* @param sample_count - Number of samples within each channel (unused).
|
||||
*/
|
||||
static void ApplyI3dl2ReverbEffectBypass(std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 channel_count,
|
||||
[[maybe_unused]] const u32 sample_count) {
|
||||
for (u32 i = 0; i < channel_count; i++) {
|
||||
if (inputs[i].data() != outputs[i].data()) {
|
||||
std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick the delay lines, reading and returning their current output, and writing a new decaying
|
||||
* sample (mix).
|
||||
*
|
||||
* @param decay0 - The first decay line.
|
||||
* @param decay1 - The second decay line.
|
||||
* @param fdn - Feedback delay network.
|
||||
* @param mix - The new calculated sample to be written and decayed.
|
||||
* @return The next delayed and decayed sample.
|
||||
*/
|
||||
static Common::FixedPoint<50, 14> Axfx2AllPassTick(I3dl2ReverbInfo::I3dl2DelayLine& decay0,
|
||||
I3dl2ReverbInfo::I3dl2DelayLine& decay1,
|
||||
I3dl2ReverbInfo::I3dl2DelayLine& fdn,
|
||||
const Common::FixedPoint<50, 14> mix) {
|
||||
auto val{decay0.Read()};
|
||||
auto mixed{mix - (val * decay0.wet_gain)};
|
||||
auto out{decay0.Tick(mixed) + (mixed * decay0.wet_gain)};
|
||||
|
||||
val = decay1.Read();
|
||||
mixed = out - (val * decay1.wet_gain);
|
||||
out = decay1.Tick(mixed) + (mixed * decay1.wet_gain);
|
||||
|
||||
fdn.Tick(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Impl. Apply a I3DL2 reverb according to the current state, on the input mix buffers,
|
||||
* saving the results to the output mix buffers.
|
||||
*
|
||||
* @tparam NumChannels - Number of channels to process. 1-6.
|
||||
Inputs/outputs should have this many buffers.
|
||||
* @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
|
||||
* @param inputs - Input mix buffers to perform the reverb on.
|
||||
* @param outputs - Output mix buffers to receive the reverbed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t NumChannels>
|
||||
static void ApplyI3dl2ReverbEffect(I3dl2ReverbInfo::State& state,
|
||||
std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count) {
|
||||
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
|
||||
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
|
||||
};
|
||||
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
|
||||
0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3,
|
||||
};
|
||||
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
|
||||
2, 0, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5,
|
||||
};
|
||||
|
||||
std::span<const u8> tap_indexes{};
|
||||
if constexpr (NumChannels == 1) {
|
||||
tap_indexes = OutTapIndexes1Ch;
|
||||
} else if constexpr (NumChannels == 2) {
|
||||
tap_indexes = OutTapIndexes2Ch;
|
||||
} else if constexpr (NumChannels == 4) {
|
||||
tap_indexes = OutTapIndexes4Ch;
|
||||
} else if constexpr (NumChannels == 6) {
|
||||
tap_indexes = OutTapIndexes6Ch;
|
||||
}
|
||||
|
||||
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
|
||||
Common::FixedPoint<50, 14> early_to_late_tap{
|
||||
state.early_delay_line.TapOut(state.early_to_late_taps)};
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
|
||||
|
||||
for (u32 early_tap = 0; early_tap < I3dl2ReverbInfo::MaxDelayTaps; early_tap++) {
|
||||
output_samples[tap_indexes[early_tap]] +=
|
||||
state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
|
||||
EarlyGains[early_tap];
|
||||
if constexpr (NumChannels == 6) {
|
||||
output_samples[static_cast<u32>(Channels::LFE)] +=
|
||||
state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
|
||||
EarlyGains[early_tap];
|
||||
}
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> current_sample{};
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
current_sample += inputs[channel][sample_index];
|
||||
}
|
||||
|
||||
state.lowpass_0 =
|
||||
(current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1).to_float();
|
||||
state.early_delay_line.Tick(state.lowpass_0);
|
||||
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
output_samples[channel] *= state.early_gain;
|
||||
}
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> filtered_samples{};
|
||||
for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
|
||||
filtered_samples[delay_line] =
|
||||
state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][0] +
|
||||
state.shelf_filter[delay_line];
|
||||
state.shelf_filter[delay_line] =
|
||||
(filtered_samples[delay_line] * state.lowpass_coeff[delay_line][2] +
|
||||
state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][1])
|
||||
.to_float();
|
||||
}
|
||||
|
||||
const std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> mix_matrix{
|
||||
filtered_samples[1] + filtered_samples[2] + early_to_late_tap * state.late_gain,
|
||||
-filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
|
||||
filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
|
||||
filtered_samples[1] - filtered_samples[2] + early_to_late_tap * state.late_gain,
|
||||
};
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> allpass_samples{};
|
||||
for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
|
||||
allpass_samples[delay_line] = Axfx2AllPassTick(
|
||||
state.decay_delay_lines0[delay_line], state.decay_delay_lines1[delay_line],
|
||||
state.fdn_delay_lines[delay_line], mix_matrix[delay_line]);
|
||||
}
|
||||
|
||||
if constexpr (NumChannels == 6) {
|
||||
const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
|
||||
allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
|
||||
allpass_samples[3], allpass_samples[2], allpass_samples[3],
|
||||
};
|
||||
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
Common::FixedPoint<50, 14> allpass{};
|
||||
|
||||
if (channel == static_cast<u32>(Channels::Center)) {
|
||||
allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
|
||||
} else {
|
||||
allpass = allpass_outputs[channel];
|
||||
}
|
||||
|
||||
auto out_sample{output_samples[channel] + allpass +
|
||||
state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
|
||||
|
||||
outputs[channel][sample_index] =
|
||||
static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
|
||||
}
|
||||
} else {
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
auto out_sample{output_samples[channel] + allpass_samples[channel] +
|
||||
state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
|
||||
outputs[channel][sample_index] =
|
||||
static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a I3DL2 reverb if enabled, according to the current state, on the input mix buffers,
|
||||
* saving the results to the output mix buffers.
|
||||
*
|
||||
* @param params - Input parameters to use.
|
||||
* @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
|
||||
* @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
|
||||
* @param inputs - Input mix buffers to performan the delay on.
|
||||
* @param outputs - Output mix buffers to receive the delayed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
|
||||
I3dl2ReverbInfo::State& state, const bool enabled,
|
||||
std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count) {
|
||||
if (enabled) {
|
||||
switch (params.channel_count) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
ApplyI3dl2ReverbEffect<1>(state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 2:
|
||||
ApplyI3dl2ReverbEffect<2>(state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 4:
|
||||
ApplyI3dl2ReverbEffect<4>(state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 6:
|
||||
ApplyI3dl2ReverbEffect<6>(state, inputs, outputs, sample_count);
|
||||
break;
|
||||
default:
|
||||
ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<I3dl2ReverbInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == I3dl2ReverbInfo::ParameterState::Updating) {
|
||||
UpdateI3dl2ReverbEffectParameter(parameter, *state_, false);
|
||||
} else if (parameter.state == I3dl2ReverbInfo::ParameterState::Initialized) {
|
||||
InitializeI3dl2ReverbEffect(parameter, *state_, workbuffer);
|
||||
}
|
||||
}
|
||||
ApplyI3dl2ReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/effect/i3dl2.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to
|
||||
* the I3DL2 spec, outputs receives the results.
|
||||
*/
|
||||
struct I3dl2ReverbCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
I3dl2ReverbInfo::ParameterVersion1 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/effect/i3dl2.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to
|
||||
* the I3DL2 spec, outputs receives the results.
|
||||
*/
|
||||
struct I3dl2ReverbCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
I3dl2ReverbInfo::ParameterVersion1 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,222 +1,222 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/light_limiter.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Update the LightLimiterInfo state according to the given parameters.
|
||||
* A no-op.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
*/
|
||||
static void UpdateLightLimiterEffectParameter(const LightLimiterInfo::ParameterVersion2& params,
|
||||
LightLimiterInfo::State& state) {}
|
||||
|
||||
/**
|
||||
* Initialize a new LightLimiterInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
* @param workbuffer - Game-supplied memory for the state. (Unused)
|
||||
*/
|
||||
static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
|
||||
LightLimiterInfo::State& state, const CpuAddr workbuffer) {
|
||||
state = {};
|
||||
state.samples_average.fill(0.0f);
|
||||
state.compression_gain.fill(1.0f);
|
||||
state.look_ahead_sample_offsets.fill(0);
|
||||
for (u32 i = 0; i < params.channel_count; i++) {
|
||||
state.look_ahead_sample_buffers[i].resize(params.look_ahead_samples_max, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a light limiter effect if enabled, according to the current state, on the input mix
|
||||
* buffers, saving the results to the output mix buffers.
|
||||
*
|
||||
* @param params - Input parameters to use.
|
||||
* @param state - State to use, must be initialized (see InitializeLightLimiterEffect).
|
||||
* @param enabled - If enabled, limiter will be applied, otherwise input is copied to output.
|
||||
* @param inputs - Input mix buffers to perform the limiter on.
|
||||
* @param outputs - Output mix buffers to receive the limited samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
* @params statistics - Optional output statistics, only used with version 2.
|
||||
*/
|
||||
static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
|
||||
LightLimiterInfo::State& state, const bool enabled,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count,
|
||||
LightLimiterInfo::StatisticsInternal* statistics) {
|
||||
constexpr s64 min{std::numeric_limits<s32>::min()};
|
||||
constexpr s64 max{std::numeric_limits<s32>::max()};
|
||||
|
||||
const auto recip_estimate = [](f64 a) -> f64 {
|
||||
s32 q, s;
|
||||
f64 r;
|
||||
q = (s32)(a * 512.0); /* a in units of 1/512 rounded down */
|
||||
r = 1.0 / (((f64)q + 0.5) / 512.0); /* reciprocal r */
|
||||
s = (s32)(256.0 * r + 0.5); /* r in units of 1/256 rounded to nearest */
|
||||
return ((f64)s / 256.0);
|
||||
};
|
||||
|
||||
if (enabled) {
|
||||
if (statistics && params.statistics_reset_required) {
|
||||
for (u32 i = 0; i < params.channel_count; i++) {
|
||||
statistics->channel_compression_gain_min[i] = 1.0f;
|
||||
statistics->channel_max_sample[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
|
||||
for (u32 channel = 0; channel < params.channel_count; channel++) {
|
||||
auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) /
|
||||
Common::FixedPoint<49, 15>::one) *
|
||||
params.input_gain};
|
||||
auto abs_sample{sample};
|
||||
if (sample < 0.0f) {
|
||||
abs_sample = -sample;
|
||||
}
|
||||
auto coeff{abs_sample > state.samples_average[channel] ? params.attack_coeff
|
||||
: params.release_coeff};
|
||||
state.samples_average[channel] +=
|
||||
((abs_sample - state.samples_average[channel]) * coeff).to_float();
|
||||
|
||||
// Reciprocal estimate
|
||||
auto new_average_sample{Common::FixedPoint<49, 15>(
|
||||
recip_estimate(state.samples_average[channel].to_double()))};
|
||||
if (params.processing_mode != LightLimiterInfo::ProcessingMode::Mode1) {
|
||||
// Two Newton-Raphson steps
|
||||
auto temp{2.0 - (state.samples_average[channel] * new_average_sample)};
|
||||
new_average_sample = 2.0 - (state.samples_average[channel] * temp);
|
||||
}
|
||||
|
||||
auto above_threshold{state.samples_average[channel] > params.threshold};
|
||||
auto attenuation{above_threshold ? params.threshold * new_average_sample : 1.0f};
|
||||
coeff = attenuation < state.compression_gain[channel] ? params.attack_coeff
|
||||
: params.release_coeff;
|
||||
state.compression_gain[channel] +=
|
||||
(attenuation - state.compression_gain[channel]) * coeff;
|
||||
|
||||
auto lookahead_sample{
|
||||
state.look_ahead_sample_buffers[channel]
|
||||
[state.look_ahead_sample_offsets[channel]]};
|
||||
|
||||
state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] =
|
||||
sample;
|
||||
state.look_ahead_sample_offsets[channel] =
|
||||
(state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min;
|
||||
|
||||
outputs[channel][sample_index] = static_cast<s32>(
|
||||
std::clamp((lookahead_sample * state.compression_gain[channel] *
|
||||
params.output_gain * Common::FixedPoint<49, 15>::one)
|
||||
.to_long(),
|
||||
min, max));
|
||||
|
||||
if (statistics) {
|
||||
statistics->channel_max_sample[channel] =
|
||||
std::max(statistics->channel_max_sample[channel], abs_sample.to_float());
|
||||
statistics->channel_compression_gain_min[channel] =
|
||||
std::min(statistics->channel_compression_gain_min[channel],
|
||||
state.compression_gain[channel].to_float());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (u32 i = 0; i < params.channel_count; i++) {
|
||||
if (params.inputs[i] != params.outputs[i]) {
|
||||
std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("LightLimiterVersion1Command\n\tinputs: ");
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
|
||||
UpdateLightLimiterEffectParameter(parameter, *state_);
|
||||
} else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
|
||||
InitializeLightLimiterEffect(parameter, *state_, workbuffer);
|
||||
}
|
||||
}
|
||||
|
||||
LightLimiterInfo::StatisticsInternal* statistics{nullptr};
|
||||
ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count, statistics);
|
||||
}
|
||||
|
||||
bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n");
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
|
||||
UpdateLightLimiterEffectParameter(parameter, *state_);
|
||||
} else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
|
||||
InitializeLightLimiterEffect(parameter, *state_, workbuffer);
|
||||
}
|
||||
}
|
||||
|
||||
auto statistics{reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(result_state)};
|
||||
ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count, statistics);
|
||||
}
|
||||
|
||||
bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/light_limiter.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Update the LightLimiterInfo state according to the given parameters.
|
||||
* A no-op.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
*/
|
||||
static void UpdateLightLimiterEffectParameter(const LightLimiterInfo::ParameterVersion2& params,
|
||||
LightLimiterInfo::State& state) {}
|
||||
|
||||
/**
|
||||
* Initialize a new LightLimiterInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
* @param workbuffer - Game-supplied memory for the state. (Unused)
|
||||
*/
|
||||
static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
|
||||
LightLimiterInfo::State& state, const CpuAddr workbuffer) {
|
||||
state = {};
|
||||
state.samples_average.fill(0.0f);
|
||||
state.compression_gain.fill(1.0f);
|
||||
state.look_ahead_sample_offsets.fill(0);
|
||||
for (u32 i = 0; i < params.channel_count; i++) {
|
||||
state.look_ahead_sample_buffers[i].resize(params.look_ahead_samples_max, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a light limiter effect if enabled, according to the current state, on the input mix
|
||||
* buffers, saving the results to the output mix buffers.
|
||||
*
|
||||
* @param params - Input parameters to use.
|
||||
* @param state - State to use, must be initialized (see InitializeLightLimiterEffect).
|
||||
* @param enabled - If enabled, limiter will be applied, otherwise input is copied to output.
|
||||
* @param inputs - Input mix buffers to perform the limiter on.
|
||||
* @param outputs - Output mix buffers to receive the limited samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
* @params statistics - Optional output statistics, only used with version 2.
|
||||
*/
|
||||
static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
|
||||
LightLimiterInfo::State& state, const bool enabled,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count,
|
||||
LightLimiterInfo::StatisticsInternal* statistics) {
|
||||
constexpr s64 min{std::numeric_limits<s32>::min()};
|
||||
constexpr s64 max{std::numeric_limits<s32>::max()};
|
||||
|
||||
const auto recip_estimate = [](f64 a) -> f64 {
|
||||
s32 q, s;
|
||||
f64 r;
|
||||
q = (s32)(a * 512.0); /* a in units of 1/512 rounded down */
|
||||
r = 1.0 / (((f64)q + 0.5) / 512.0); /* reciprocal r */
|
||||
s = (s32)(256.0 * r + 0.5); /* r in units of 1/256 rounded to nearest */
|
||||
return ((f64)s / 256.0);
|
||||
};
|
||||
|
||||
if (enabled) {
|
||||
if (statistics && params.statistics_reset_required) {
|
||||
for (u32 i = 0; i < params.channel_count; i++) {
|
||||
statistics->channel_compression_gain_min[i] = 1.0f;
|
||||
statistics->channel_max_sample[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
|
||||
for (u32 channel = 0; channel < params.channel_count; channel++) {
|
||||
auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) /
|
||||
Common::FixedPoint<49, 15>::one) *
|
||||
params.input_gain};
|
||||
auto abs_sample{sample};
|
||||
if (sample < 0.0f) {
|
||||
abs_sample = -sample;
|
||||
}
|
||||
auto coeff{abs_sample > state.samples_average[channel] ? params.attack_coeff
|
||||
: params.release_coeff};
|
||||
state.samples_average[channel] +=
|
||||
((abs_sample - state.samples_average[channel]) * coeff).to_float();
|
||||
|
||||
// Reciprocal estimate
|
||||
auto new_average_sample{Common::FixedPoint<49, 15>(
|
||||
recip_estimate(state.samples_average[channel].to_double()))};
|
||||
if (params.processing_mode != LightLimiterInfo::ProcessingMode::Mode1) {
|
||||
// Two Newton-Raphson steps
|
||||
auto temp{2.0 - (state.samples_average[channel] * new_average_sample)};
|
||||
new_average_sample = 2.0 - (state.samples_average[channel] * temp);
|
||||
}
|
||||
|
||||
auto above_threshold{state.samples_average[channel] > params.threshold};
|
||||
auto attenuation{above_threshold ? params.threshold * new_average_sample : 1.0f};
|
||||
coeff = attenuation < state.compression_gain[channel] ? params.attack_coeff
|
||||
: params.release_coeff;
|
||||
state.compression_gain[channel] +=
|
||||
(attenuation - state.compression_gain[channel]) * coeff;
|
||||
|
||||
auto lookahead_sample{
|
||||
state.look_ahead_sample_buffers[channel]
|
||||
[state.look_ahead_sample_offsets[channel]]};
|
||||
|
||||
state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] =
|
||||
sample;
|
||||
state.look_ahead_sample_offsets[channel] =
|
||||
(state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min;
|
||||
|
||||
outputs[channel][sample_index] = static_cast<s32>(
|
||||
std::clamp((lookahead_sample * state.compression_gain[channel] *
|
||||
params.output_gain * Common::FixedPoint<49, 15>::one)
|
||||
.to_long(),
|
||||
min, max));
|
||||
|
||||
if (statistics) {
|
||||
statistics->channel_max_sample[channel] =
|
||||
std::max(statistics->channel_max_sample[channel], abs_sample.to_float());
|
||||
statistics->channel_compression_gain_min[channel] =
|
||||
std::min(statistics->channel_compression_gain_min[channel],
|
||||
state.compression_gain[channel].to_float());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (u32 i = 0; i < params.channel_count; i++) {
|
||||
if (params.inputs[i] != params.outputs[i]) {
|
||||
std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("LightLimiterVersion1Command\n\tinputs: ");
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
|
||||
UpdateLightLimiterEffectParameter(parameter, *state_);
|
||||
} else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
|
||||
InitializeLightLimiterEffect(parameter, *state_, workbuffer);
|
||||
}
|
||||
}
|
||||
|
||||
LightLimiterInfo::StatisticsInternal* statistics{nullptr};
|
||||
ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count, statistics);
|
||||
}
|
||||
|
||||
bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n");
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
|
||||
UpdateLightLimiterEffectParameter(parameter, *state_);
|
||||
} else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
|
||||
InitializeLightLimiterEffect(parameter, *state_, workbuffer);
|
||||
}
|
||||
}
|
||||
|
||||
auto statistics{reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(result_state)};
|
||||
ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count, statistics);
|
||||
}
|
||||
|
||||
bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/effect/light_limiter.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for limiting volume between a high and low threshold.
|
||||
* Version 1.
|
||||
*/
|
||||
struct LightLimiterVersion1Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
LightLimiterInfo::ParameterVersion2 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* AudioRenderer command for limiting volume between a high and low threshold.
|
||||
* Version 2 with output statistics.
|
||||
*/
|
||||
struct LightLimiterVersion2Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
LightLimiterInfo::ParameterVersion2 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Optional statistics, sent back to the sysmodule
|
||||
CpuAddr result_state;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/effect/light_limiter.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for limiting volume between a high and low threshold.
|
||||
* Version 1.
|
||||
*/
|
||||
struct LightLimiterVersion1Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
LightLimiterInfo::ParameterVersion2 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* AudioRenderer command for limiting volume between a high and low threshold.
|
||||
* Version 2 with output statistics.
|
||||
*/
|
||||
struct LightLimiterVersion2Command : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
LightLimiterInfo::ParameterVersion2 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Optional statistics, sent back to the sysmodule
|
||||
CpuAddr result_state;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/biquad_filter.h"
|
||||
#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format(
|
||||
"MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n",
|
||||
input, output, needs_init[0], needs_init[1]);
|
||||
}
|
||||
|
||||
void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
if (filter_tap_count > MaxBiquadFilters) {
|
||||
LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count);
|
||||
filter_tap_count = MaxBiquadFilters;
|
||||
}
|
||||
|
||||
auto input_buffer{
|
||||
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
|
||||
auto output_buffer{
|
||||
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
|
||||
|
||||
// TODO: Fix this, currently just applies the filter to the input twice,
|
||||
// and doesn't chain the biquads together at all.
|
||||
for (u32 i = 0; i < filter_tap_count; i++) {
|
||||
auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(states[i])};
|
||||
if (needs_init[i]) {
|
||||
*state = {};
|
||||
}
|
||||
|
||||
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state,
|
||||
processor.sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/biquad_filter.h"
|
||||
#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format(
|
||||
"MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n",
|
||||
input, output, needs_init[0], needs_init[1]);
|
||||
}
|
||||
|
||||
void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
if (filter_tap_count > MaxBiquadFilters) {
|
||||
LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count);
|
||||
filter_tap_count = MaxBiquadFilters;
|
||||
}
|
||||
|
||||
auto input_buffer{
|
||||
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
|
||||
auto output_buffer{
|
||||
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
|
||||
|
||||
// TODO: Fix this, currently just applies the filter to the input twice,
|
||||
// and doesn't chain the biquads together at all.
|
||||
for (u32 i = 0; i < filter_tap_count; i++) {
|
||||
auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(states[i])};
|
||||
if (needs_init[i]) {
|
||||
*state = {};
|
||||
}
|
||||
|
||||
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state,
|
||||
processor.sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/voice/voice_info.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for applying multiple biquads at once.
|
||||
*/
|
||||
struct MultiTapBiquadFilterCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer index
|
||||
s16 input;
|
||||
/// Output mix buffer index
|
||||
s16 output;
|
||||
/// Biquad parameters
|
||||
std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads;
|
||||
/// Biquad states, updated each call
|
||||
std::array<CpuAddr, MaxBiquadFilters> states;
|
||||
/// If each biquad needs initialisation
|
||||
std::array<bool, MaxBiquadFilters> needs_init;
|
||||
/// Number of active biquads
|
||||
u8 filter_tap_count;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/voice/voice_info.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for applying multiple biquads at once.
|
||||
*/
|
||||
struct MultiTapBiquadFilterCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer index
|
||||
s16 input;
|
||||
/// Output mix buffer index
|
||||
s16 output;
|
||||
/// Biquad parameters
|
||||
std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads;
|
||||
/// Biquad states, updated each call
|
||||
std::array<CpuAddr, MaxBiquadFilters> states;
|
||||
/// If each biquad needs initialisation
|
||||
std::array<bool, MaxBiquadFilters> needs_init;
|
||||
/// Number of active biquads
|
||||
u8 filter_tap_count;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,440 +1,440 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <numbers>
|
||||
#include <ranges>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/reverb.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = {
|
||||
53.9532470703125f,
|
||||
79.19256591796875f,
|
||||
116.23876953125f,
|
||||
170.61529541015625f,
|
||||
};
|
||||
|
||||
constexpr std::array<f32, ReverbInfo::MaxDelayLines> DecayMaxDelayLineTimes = {
|
||||
7.0f,
|
||||
9.0f,
|
||||
13.0f,
|
||||
17.0f,
|
||||
};
|
||||
|
||||
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps + 1>, ReverbInfo::NumEarlyModes>
|
||||
EarlyDelayTimes = {
|
||||
{{0.000000f, 3.500000f, 2.799988f, 3.899963f, 2.699951f, 13.399963f, 7.899963f, 8.399963f,
|
||||
9.899963f, 12.000000f, 12.500000f},
|
||||
{0.000000f, 11.799988f, 5.500000f, 11.199951f, 10.399963f, 38.099976f, 22.199951f,
|
||||
29.599976f, 21.199951f, 24.799988f, 40.000000f},
|
||||
{0.000000f, 41.500000f, 20.500000f, 41.299988f, 0.000000f, 29.500000f, 33.799988f,
|
||||
45.199951f, 46.799988f, 0.000000f, 50.000000f},
|
||||
{33.099976f, 43.299988f, 22.799988f, 37.899963f, 14.899963f, 35.299988f, 17.899963f,
|
||||
34.199951f, 0.000000f, 43.299988f, 50.000000f},
|
||||
{0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
|
||||
0.000000f, 0.000000f, 0.000000f}},
|
||||
};
|
||||
|
||||
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps>, ReverbInfo::NumEarlyModes>
|
||||
EarlyDelayGains = {{
|
||||
{0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f,
|
||||
0.679993f, 0.679993f},
|
||||
{0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.679993f, 0.679993f,
|
||||
0.679993f, 0.679993f},
|
||||
{0.500000f, 0.699951f, 0.699951f, 0.679993f, 0.500000f, 0.679993f, 0.679993f, 0.699951f,
|
||||
0.679993f, 0.000000f},
|
||||
{0.929993f, 0.919983f, 0.869995f, 0.859985f, 0.939941f, 0.809998f, 0.799988f, 0.769958f,
|
||||
0.759949f, 0.649963f},
|
||||
{0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
|
||||
0.000000f, 0.000000f},
|
||||
}};
|
||||
|
||||
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
|
||||
FdnDelayTimes = {{
|
||||
{53.953247f, 79.192566f, 116.238770f, 130.615295f},
|
||||
{53.953247f, 79.192566f, 116.238770f, 170.615295f},
|
||||
{5.000000f, 10.000000f, 5.000000f, 10.000000f},
|
||||
{47.029968f, 71.000000f, 103.000000f, 170.000000f},
|
||||
{53.953247f, 79.192566f, 116.238770f, 170.615295f},
|
||||
}};
|
||||
|
||||
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
|
||||
DecayDelayTimes = {{
|
||||
{7.000000f, 9.000000f, 13.000000f, 17.000000f},
|
||||
{7.000000f, 9.000000f, 13.000000f, 17.000000f},
|
||||
{1.000000f, 1.000000f, 1.000000f, 1.000000f},
|
||||
{7.000000f, 7.000000f, 13.000000f, 9.000000f},
|
||||
{7.000000f, 9.000000f, 13.000000f, 17.000000f},
|
||||
}};
|
||||
|
||||
/**
|
||||
* Update the ReverbInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
*/
|
||||
static void UpdateReverbEffectParameter(const ReverbInfo::ParameterVersion2& params,
|
||||
ReverbInfo::State& state) {
|
||||
const auto pow_10 = [](f32 val) -> f32 {
|
||||
return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
|
||||
};
|
||||
const auto cos = [](f32 degrees) -> f32 {
|
||||
return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
|
||||
};
|
||||
|
||||
static bool unk_initialized{false};
|
||||
static Common::FixedPoint<50, 14> unk_value{};
|
||||
|
||||
const auto sample_rate{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
|
||||
const auto pre_delay_time{Common::FixedPoint<50, 14>::from_base(params.pre_delay)};
|
||||
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayTaps; i++) {
|
||||
auto early_delay{
|
||||
((pre_delay_time + EarlyDelayTimes[params.early_mode][i]) * sample_rate).to_int()};
|
||||
early_delay = std::min(early_delay, state.pre_delay_line.sample_count_max);
|
||||
state.early_delay_times[i] = early_delay + 1;
|
||||
state.early_gains[i] = Common::FixedPoint<50, 14>::from_base(params.early_gain) *
|
||||
EarlyDelayGains[params.early_mode][i];
|
||||
}
|
||||
|
||||
if (params.channel_count == 2) {
|
||||
state.early_gains[4] * 0.5f;
|
||||
state.early_gains[5] * 0.5f;
|
||||
}
|
||||
|
||||
auto pre_time{
|
||||
((pre_delay_time + EarlyDelayTimes[params.early_mode][10]) * sample_rate).to_int()};
|
||||
state.pre_delay_time = std::min(pre_time, state.pre_delay_line.sample_count_max);
|
||||
|
||||
if (!unk_initialized) {
|
||||
unk_value = cos((1280.0f / sample_rate).to_float());
|
||||
unk_initialized = true;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
|
||||
const auto fdn_delay{(FdnDelayTimes[params.late_mode][i] * sample_rate).to_int()};
|
||||
state.fdn_delay_lines[i].sample_count =
|
||||
std::min(fdn_delay, state.fdn_delay_lines[i].sample_count_max);
|
||||
state.fdn_delay_lines[i].buffer_end =
|
||||
&state.fdn_delay_lines[i].buffer[state.fdn_delay_lines[i].sample_count - 1];
|
||||
|
||||
const auto decay_delay{(DecayDelayTimes[params.late_mode][i] * sample_rate).to_int()};
|
||||
state.decay_delay_lines[i].sample_count =
|
||||
std::min(decay_delay, state.decay_delay_lines[i].sample_count_max);
|
||||
state.decay_delay_lines[i].buffer_end =
|
||||
&state.decay_delay_lines[i].buffer[state.decay_delay_lines[i].sample_count - 1];
|
||||
|
||||
state.decay_delay_lines[i].decay =
|
||||
0.5999755859375f * (1.0f - Common::FixedPoint<50, 14>::from_base(params.colouration));
|
||||
|
||||
auto a{(Common::FixedPoint<50, 14>(state.fdn_delay_lines[i].sample_count_max) +
|
||||
state.decay_delay_lines[i].sample_count_max) *
|
||||
-3};
|
||||
auto b{a / (Common::FixedPoint<50, 14>::from_base(params.decay_time) * sample_rate)};
|
||||
Common::FixedPoint<50, 14> c{0.0f};
|
||||
Common::FixedPoint<50, 14> d{0.0f};
|
||||
auto hf_decay_ratio{Common::FixedPoint<50, 14>::from_base(params.high_freq_decay_ratio)};
|
||||
|
||||
if (hf_decay_ratio > 0.99493408203125f) {
|
||||
c = 0.0f;
|
||||
d = 1.0f;
|
||||
} else {
|
||||
const auto e{
|
||||
pow_10(((((1.0f / hf_decay_ratio) - 1.0f) * 2) / 100 * (b / 10)).to_float())};
|
||||
const auto f{1.0f - e};
|
||||
const auto g{2.0f - (unk_value * e * 2)};
|
||||
const auto h{std::sqrt(std::pow(g.to_float(), 2.0f) - (std::pow(f, 2.0f) * 4))};
|
||||
|
||||
c = (g - h) / (f * 2.0f);
|
||||
d = 1.0f - c;
|
||||
}
|
||||
|
||||
state.hf_decay_prev_gain[i] = c;
|
||||
state.hf_decay_gain[i] = pow_10((b / 1000).to_float()) * d * 0.70709228515625f;
|
||||
state.prev_feedback_output[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new ReverbInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
* @param workbuffer - Game-supplied memory for the state. (Unused)
|
||||
* @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb begins.
|
||||
*/
|
||||
static void InitializeReverbEffect(const ReverbInfo::ParameterVersion2& params,
|
||||
ReverbInfo::State& state, const CpuAddr workbuffer,
|
||||
const bool long_size_pre_delay_supported) {
|
||||
state = {};
|
||||
|
||||
auto delay{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
|
||||
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
|
||||
auto fdn_delay_time{(FdnMaxDelayLineTimes[i] * delay).to_uint_floor()};
|
||||
state.fdn_delay_lines[i].Initialize(fdn_delay_time, 1.0f);
|
||||
|
||||
auto decay_delay_time{(DecayMaxDelayLineTimes[i] * delay).to_uint_floor()};
|
||||
state.decay_delay_lines[i].Initialize(decay_delay_time, 0.0f);
|
||||
}
|
||||
|
||||
const auto pre_delay{long_size_pre_delay_supported ? 350.0f : 150.0f};
|
||||
const auto pre_delay_line{(pre_delay * delay).to_uint_floor()};
|
||||
state.pre_delay_line.Initialize(pre_delay_line, 1.0f);
|
||||
|
||||
const auto center_delay_time{(5 * delay).to_uint_floor()};
|
||||
state.center_delay_line.Initialize(center_delay_time, 1.0f);
|
||||
|
||||
UpdateReverbEffectParameter(params, state);
|
||||
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
|
||||
std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
|
||||
std::ranges::fill(state.decay_delay_lines[i].buffer, 0);
|
||||
}
|
||||
std::ranges::fill(state.center_delay_line.buffer, 0);
|
||||
std::ranges::fill(state.pre_delay_line.buffer, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass-through the effect, copying input to output directly, with no reverb applied.
|
||||
*
|
||||
* @param inputs - Array of input mix buffers to copy.
|
||||
* @param outputs - Array of output mix buffers to receive copy.
|
||||
* @param channel_count - Number of channels in inputs and outputs.
|
||||
* @param sample_count - Number of samples within each channel.
|
||||
*/
|
||||
static void ApplyReverbEffectBypass(std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 channel_count,
|
||||
const u32 sample_count) {
|
||||
for (u32 i = 0; i < channel_count; i++) {
|
||||
if (inputs[i].data() != outputs[i].data()) {
|
||||
std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick the delay lines, reading and returning their current output, and writing a new decaying
|
||||
* sample (mix).
|
||||
*
|
||||
* @param decay - The decay line.
|
||||
* @param fdn - Feedback delay network.
|
||||
* @param mix - The new calculated sample to be written and decayed.
|
||||
* @return The next delayed and decayed sample.
|
||||
*/
|
||||
static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine& decay,
|
||||
ReverbInfo::ReverbDelayLine& fdn,
|
||||
const Common::FixedPoint<50, 14> mix) {
|
||||
const auto val{decay.Read()};
|
||||
const auto mixed{mix - (val * decay.decay)};
|
||||
const auto out{decay.Tick(mixed) + (mixed * decay.decay)};
|
||||
|
||||
fdn.Tick(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Impl. Apply a Reverb according to the current state, on the input mix buffers,
|
||||
* saving the results to the output mix buffers.
|
||||
*
|
||||
* @tparam NumChannels - Number of channels to process. 1-6.
|
||||
Inputs/outputs should have this many buffers.
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to use, must be initialized (see InitializeReverbEffect).
|
||||
* @param inputs - Input mix buffers to perform the reverb on.
|
||||
* @param outputs - Output mix buffers to receive the reverbed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t NumChannels>
|
||||
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
|
||||
0, 0, 1, 1, 0, 1, 0, 0, 1, 1,
|
||||
};
|
||||
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
|
||||
0, 0, 1, 1, 0, 1, 2, 2, 3, 3,
|
||||
};
|
||||
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
|
||||
0, 0, 1, 1, 2, 2, 4, 4, 5, 5,
|
||||
};
|
||||
|
||||
std::span<const u8> tap_indexes{};
|
||||
if constexpr (NumChannels == 1) {
|
||||
tap_indexes = OutTapIndexes1Ch;
|
||||
} else if constexpr (NumChannels == 2) {
|
||||
tap_indexes = OutTapIndexes2Ch;
|
||||
} else if constexpr (NumChannels == 4) {
|
||||
tap_indexes = OutTapIndexes4Ch;
|
||||
} else if constexpr (NumChannels == 6) {
|
||||
tap_indexes = OutTapIndexes6Ch;
|
||||
}
|
||||
|
||||
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
|
||||
|
||||
for (u32 early_tap = 0; early_tap < ReverbInfo::MaxDelayTaps; early_tap++) {
|
||||
const auto sample{state.pre_delay_line.TapOut(state.early_delay_times[early_tap]) *
|
||||
state.early_gains[early_tap]};
|
||||
output_samples[tap_indexes[early_tap]] += sample;
|
||||
if constexpr (NumChannels == 6) {
|
||||
output_samples[static_cast<u32>(Channels::LFE)] += sample;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (NumChannels == 6) {
|
||||
output_samples[static_cast<u32>(Channels::LFE)] *= 0.2f;
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> input_sample{};
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
input_sample += inputs[channel][sample_index];
|
||||
}
|
||||
|
||||
input_sample *= 64;
|
||||
input_sample *= Common::FixedPoint<50, 14>::from_base(params.base_gain);
|
||||
state.pre_delay_line.Write(input_sample);
|
||||
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
|
||||
state.prev_feedback_output[i] =
|
||||
state.prev_feedback_output[i] * state.hf_decay_prev_gain[i] +
|
||||
state.fdn_delay_lines[i].Read() * state.hf_decay_gain[i];
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> pre_delay_sample{
|
||||
state.pre_delay_line.Read() * Common::FixedPoint<50, 14>::from_base(params.late_gain)};
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> mix_matrix{
|
||||
state.prev_feedback_output[2] + state.prev_feedback_output[1] + pre_delay_sample,
|
||||
-state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
|
||||
state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
|
||||
state.prev_feedback_output[1] - state.prev_feedback_output[2] + pre_delay_sample,
|
||||
};
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> allpass_samples{};
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
|
||||
allpass_samples[i] = Axfx2AllPassTick(state.decay_delay_lines[i],
|
||||
state.fdn_delay_lines[i], mix_matrix[i]);
|
||||
}
|
||||
|
||||
const auto dry_gain{Common::FixedPoint<50, 14>::from_base(params.dry_gain)};
|
||||
const auto wet_gain{Common::FixedPoint<50, 14>::from_base(params.wet_gain)};
|
||||
|
||||
if constexpr (NumChannels == 6) {
|
||||
const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
|
||||
allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
|
||||
allpass_samples[3], allpass_samples[2], allpass_samples[3],
|
||||
};
|
||||
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
auto in_sample{inputs[channel][sample_index] * dry_gain};
|
||||
|
||||
Common::FixedPoint<50, 14> allpass{};
|
||||
if (channel == static_cast<u32>(Channels::Center)) {
|
||||
allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
|
||||
} else {
|
||||
allpass = allpass_outputs[channel];
|
||||
}
|
||||
|
||||
auto out_sample{((output_samples[channel] + allpass) * wet_gain) / 64};
|
||||
outputs[channel][sample_index] = (in_sample + out_sample).to_int();
|
||||
}
|
||||
} else {
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
auto in_sample{inputs[channel][sample_index] * dry_gain};
|
||||
auto out_sample{((output_samples[channel] + allpass_samples[channel]) * wet_gain) /
|
||||
64};
|
||||
outputs[channel][sample_index] = (in_sample + out_sample).to_int();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a Reverb if enabled, according to the current state, on the input mix buffers,
|
||||
* saving the results to the output mix buffers.
|
||||
*
|
||||
* @param params - Input parameters to use.
|
||||
* @param state - State to use, must be initialized (see InitializeReverbEffect).
|
||||
* @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
|
||||
* @param inputs - Input mix buffers to performan the reverb on.
|
||||
* @param outputs - Output mix buffers to receive the reverbed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
|
||||
const bool enabled, std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
if (enabled) {
|
||||
switch (params.channel_count) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
ApplyReverbEffect<1>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 2:
|
||||
ApplyReverbEffect<2>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 4:
|
||||
ApplyReverbEffect<4>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 6:
|
||||
ApplyReverbEffect<6>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
default:
|
||||
ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format(
|
||||
"ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled,
|
||||
long_size_pre_delay_supported);
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<ReverbInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == ReverbInfo::ParameterState::Updating) {
|
||||
UpdateReverbEffectParameter(parameter, *state_);
|
||||
} else if (parameter.state == ReverbInfo::ParameterState::Initialized) {
|
||||
InitializeReverbEffect(parameter, *state_, workbuffer, long_size_pre_delay_supported);
|
||||
}
|
||||
}
|
||||
ApplyReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <numbers>
|
||||
#include <ranges>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/effect/reverb.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = {
|
||||
53.9532470703125f,
|
||||
79.19256591796875f,
|
||||
116.23876953125f,
|
||||
170.61529541015625f,
|
||||
};
|
||||
|
||||
constexpr std::array<f32, ReverbInfo::MaxDelayLines> DecayMaxDelayLineTimes = {
|
||||
7.0f,
|
||||
9.0f,
|
||||
13.0f,
|
||||
17.0f,
|
||||
};
|
||||
|
||||
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps + 1>, ReverbInfo::NumEarlyModes>
|
||||
EarlyDelayTimes = {
|
||||
{{0.000000f, 3.500000f, 2.799988f, 3.899963f, 2.699951f, 13.399963f, 7.899963f, 8.399963f,
|
||||
9.899963f, 12.000000f, 12.500000f},
|
||||
{0.000000f, 11.799988f, 5.500000f, 11.199951f, 10.399963f, 38.099976f, 22.199951f,
|
||||
29.599976f, 21.199951f, 24.799988f, 40.000000f},
|
||||
{0.000000f, 41.500000f, 20.500000f, 41.299988f, 0.000000f, 29.500000f, 33.799988f,
|
||||
45.199951f, 46.799988f, 0.000000f, 50.000000f},
|
||||
{33.099976f, 43.299988f, 22.799988f, 37.899963f, 14.899963f, 35.299988f, 17.899963f,
|
||||
34.199951f, 0.000000f, 43.299988f, 50.000000f},
|
||||
{0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
|
||||
0.000000f, 0.000000f, 0.000000f}},
|
||||
};
|
||||
|
||||
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps>, ReverbInfo::NumEarlyModes>
|
||||
EarlyDelayGains = {{
|
||||
{0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f,
|
||||
0.679993f, 0.679993f},
|
||||
{0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.679993f, 0.679993f,
|
||||
0.679993f, 0.679993f},
|
||||
{0.500000f, 0.699951f, 0.699951f, 0.679993f, 0.500000f, 0.679993f, 0.679993f, 0.699951f,
|
||||
0.679993f, 0.000000f},
|
||||
{0.929993f, 0.919983f, 0.869995f, 0.859985f, 0.939941f, 0.809998f, 0.799988f, 0.769958f,
|
||||
0.759949f, 0.649963f},
|
||||
{0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
|
||||
0.000000f, 0.000000f},
|
||||
}};
|
||||
|
||||
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
|
||||
FdnDelayTimes = {{
|
||||
{53.953247f, 79.192566f, 116.238770f, 130.615295f},
|
||||
{53.953247f, 79.192566f, 116.238770f, 170.615295f},
|
||||
{5.000000f, 10.000000f, 5.000000f, 10.000000f},
|
||||
{47.029968f, 71.000000f, 103.000000f, 170.000000f},
|
||||
{53.953247f, 79.192566f, 116.238770f, 170.615295f},
|
||||
}};
|
||||
|
||||
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
|
||||
DecayDelayTimes = {{
|
||||
{7.000000f, 9.000000f, 13.000000f, 17.000000f},
|
||||
{7.000000f, 9.000000f, 13.000000f, 17.000000f},
|
||||
{1.000000f, 1.000000f, 1.000000f, 1.000000f},
|
||||
{7.000000f, 7.000000f, 13.000000f, 9.000000f},
|
||||
{7.000000f, 9.000000f, 13.000000f, 17.000000f},
|
||||
}};
|
||||
|
||||
/**
|
||||
* Update the ReverbInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
*/
|
||||
static void UpdateReverbEffectParameter(const ReverbInfo::ParameterVersion2& params,
|
||||
ReverbInfo::State& state) {
|
||||
const auto pow_10 = [](f32 val) -> f32 {
|
||||
return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
|
||||
};
|
||||
const auto cos = [](f32 degrees) -> f32 {
|
||||
return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
|
||||
};
|
||||
|
||||
static bool unk_initialized{false};
|
||||
static Common::FixedPoint<50, 14> unk_value{};
|
||||
|
||||
const auto sample_rate{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
|
||||
const auto pre_delay_time{Common::FixedPoint<50, 14>::from_base(params.pre_delay)};
|
||||
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayTaps; i++) {
|
||||
auto early_delay{
|
||||
((pre_delay_time + EarlyDelayTimes[params.early_mode][i]) * sample_rate).to_int()};
|
||||
early_delay = std::min(early_delay, state.pre_delay_line.sample_count_max);
|
||||
state.early_delay_times[i] = early_delay + 1;
|
||||
state.early_gains[i] = Common::FixedPoint<50, 14>::from_base(params.early_gain) *
|
||||
EarlyDelayGains[params.early_mode][i];
|
||||
}
|
||||
|
||||
if (params.channel_count == 2) {
|
||||
state.early_gains[4] * 0.5f;
|
||||
state.early_gains[5] * 0.5f;
|
||||
}
|
||||
|
||||
auto pre_time{
|
||||
((pre_delay_time + EarlyDelayTimes[params.early_mode][10]) * sample_rate).to_int()};
|
||||
state.pre_delay_time = std::min(pre_time, state.pre_delay_line.sample_count_max);
|
||||
|
||||
if (!unk_initialized) {
|
||||
unk_value = cos((1280.0f / sample_rate).to_float());
|
||||
unk_initialized = true;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
|
||||
const auto fdn_delay{(FdnDelayTimes[params.late_mode][i] * sample_rate).to_int()};
|
||||
state.fdn_delay_lines[i].sample_count =
|
||||
std::min(fdn_delay, state.fdn_delay_lines[i].sample_count_max);
|
||||
state.fdn_delay_lines[i].buffer_end =
|
||||
&state.fdn_delay_lines[i].buffer[state.fdn_delay_lines[i].sample_count - 1];
|
||||
|
||||
const auto decay_delay{(DecayDelayTimes[params.late_mode][i] * sample_rate).to_int()};
|
||||
state.decay_delay_lines[i].sample_count =
|
||||
std::min(decay_delay, state.decay_delay_lines[i].sample_count_max);
|
||||
state.decay_delay_lines[i].buffer_end =
|
||||
&state.decay_delay_lines[i].buffer[state.decay_delay_lines[i].sample_count - 1];
|
||||
|
||||
state.decay_delay_lines[i].decay =
|
||||
0.5999755859375f * (1.0f - Common::FixedPoint<50, 14>::from_base(params.colouration));
|
||||
|
||||
auto a{(Common::FixedPoint<50, 14>(state.fdn_delay_lines[i].sample_count_max) +
|
||||
state.decay_delay_lines[i].sample_count_max) *
|
||||
-3};
|
||||
auto b{a / (Common::FixedPoint<50, 14>::from_base(params.decay_time) * sample_rate)};
|
||||
Common::FixedPoint<50, 14> c{0.0f};
|
||||
Common::FixedPoint<50, 14> d{0.0f};
|
||||
auto hf_decay_ratio{Common::FixedPoint<50, 14>::from_base(params.high_freq_decay_ratio)};
|
||||
|
||||
if (hf_decay_ratio > 0.99493408203125f) {
|
||||
c = 0.0f;
|
||||
d = 1.0f;
|
||||
} else {
|
||||
const auto e{
|
||||
pow_10(((((1.0f / hf_decay_ratio) - 1.0f) * 2) / 100 * (b / 10)).to_float())};
|
||||
const auto f{1.0f - e};
|
||||
const auto g{2.0f - (unk_value * e * 2)};
|
||||
const auto h{std::sqrt(std::pow(g.to_float(), 2.0f) - (std::pow(f, 2.0f) * 4))};
|
||||
|
||||
c = (g - h) / (f * 2.0f);
|
||||
d = 1.0f - c;
|
||||
}
|
||||
|
||||
state.hf_decay_prev_gain[i] = c;
|
||||
state.hf_decay_gain[i] = pow_10((b / 1000).to_float()) * d * 0.70709228515625f;
|
||||
state.prev_feedback_output[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new ReverbInfo state according to the given parameters.
|
||||
*
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to be updated.
|
||||
* @param workbuffer - Game-supplied memory for the state. (Unused)
|
||||
* @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb begins.
|
||||
*/
|
||||
static void InitializeReverbEffect(const ReverbInfo::ParameterVersion2& params,
|
||||
ReverbInfo::State& state, const CpuAddr workbuffer,
|
||||
const bool long_size_pre_delay_supported) {
|
||||
state = {};
|
||||
|
||||
auto delay{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
|
||||
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
|
||||
auto fdn_delay_time{(FdnMaxDelayLineTimes[i] * delay).to_uint_floor()};
|
||||
state.fdn_delay_lines[i].Initialize(fdn_delay_time, 1.0f);
|
||||
|
||||
auto decay_delay_time{(DecayMaxDelayLineTimes[i] * delay).to_uint_floor()};
|
||||
state.decay_delay_lines[i].Initialize(decay_delay_time, 0.0f);
|
||||
}
|
||||
|
||||
const auto pre_delay{long_size_pre_delay_supported ? 350.0f : 150.0f};
|
||||
const auto pre_delay_line{(pre_delay * delay).to_uint_floor()};
|
||||
state.pre_delay_line.Initialize(pre_delay_line, 1.0f);
|
||||
|
||||
const auto center_delay_time{(5 * delay).to_uint_floor()};
|
||||
state.center_delay_line.Initialize(center_delay_time, 1.0f);
|
||||
|
||||
UpdateReverbEffectParameter(params, state);
|
||||
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
|
||||
std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
|
||||
std::ranges::fill(state.decay_delay_lines[i].buffer, 0);
|
||||
}
|
||||
std::ranges::fill(state.center_delay_line.buffer, 0);
|
||||
std::ranges::fill(state.pre_delay_line.buffer, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass-through the effect, copying input to output directly, with no reverb applied.
|
||||
*
|
||||
* @param inputs - Array of input mix buffers to copy.
|
||||
* @param outputs - Array of output mix buffers to receive copy.
|
||||
* @param channel_count - Number of channels in inputs and outputs.
|
||||
* @param sample_count - Number of samples within each channel.
|
||||
*/
|
||||
static void ApplyReverbEffectBypass(std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 channel_count,
|
||||
const u32 sample_count) {
|
||||
for (u32 i = 0; i < channel_count; i++) {
|
||||
if (inputs[i].data() != outputs[i].data()) {
|
||||
std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick the delay lines, reading and returning their current output, and writing a new decaying
|
||||
* sample (mix).
|
||||
*
|
||||
* @param decay - The decay line.
|
||||
* @param fdn - Feedback delay network.
|
||||
* @param mix - The new calculated sample to be written and decayed.
|
||||
* @return The next delayed and decayed sample.
|
||||
*/
|
||||
static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine& decay,
|
||||
ReverbInfo::ReverbDelayLine& fdn,
|
||||
const Common::FixedPoint<50, 14> mix) {
|
||||
const auto val{decay.Read()};
|
||||
const auto mixed{mix - (val * decay.decay)};
|
||||
const auto out{decay.Tick(mixed) + (mixed * decay.decay)};
|
||||
|
||||
fdn.Tick(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Impl. Apply a Reverb according to the current state, on the input mix buffers,
|
||||
* saving the results to the output mix buffers.
|
||||
*
|
||||
* @tparam NumChannels - Number of channels to process. 1-6.
|
||||
Inputs/outputs should have this many buffers.
|
||||
* @param params - Input parameters to update the state.
|
||||
* @param state - State to use, must be initialized (see InitializeReverbEffect).
|
||||
* @param inputs - Input mix buffers to perform the reverb on.
|
||||
* @param outputs - Output mix buffers to receive the reverbed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t NumChannels>
|
||||
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
|
||||
0, 0, 1, 1, 0, 1, 0, 0, 1, 1,
|
||||
};
|
||||
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
|
||||
0, 0, 1, 1, 0, 1, 2, 2, 3, 3,
|
||||
};
|
||||
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
|
||||
0, 0, 1, 1, 2, 2, 4, 4, 5, 5,
|
||||
};
|
||||
|
||||
std::span<const u8> tap_indexes{};
|
||||
if constexpr (NumChannels == 1) {
|
||||
tap_indexes = OutTapIndexes1Ch;
|
||||
} else if constexpr (NumChannels == 2) {
|
||||
tap_indexes = OutTapIndexes2Ch;
|
||||
} else if constexpr (NumChannels == 4) {
|
||||
tap_indexes = OutTapIndexes4Ch;
|
||||
} else if constexpr (NumChannels == 6) {
|
||||
tap_indexes = OutTapIndexes6Ch;
|
||||
}
|
||||
|
||||
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
|
||||
|
||||
for (u32 early_tap = 0; early_tap < ReverbInfo::MaxDelayTaps; early_tap++) {
|
||||
const auto sample{state.pre_delay_line.TapOut(state.early_delay_times[early_tap]) *
|
||||
state.early_gains[early_tap]};
|
||||
output_samples[tap_indexes[early_tap]] += sample;
|
||||
if constexpr (NumChannels == 6) {
|
||||
output_samples[static_cast<u32>(Channels::LFE)] += sample;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (NumChannels == 6) {
|
||||
output_samples[static_cast<u32>(Channels::LFE)] *= 0.2f;
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> input_sample{};
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
input_sample += inputs[channel][sample_index];
|
||||
}
|
||||
|
||||
input_sample *= 64;
|
||||
input_sample *= Common::FixedPoint<50, 14>::from_base(params.base_gain);
|
||||
state.pre_delay_line.Write(input_sample);
|
||||
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
|
||||
state.prev_feedback_output[i] =
|
||||
state.prev_feedback_output[i] * state.hf_decay_prev_gain[i] +
|
||||
state.fdn_delay_lines[i].Read() * state.hf_decay_gain[i];
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> pre_delay_sample{
|
||||
state.pre_delay_line.Read() * Common::FixedPoint<50, 14>::from_base(params.late_gain)};
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> mix_matrix{
|
||||
state.prev_feedback_output[2] + state.prev_feedback_output[1] + pre_delay_sample,
|
||||
-state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
|
||||
state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
|
||||
state.prev_feedback_output[1] - state.prev_feedback_output[2] + pre_delay_sample,
|
||||
};
|
||||
|
||||
std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> allpass_samples{};
|
||||
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
|
||||
allpass_samples[i] = Axfx2AllPassTick(state.decay_delay_lines[i],
|
||||
state.fdn_delay_lines[i], mix_matrix[i]);
|
||||
}
|
||||
|
||||
const auto dry_gain{Common::FixedPoint<50, 14>::from_base(params.dry_gain)};
|
||||
const auto wet_gain{Common::FixedPoint<50, 14>::from_base(params.wet_gain)};
|
||||
|
||||
if constexpr (NumChannels == 6) {
|
||||
const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
|
||||
allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
|
||||
allpass_samples[3], allpass_samples[2], allpass_samples[3],
|
||||
};
|
||||
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
auto in_sample{inputs[channel][sample_index] * dry_gain};
|
||||
|
||||
Common::FixedPoint<50, 14> allpass{};
|
||||
if (channel == static_cast<u32>(Channels::Center)) {
|
||||
allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
|
||||
} else {
|
||||
allpass = allpass_outputs[channel];
|
||||
}
|
||||
|
||||
auto out_sample{((output_samples[channel] + allpass) * wet_gain) / 64};
|
||||
outputs[channel][sample_index] = (in_sample + out_sample).to_int();
|
||||
}
|
||||
} else {
|
||||
for (u32 channel = 0; channel < NumChannels; channel++) {
|
||||
auto in_sample{inputs[channel][sample_index] * dry_gain};
|
||||
auto out_sample{((output_samples[channel] + allpass_samples[channel]) * wet_gain) /
|
||||
64};
|
||||
outputs[channel][sample_index] = (in_sample + out_sample).to_int();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a Reverb if enabled, according to the current state, on the input mix buffers,
|
||||
* saving the results to the output mix buffers.
|
||||
*
|
||||
* @param params - Input parameters to use.
|
||||
* @param state - State to use, must be initialized (see InitializeReverbEffect).
|
||||
* @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
|
||||
* @param inputs - Input mix buffers to performan the reverb on.
|
||||
* @param outputs - Output mix buffers to receive the reverbed samples.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
|
||||
const bool enabled, std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
if (enabled) {
|
||||
switch (params.channel_count) {
|
||||
case 0:
|
||||
return;
|
||||
case 1:
|
||||
ApplyReverbEffect<1>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 2:
|
||||
ApplyReverbEffect<2>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 4:
|
||||
ApplyReverbEffect<4>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
case 6:
|
||||
ApplyReverbEffect<6>(params, state, inputs, outputs, sample_count);
|
||||
break;
|
||||
default:
|
||||
ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format(
|
||||
"ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled,
|
||||
long_size_pre_delay_supported);
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
auto state_{reinterpret_cast<ReverbInfo::State*>(state)};
|
||||
|
||||
if (effect_enabled) {
|
||||
if (parameter.state == ReverbInfo::ParameterState::Updating) {
|
||||
UpdateReverbEffectParameter(parameter, *state_);
|
||||
} else if (parameter.state == ReverbInfo::ParameterState::Initialized) {
|
||||
InitializeReverbEffect(parameter, *state_, workbuffer, long_size_pre_delay_supported);
|
||||
}
|
||||
}
|
||||
ApplyReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
|
||||
processor.sample_count);
|
||||
}
|
||||
|
||||
bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/effect/reverb.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives
|
||||
* the results.
|
||||
*/
|
||||
struct ReverbCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
ReverbInfo::ParameterVersion2 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
/// Is a longer pre-delay time supported?
|
||||
bool long_size_pre_delay_supported;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/effect/reverb.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives
|
||||
* the results.
|
||||
*/
|
||||
struct ReverbCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Input parameters
|
||||
ReverbInfo::ParameterVersion2 parameter;
|
||||
/// State, updated each call
|
||||
CpuAddr state;
|
||||
/// Game-supplied workbuffer (Unused)
|
||||
CpuAddr workbuffer;
|
||||
/// Is this effect enabled?
|
||||
bool effect_enabled;
|
||||
/// Is a longer pre-delay time supported?
|
||||
bool long_size_pre_delay_supported;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
enum class CommandId : u8 {
|
||||
/* 0x00 */ Invalid,
|
||||
/* 0x01 */ DataSourcePcmInt16Version1,
|
||||
/* 0x02 */ DataSourcePcmInt16Version2,
|
||||
/* 0x03 */ DataSourcePcmFloatVersion1,
|
||||
/* 0x04 */ DataSourcePcmFloatVersion2,
|
||||
/* 0x05 */ DataSourceAdpcmVersion1,
|
||||
/* 0x06 */ DataSourceAdpcmVersion2,
|
||||
/* 0x07 */ Volume,
|
||||
/* 0x08 */ VolumeRamp,
|
||||
/* 0x09 */ BiquadFilter,
|
||||
/* 0x0A */ Mix,
|
||||
/* 0x0B */ MixRamp,
|
||||
/* 0x0C */ MixRampGrouped,
|
||||
/* 0x0D */ DepopPrepare,
|
||||
/* 0x0E */ DepopForMixBuffers,
|
||||
/* 0x0F */ Delay,
|
||||
/* 0x10 */ Upsample,
|
||||
/* 0x11 */ DownMix6chTo2ch,
|
||||
/* 0x12 */ Aux,
|
||||
/* 0x13 */ DeviceSink,
|
||||
/* 0x14 */ CircularBufferSink,
|
||||
/* 0x15 */ Reverb,
|
||||
/* 0x16 */ I3dl2Reverb,
|
||||
/* 0x17 */ Performance,
|
||||
/* 0x18 */ ClearMixBuffer,
|
||||
/* 0x19 */ CopyMixBuffer,
|
||||
/* 0x1A */ LightLimiterVersion1,
|
||||
/* 0x1B */ LightLimiterVersion2,
|
||||
/* 0x1C */ MultiTapBiquadFilter,
|
||||
/* 0x1D */ Capture,
|
||||
/* 0x1E */ Compressor,
|
||||
};
|
||||
|
||||
constexpr u32 CommandMagic{0xCAFEBABE};
|
||||
|
||||
/**
|
||||
* A command, generated by the host, and processed by the ADSP's AudioRenderer.
|
||||
*/
|
||||
struct ICommand {
|
||||
virtual ~ICommand() = default;
|
||||
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
virtual void Process(const ADSP::CommandListProcessor& processor) = 0;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0;
|
||||
|
||||
/// Command magic 0xCAFEBABE
|
||||
u32 magic{};
|
||||
/// Command enabled
|
||||
bool enabled{};
|
||||
/// Type of this command (see CommandId)
|
||||
CommandId type{};
|
||||
/// Size of this command
|
||||
s16 size{};
|
||||
/// Estimated processing time for this command
|
||||
u32 estimated_process_time{};
|
||||
/// Node id of the voice or mix this command was generated from
|
||||
u32 node_id{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
enum class CommandId : u8 {
|
||||
/* 0x00 */ Invalid,
|
||||
/* 0x01 */ DataSourcePcmInt16Version1,
|
||||
/* 0x02 */ DataSourcePcmInt16Version2,
|
||||
/* 0x03 */ DataSourcePcmFloatVersion1,
|
||||
/* 0x04 */ DataSourcePcmFloatVersion2,
|
||||
/* 0x05 */ DataSourceAdpcmVersion1,
|
||||
/* 0x06 */ DataSourceAdpcmVersion2,
|
||||
/* 0x07 */ Volume,
|
||||
/* 0x08 */ VolumeRamp,
|
||||
/* 0x09 */ BiquadFilter,
|
||||
/* 0x0A */ Mix,
|
||||
/* 0x0B */ MixRamp,
|
||||
/* 0x0C */ MixRampGrouped,
|
||||
/* 0x0D */ DepopPrepare,
|
||||
/* 0x0E */ DepopForMixBuffers,
|
||||
/* 0x0F */ Delay,
|
||||
/* 0x10 */ Upsample,
|
||||
/* 0x11 */ DownMix6chTo2ch,
|
||||
/* 0x12 */ Aux,
|
||||
/* 0x13 */ DeviceSink,
|
||||
/* 0x14 */ CircularBufferSink,
|
||||
/* 0x15 */ Reverb,
|
||||
/* 0x16 */ I3dl2Reverb,
|
||||
/* 0x17 */ Performance,
|
||||
/* 0x18 */ ClearMixBuffer,
|
||||
/* 0x19 */ CopyMixBuffer,
|
||||
/* 0x1A */ LightLimiterVersion1,
|
||||
/* 0x1B */ LightLimiterVersion2,
|
||||
/* 0x1C */ MultiTapBiquadFilter,
|
||||
/* 0x1D */ Capture,
|
||||
/* 0x1E */ Compressor,
|
||||
};
|
||||
|
||||
constexpr u32 CommandMagic{0xCAFEBABE};
|
||||
|
||||
/**
|
||||
* A command, generated by the host, and processed by the ADSP's AudioRenderer.
|
||||
*/
|
||||
struct ICommand {
|
||||
virtual ~ICommand() = default;
|
||||
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
virtual void Process(const ADSP::CommandListProcessor& processor) = 0;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0;
|
||||
|
||||
/// Command magic 0xCAFEBABE
|
||||
u32 magic{};
|
||||
/// Command enabled
|
||||
bool enabled{};
|
||||
/// Type of this command (see CommandId)
|
||||
CommandId type{};
|
||||
/// Size of this command
|
||||
s16 size{};
|
||||
/// Estimated processing time for this command
|
||||
u32 estimated_process_time{};
|
||||
/// Node id of the voice or mix this command was generated from
|
||||
u32 node_id{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/clear_mix.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("ClearMixBufferCommand\n");
|
||||
}
|
||||
|
||||
void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes());
|
||||
}
|
||||
|
||||
bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/clear_mix.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("ClearMixBufferCommand\n");
|
||||
}
|
||||
|
||||
void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes());
|
||||
}
|
||||
|
||||
bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for a clearing the mix buffers.
|
||||
* Used at the start of each command list.
|
||||
*/
|
||||
struct ClearMixBufferCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for a clearing the mix buffers.
|
||||
* Used at the start of each command list.
|
||||
*/
|
||||
struct ClearMixBufferCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/copy_mix.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index,
|
||||
output_index);
|
||||
}
|
||||
|
||||
void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32));
|
||||
}
|
||||
|
||||
bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/copy_mix.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index,
|
||||
output_index);
|
||||
}
|
||||
|
||||
void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32));
|
||||
}
|
||||
|
||||
bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for a copying a mix buffer from input to output.
|
||||
*/
|
||||
struct CopyMixBufferCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer index
|
||||
s16 input_index;
|
||||
/// Output mix buffer index
|
||||
s16 output_index;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for a copying a mix buffer from input to output.
|
||||
*/
|
||||
struct CopyMixBufferCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer index
|
||||
s16 input_index;
|
||||
/// Output mix buffer index
|
||||
s16 output_index;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time
|
||||
* according to decay.
|
||||
*
|
||||
* @param output - Output buffer to be depopped.
|
||||
* @param depop_sample - Depopped sample to apply to output samples.
|
||||
* @param decay_ - Amount to decay the depopped sample for every output sample.
|
||||
* @param sample_count - Samples to process.
|
||||
* @return Final decayed depop sample.
|
||||
*/
|
||||
static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample,
|
||||
Common::FixedPoint<49, 15>& decay_, const u32 sample_count) {
|
||||
auto sample{std::abs(depop_sample)};
|
||||
auto decay{decay_.to_raw()};
|
||||
|
||||
if (depop_sample <= 0) {
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
|
||||
output[i] -= sample;
|
||||
}
|
||||
return -sample;
|
||||
} else {
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
|
||||
output[i] += sample;
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
|
||||
void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input,
|
||||
count, decay.to_float());
|
||||
}
|
||||
|
||||
void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto end_index{std::min(processor.buffer_count, input + count)};
|
||||
std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index};
|
||||
|
||||
for (u32 index = input; index < end_index; index++) {
|
||||
const auto depop_sample{depop_buff[index]};
|
||||
if (depop_sample != 0) {
|
||||
auto input_buffer{processor.mix_buffers.subspan(index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
depop_buff[index] =
|
||||
ApplyDepopMix(input_buffer, depop_sample, decay, processor.sample_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time
|
||||
* according to decay.
|
||||
*
|
||||
* @param output - Output buffer to be depopped.
|
||||
* @param depop_sample - Depopped sample to apply to output samples.
|
||||
* @param decay_ - Amount to decay the depopped sample for every output sample.
|
||||
* @param sample_count - Samples to process.
|
||||
* @return Final decayed depop sample.
|
||||
*/
|
||||
static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample,
|
||||
Common::FixedPoint<49, 15>& decay_, const u32 sample_count) {
|
||||
auto sample{std::abs(depop_sample)};
|
||||
auto decay{decay_.to_raw()};
|
||||
|
||||
if (depop_sample <= 0) {
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
|
||||
output[i] -= sample;
|
||||
}
|
||||
return -sample;
|
||||
} else {
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
|
||||
output[i] += sample;
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
|
||||
void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input,
|
||||
count, decay.to_float());
|
||||
}
|
||||
|
||||
void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto end_index{std::min(processor.buffer_count, input + count)};
|
||||
std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index};
|
||||
|
||||
for (u32 index = input; index < end_index; index++) {
|
||||
const auto depop_sample{depop_buff[index]};
|
||||
if (depop_sample != 0) {
|
||||
auto input_buffer{processor.mix_buffers.subspan(index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
depop_buff[index] =
|
||||
ApplyDepopMix(input_buffer, depop_sample, decay, processor.sample_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for depopping a mix buffer.
|
||||
* Adds a cumulation of previous samples to the current mix buffer with a decay.
|
||||
*/
|
||||
struct DepopForMixBuffersCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Starting input mix buffer index
|
||||
u32 input;
|
||||
/// Number of mix buffers to depop
|
||||
u32 count;
|
||||
/// Amount to decay the depop sample for each new sample
|
||||
Common::FixedPoint<49, 15> decay;
|
||||
/// Address of the depop buffer, holding the last sample for every mix buffer
|
||||
CpuAddr depop_buffer;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for depopping a mix buffer.
|
||||
* Adds a cumulation of previous samples to the current mix buffer with a decay.
|
||||
*/
|
||||
struct DepopForMixBuffersCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Starting input mix buffer index
|
||||
u32 input;
|
||||
/// Number of mix buffers to depop
|
||||
u32 count;
|
||||
/// Amount to decay the depop sample for each new sample
|
||||
Common::FixedPoint<49, 15> decay;
|
||||
/// Address of the depop buffer, holding the last sample for every mix buffer
|
||||
CpuAddr depop_buffer;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/depop_prepare.h"
|
||||
#include "audio_core/renderer/voice/voice_state.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("DepopPrepareCommand\n\tinputs: ");
|
||||
for (u32 i = 0; i < buffer_count; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto samples{reinterpret_cast<s32*>(previous_samples)};
|
||||
auto buffer{reinterpret_cast<s32*>(depop_buffer)};
|
||||
|
||||
for (u32 i = 0; i < buffer_count; i++) {
|
||||
if (samples[i]) {
|
||||
buffer[inputs[i]] += samples[i];
|
||||
samples[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/depop_prepare.h"
|
||||
#include "audio_core/renderer/voice/voice_state.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("DepopPrepareCommand\n\tinputs: ");
|
||||
for (u32 i = 0; i < buffer_count; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto samples{reinterpret_cast<s32*>(previous_samples)};
|
||||
auto buffer{reinterpret_cast<s32*>(depop_buffer)};
|
||||
|
||||
for (u32 i = 0; i < buffer_count; i++) {
|
||||
if (samples[i]) {
|
||||
buffer[inputs[i]] += samples[i];
|
||||
samples[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for preparing depop.
|
||||
* Adds the previusly output last samples to the depop buffer.
|
||||
*/
|
||||
struct DepopPrepareCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Depop buffer offset for each mix buffer
|
||||
std::array<s16, MaxMixBuffers> inputs;
|
||||
/// Pointer to the previous mix buffer samples
|
||||
CpuAddr previous_samples;
|
||||
/// Number of mix buffers to use
|
||||
u32 buffer_count;
|
||||
/// Pointer to the current depop values
|
||||
CpuAddr depop_buffer;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for preparing depop.
|
||||
* Adds the previusly output last samples to the depop buffer.
|
||||
*/
|
||||
struct DepopPrepareCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Depop buffer offset for each mix buffer
|
||||
std::array<s16, MaxMixBuffers> inputs;
|
||||
/// Pointer to the previous mix buffer samples
|
||||
CpuAddr previous_samples;
|
||||
/// Number of mix buffers to use
|
||||
u32 buffer_count;
|
||||
/// Pointer to the current depop values
|
||||
CpuAddr depop_buffer;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/mix.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Mix input mix buffer into output mix buffer, with volume applied to the input.
|
||||
*
|
||||
* @tparam Q - Number of bits for fixed point operations.
|
||||
* @param output - Output mix buffer.
|
||||
* @param input - Input mix buffer.
|
||||
* @param volume - Volume applied to the input.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t Q>
|
||||
static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f32 volume_,
|
||||
const u32 sample_count) {
|
||||
const Common::FixedPoint<64 - Q, Q> volume{volume_};
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
output[i] = (output[i] + input[i] * volume).to_int();
|
||||
}
|
||||
}
|
||||
|
||||
void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("MixCommand");
|
||||
string += fmt::format("\n\tinput {:02X}", input_index);
|
||||
string += fmt::format("\n\toutput {:02X}", output_index);
|
||||
string += fmt::format("\n\tvolume {:.8f}", volume);
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void MixCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
// If volume is 0, nothing will be added to the output, so just skip.
|
||||
if (volume == 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (precision) {
|
||||
case 15:
|
||||
ApplyMix<15>(output, input, volume, processor.sample_count);
|
||||
break;
|
||||
|
||||
case 23:
|
||||
ApplyMix<23>(output, input, volume, processor.sample_count);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/mix.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Mix input mix buffer into output mix buffer, with volume applied to the input.
|
||||
*
|
||||
* @tparam Q - Number of bits for fixed point operations.
|
||||
* @param output - Output mix buffer.
|
||||
* @param input - Input mix buffer.
|
||||
* @param volume - Volume applied to the input.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t Q>
|
||||
static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f32 volume_,
|
||||
const u32 sample_count) {
|
||||
const Common::FixedPoint<64 - Q, Q> volume{volume_};
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
output[i] = (output[i] + input[i] * volume).to_int();
|
||||
}
|
||||
}
|
||||
|
||||
void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("MixCommand");
|
||||
string += fmt::format("\n\tinput {:02X}", input_index);
|
||||
string += fmt::format("\n\toutput {:02X}", output_index);
|
||||
string += fmt::format("\n\tvolume {:.8f}", volume);
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void MixCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
// If volume is 0, nothing will be added to the output, so just skip.
|
||||
if (volume == 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (precision) {
|
||||
case 15:
|
||||
ApplyMix<15>(output, input, volume, processor.sample_count);
|
||||
break;
|
||||
|
||||
case 23:
|
||||
ApplyMix<23>(output, input, volume, processor.sample_count);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
|
||||
* applied to the input.
|
||||
*/
|
||||
struct MixCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Fixed point precision
|
||||
u8 precision;
|
||||
/// Input mix buffer index
|
||||
s16 input_index;
|
||||
/// Output mix buffer index
|
||||
s16 output_index;
|
||||
/// Mix volume applied to the input
|
||||
f32 volume;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
|
||||
* applied to the input.
|
||||
*/
|
||||
struct MixCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Fixed point precision
|
||||
u8 precision;
|
||||
/// Input mix buffer index
|
||||
s16 input_index;
|
||||
/// Output mix buffer index
|
||||
s16 output_index;
|
||||
/// Mix volume applied to the input
|
||||
f32 volume;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,82 +1,82 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/mix_ramp.h"
|
||||
#include "common/fixed_point.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
template <size_t Q>
|
||||
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
|
||||
const f32 ramp_, const u32 sample_count) {
|
||||
Common::FixedPoint<64 - Q, Q> volume{volume_};
|
||||
Common::FixedPoint<64 - Q, Q> sample{0};
|
||||
|
||||
if (ramp_ == 0.0f) {
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
sample = input[i] * volume;
|
||||
output[i] = (output[i] + sample).to_int();
|
||||
}
|
||||
} else {
|
||||
Common::FixedPoint<64 - Q, Q> ramp{ramp_};
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
sample = input[i] * volume;
|
||||
output[i] = (output[i] + sample).to_int();
|
||||
volume += ramp;
|
||||
}
|
||||
}
|
||||
return sample.to_int();
|
||||
}
|
||||
|
||||
template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32);
|
||||
template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32);
|
||||
|
||||
void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
|
||||
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
|
||||
string += fmt::format("MixRampCommand");
|
||||
string += fmt::format("\n\tinput {:02X}", input_index);
|
||||
string += fmt::format("\n\toutput {:02X}", output_index);
|
||||
string += fmt::format("\n\tvolume {:.8f}", volume);
|
||||
string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
|
||||
string += fmt::format("\n\tramp {:.8f}", ramp);
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
|
||||
auto prev_sample_ptr{reinterpret_cast<s32*>(previous_sample)};
|
||||
|
||||
// If previous volume and ramp are both 0, nothing will be added to the output, so just skip.
|
||||
if (prev_volume == 0.0f && ramp == 0.0f) {
|
||||
*prev_sample_ptr = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (precision) {
|
||||
case 15:
|
||||
*prev_sample_ptr =
|
||||
ApplyMixRamp<15>(output, input, prev_volume, ramp, processor.sample_count);
|
||||
break;
|
||||
|
||||
case 23:
|
||||
*prev_sample_ptr =
|
||||
ApplyMixRamp<23>(output, input, prev_volume, ramp, processor.sample_count);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/mix_ramp.h"
|
||||
#include "common/fixed_point.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
template <size_t Q>
|
||||
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
|
||||
const f32 ramp_, const u32 sample_count) {
|
||||
Common::FixedPoint<64 - Q, Q> volume{volume_};
|
||||
Common::FixedPoint<64 - Q, Q> sample{0};
|
||||
|
||||
if (ramp_ == 0.0f) {
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
sample = input[i] * volume;
|
||||
output[i] = (output[i] + sample).to_int();
|
||||
}
|
||||
} else {
|
||||
Common::FixedPoint<64 - Q, Q> ramp{ramp_};
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
sample = input[i] * volume;
|
||||
output[i] = (output[i] + sample).to_int();
|
||||
volume += ramp;
|
||||
}
|
||||
}
|
||||
return sample.to_int();
|
||||
}
|
||||
|
||||
template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32);
|
||||
template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32);
|
||||
|
||||
void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
|
||||
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
|
||||
string += fmt::format("MixRampCommand");
|
||||
string += fmt::format("\n\tinput {:02X}", input_index);
|
||||
string += fmt::format("\n\toutput {:02X}", output_index);
|
||||
string += fmt::format("\n\tvolume {:.8f}", volume);
|
||||
string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
|
||||
string += fmt::format("\n\tramp {:.8f}", ramp);
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
|
||||
auto prev_sample_ptr{reinterpret_cast<s32*>(previous_sample)};
|
||||
|
||||
// If previous volume and ramp are both 0, nothing will be added to the output, so just skip.
|
||||
if (prev_volume == 0.0f && ramp == 0.0f) {
|
||||
*prev_sample_ptr = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (precision) {
|
||||
case 15:
|
||||
*prev_sample_ptr =
|
||||
ApplyMixRamp<15>(output, input, prev_volume, ramp, processor.sample_count);
|
||||
break;
|
||||
|
||||
case 23:
|
||||
*prev_sample_ptr =
|
||||
ApplyMixRamp<23>(output, input, prev_volume, ramp, processor.sample_count);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,73 +1,73 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
|
||||
* applied to the input, and volume ramping to smooth out the transition.
|
||||
*/
|
||||
struct MixRampCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Fixed point precision
|
||||
u8 precision;
|
||||
/// Input mix buffer index
|
||||
s16 input_index;
|
||||
/// Output mix buffer index
|
||||
s16 output_index;
|
||||
/// Previous mix volume
|
||||
f32 prev_volume;
|
||||
/// Current mix volume
|
||||
f32 volume;
|
||||
/// Pointer to the previous sample buffer, used for depopping
|
||||
CpuAddr previous_sample;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mix input mix buffer into output mix buffer, with volume applied to the input.
|
||||
* @tparam Q - Number of bits for fixed point operations.
|
||||
* @param output - Output mix buffer.
|
||||
* @param input - Input mix buffer.
|
||||
* @param volume_ - Volume applied to the input.
|
||||
* @param ramp_ - Ramp applied to volume every sample.
|
||||
* @param sample_count - Number of samples to process.
|
||||
* @return The final gained input sample, used for depopping.
|
||||
*/
|
||||
template <size_t Q>
|
||||
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_,
|
||||
u32 sample_count);
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
|
||||
* applied to the input, and volume ramping to smooth out the transition.
|
||||
*/
|
||||
struct MixRampCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Fixed point precision
|
||||
u8 precision;
|
||||
/// Input mix buffer index
|
||||
s16 input_index;
|
||||
/// Output mix buffer index
|
||||
s16 output_index;
|
||||
/// Previous mix volume
|
||||
f32 prev_volume;
|
||||
/// Current mix volume
|
||||
f32 volume;
|
||||
/// Pointer to the previous sample buffer, used for depopping
|
||||
CpuAddr previous_sample;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mix input mix buffer into output mix buffer, with volume applied to the input.
|
||||
* @tparam Q - Number of bits for fixed point operations.
|
||||
* @param output - Output mix buffer.
|
||||
* @param input - Input mix buffer.
|
||||
* @param volume_ - Volume applied to the input.
|
||||
* @param ramp_ - Ramp applied to volume every sample.
|
||||
* @param sample_count - Number of samples to process.
|
||||
* @return The final gained input sample, used for depopping.
|
||||
*/
|
||||
template <size_t Q>
|
||||
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_,
|
||||
u32 sample_count);
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/mix_ramp.h"
|
||||
#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
|
||||
string += "MixRampGroupedCommand";
|
||||
for (u32 i = 0; i < buffer_count; i++) {
|
||||
string += fmt::format("\n\t{}", i);
|
||||
const auto ramp{(volumes[i] - prev_volumes[i]) / static_cast<f32>(processor.sample_count)};
|
||||
string += fmt::format("\n\t\tinput {:02X}", inputs[i]);
|
||||
string += fmt::format("\n\t\toutput {:02X}", outputs[i]);
|
||||
string += fmt::format("\n\t\tvolume {:.8f}", volumes[i]);
|
||||
string += fmt::format("\n\t\tprev_volume {:.8f}", prev_volumes[i]);
|
||||
string += fmt::format("\n\t\tramp {:.8f}", ramp);
|
||||
string += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers};
|
||||
|
||||
for (u32 i = 0; i < buffer_count; i++) {
|
||||
auto last_sample{0};
|
||||
if (prev_volumes[i] != 0.0f || volumes[i] != 0.0f) {
|
||||
const auto output{processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
const auto input{processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
const auto ramp{(volumes[i] - prev_volumes[i]) /
|
||||
static_cast<f32>(processor.sample_count)};
|
||||
|
||||
if (prev_volumes[i] == 0.0f && ramp == 0.0f) {
|
||||
prev_samples[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (precision) {
|
||||
case 15:
|
||||
last_sample =
|
||||
ApplyMixRamp<15>(output, input, prev_volumes[i], ramp, processor.sample_count);
|
||||
break;
|
||||
case 23:
|
||||
last_sample =
|
||||
ApplyMixRamp<23>(output, input, prev_volumes[i], ramp, processor.sample_count);
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
prev_samples[i] = last_sample;
|
||||
}
|
||||
}
|
||||
|
||||
bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/mix_ramp.h"
|
||||
#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
|
||||
string += "MixRampGroupedCommand";
|
||||
for (u32 i = 0; i < buffer_count; i++) {
|
||||
string += fmt::format("\n\t{}", i);
|
||||
const auto ramp{(volumes[i] - prev_volumes[i]) / static_cast<f32>(processor.sample_count)};
|
||||
string += fmt::format("\n\t\tinput {:02X}", inputs[i]);
|
||||
string += fmt::format("\n\t\toutput {:02X}", outputs[i]);
|
||||
string += fmt::format("\n\t\tvolume {:.8f}", volumes[i]);
|
||||
string += fmt::format("\n\t\tprev_volume {:.8f}", prev_volumes[i]);
|
||||
string += fmt::format("\n\t\tramp {:.8f}", ramp);
|
||||
string += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers};
|
||||
|
||||
for (u32 i = 0; i < buffer_count; i++) {
|
||||
auto last_sample{0};
|
||||
if (prev_volumes[i] != 0.0f || volumes[i] != 0.0f) {
|
||||
const auto output{processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
const auto input{processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
const auto ramp{(volumes[i] - prev_volumes[i]) /
|
||||
static_cast<f32>(processor.sample_count)};
|
||||
|
||||
if (prev_volumes[i] == 0.0f && ramp == 0.0f) {
|
||||
prev_samples[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (precision) {
|
||||
case 15:
|
||||
last_sample =
|
||||
ApplyMixRamp<15>(output, input, prev_volumes[i], ramp, processor.sample_count);
|
||||
break;
|
||||
case 23:
|
||||
last_sample =
|
||||
ApplyMixRamp<23>(output, input, prev_volumes[i], ramp, processor.sample_count);
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
prev_samples[i] = last_sample;
|
||||
}
|
||||
}
|
||||
|
||||
bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with
|
||||
* a volume applied to the input, and volume ramping to smooth out the transition.
|
||||
*/
|
||||
struct MixRampGroupedCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Fixed point precision
|
||||
u8 precision;
|
||||
/// Number of mix buffers to mix
|
||||
u32 buffer_count;
|
||||
/// Input mix buffer indexes for each mix buffer
|
||||
std::array<s16, MaxMixBuffers> inputs;
|
||||
/// Output mix buffer indexes for each mix buffer
|
||||
std::array<s16, MaxMixBuffers> outputs;
|
||||
/// Previous mix volumes for each mix buffer
|
||||
std::array<f32, MaxMixBuffers> prev_volumes;
|
||||
/// Current mix volumes for each mix buffer
|
||||
std::array<f32, MaxMixBuffers> volumes;
|
||||
/// Pointer to the previous sample buffer, used for depop
|
||||
CpuAddr previous_samples;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with
|
||||
* a volume applied to the input, and volume ramping to smooth out the transition.
|
||||
*/
|
||||
struct MixRampGroupedCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Fixed point precision
|
||||
u8 precision;
|
||||
/// Number of mix buffers to mix
|
||||
u32 buffer_count;
|
||||
/// Input mix buffer indexes for each mix buffer
|
||||
std::array<s16, MaxMixBuffers> inputs;
|
||||
/// Output mix buffer indexes for each mix buffer
|
||||
std::array<s16, MaxMixBuffers> outputs;
|
||||
/// Previous mix volumes for each mix buffer
|
||||
std::array<f32, MaxMixBuffers> prev_volumes;
|
||||
/// Current mix volumes for each mix buffer
|
||||
std::array<f32, MaxMixBuffers> volumes;
|
||||
/// Pointer to the previous sample buffer, used for depop
|
||||
CpuAddr previous_samples;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/volume.h"
|
||||
#include "common/fixed_point.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Apply volume to the input mix buffer, saving to the output buffer.
|
||||
*
|
||||
* @tparam Q - Number of bits for fixed point operations.
|
||||
* @param output - Output mix buffer.
|
||||
* @param input - Input mix buffer.
|
||||
* @param volume - Volume applied to the input.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t Q>
|
||||
static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input, const f32 volume,
|
||||
const u32 sample_count) {
|
||||
if (volume == 1.0f) {
|
||||
std::memcpy(output.data(), input.data(), input.size_bytes());
|
||||
} else {
|
||||
const Common::FixedPoint<64 - Q, Q> gain{volume};
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
output[i] = (input[i] * gain).to_int();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("VolumeCommand");
|
||||
string += fmt::format("\n\tinput {:02X}", input_index);
|
||||
string += fmt::format("\n\toutput {:02X}", output_index);
|
||||
string += fmt::format("\n\tvolume {:.8f}", volume);
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
// If input and output buffers are the same, and the volume is 1.0f, this won't do
|
||||
// anything, so just skip.
|
||||
if (input_index == output_index && volume == 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
switch (precision) {
|
||||
case 15:
|
||||
ApplyUniformGain<15>(output, input, volume, processor.sample_count);
|
||||
break;
|
||||
|
||||
case 23:
|
||||
ApplyUniformGain<23>(output, input, volume, processor.sample_count);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/volume.h"
|
||||
#include "common/fixed_point.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Apply volume to the input mix buffer, saving to the output buffer.
|
||||
*
|
||||
* @tparam Q - Number of bits for fixed point operations.
|
||||
* @param output - Output mix buffer.
|
||||
* @param input - Input mix buffer.
|
||||
* @param volume - Volume applied to the input.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t Q>
|
||||
static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input, const f32 volume,
|
||||
const u32 sample_count) {
|
||||
if (volume == 1.0f) {
|
||||
std::memcpy(output.data(), input.data(), input.size_bytes());
|
||||
} else {
|
||||
const Common::FixedPoint<64 - Q, Q> gain{volume};
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
output[i] = (input[i] * gain).to_int();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("VolumeCommand");
|
||||
string += fmt::format("\n\tinput {:02X}", input_index);
|
||||
string += fmt::format("\n\toutput {:02X}", output_index);
|
||||
string += fmt::format("\n\tvolume {:.8f}", volume);
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
// If input and output buffers are the same, and the volume is 1.0f, this won't do
|
||||
// anything, so just skip.
|
||||
if (input_index == output_index && volume == 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
switch (precision) {
|
||||
case 15:
|
||||
ApplyUniformGain<15>(output, input, volume, processor.sample_count);
|
||||
break;
|
||||
|
||||
case 23:
|
||||
ApplyUniformGain<23>(output, input, volume, processor.sample_count);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for applying volume to a mix buffer.
|
||||
*/
|
||||
struct VolumeCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Fixed point precision
|
||||
u8 precision;
|
||||
/// Input mix buffer index
|
||||
s16 input_index;
|
||||
/// Output mix buffer index
|
||||
s16 output_index;
|
||||
/// Mix volume applied to the input
|
||||
f32 volume;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for applying volume to a mix buffer.
|
||||
*/
|
||||
struct VolumeCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Fixed point precision
|
||||
u8 precision;
|
||||
/// Input mix buffer index
|
||||
s16 input_index;
|
||||
/// Output mix buffer index
|
||||
s16 output_index;
|
||||
/// Mix volume applied to the input
|
||||
f32 volume;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,84 +1,84 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/volume_ramp.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Apply volume with ramping to the input mix buffer, saving to the output buffer.
|
||||
*
|
||||
* @tparam Q - Number of bits for fixed point operations.
|
||||
* @param output - Output mix buffers.
|
||||
* @param input - Input mix buffers.
|
||||
* @param volume - Volume applied to the input.
|
||||
* @param ramp - Ramp applied to volume every sample.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t Q>
|
||||
static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32> input,
|
||||
const f32 volume, const f32 ramp_, const u32 sample_count) {
|
||||
if (volume == 0.0f && ramp_ == 0.0f) {
|
||||
std::memset(output.data(), 0, output.size_bytes());
|
||||
} else if (volume == 1.0f && ramp_ == 0.0f) {
|
||||
std::memcpy(output.data(), input.data(), output.size_bytes());
|
||||
} else if (ramp_ == 0.0f) {
|
||||
const Common::FixedPoint<64 - Q, Q> gain{volume};
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
output[i] = (input[i] * gain).to_int();
|
||||
}
|
||||
} else {
|
||||
Common::FixedPoint<64 - Q, Q> gain{volume};
|
||||
const Common::FixedPoint<64 - Q, Q> ramp{ramp_};
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
output[i] = (input[i] * gain).to_int();
|
||||
gain += ramp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
|
||||
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
|
||||
string += fmt::format("VolumeRampCommand");
|
||||
string += fmt::format("\n\tinput {:02X}", input_index);
|
||||
string += fmt::format("\n\toutput {:02X}", output_index);
|
||||
string += fmt::format("\n\tvolume {:.8f}", volume);
|
||||
string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
|
||||
string += fmt::format("\n\tramp {:.8f}", ramp);
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
|
||||
|
||||
// If input and output buffers are the same, and the volume is 1.0f, and there's no ramping,
|
||||
// this won't do anything, so just skip.
|
||||
if (input_index == output_index && prev_volume == 1.0f && ramp == 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (precision) {
|
||||
case 15:
|
||||
ApplyLinearEnvelopeGain<15>(output, input, prev_volume, ramp, processor.sample_count);
|
||||
break;
|
||||
|
||||
case 23:
|
||||
ApplyLinearEnvelopeGain<23>(output, input, prev_volume, ramp, processor.sample_count);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/volume_ramp.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Apply volume with ramping to the input mix buffer, saving to the output buffer.
|
||||
*
|
||||
* @tparam Q - Number of bits for fixed point operations.
|
||||
* @param output - Output mix buffers.
|
||||
* @param input - Input mix buffers.
|
||||
* @param volume - Volume applied to the input.
|
||||
* @param ramp - Ramp applied to volume every sample.
|
||||
* @param sample_count - Number of samples to process.
|
||||
*/
|
||||
template <size_t Q>
|
||||
static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32> input,
|
||||
const f32 volume, const f32 ramp_, const u32 sample_count) {
|
||||
if (volume == 0.0f && ramp_ == 0.0f) {
|
||||
std::memset(output.data(), 0, output.size_bytes());
|
||||
} else if (volume == 1.0f && ramp_ == 0.0f) {
|
||||
std::memcpy(output.data(), input.data(), output.size_bytes());
|
||||
} else if (ramp_ == 0.0f) {
|
||||
const Common::FixedPoint<64 - Q, Q> gain{volume};
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
output[i] = (input[i] * gain).to_int();
|
||||
}
|
||||
} else {
|
||||
Common::FixedPoint<64 - Q, Q> gain{volume};
|
||||
const Common::FixedPoint<64 - Q, Q> ramp{ramp_};
|
||||
for (u32 i = 0; i < sample_count; i++) {
|
||||
output[i] = (input[i] * gain).to_int();
|
||||
gain += ramp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
|
||||
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
|
||||
string += fmt::format("VolumeRampCommand");
|
||||
string += fmt::format("\n\tinput {:02X}", input_index);
|
||||
string += fmt::format("\n\toutput {:02X}", output_index);
|
||||
string += fmt::format("\n\tvolume {:.8f}", volume);
|
||||
string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
|
||||
string += fmt::format("\n\tramp {:.8f}", ramp);
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
|
||||
|
||||
// If input and output buffers are the same, and the volume is 1.0f, and there's no ramping,
|
||||
// this won't do anything, so just skip.
|
||||
if (input_index == output_index && prev_volume == 1.0f && ramp == 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (precision) {
|
||||
case 15:
|
||||
ApplyLinearEnvelopeGain<15>(output, input, prev_volume, ramp, processor.sample_count);
|
||||
break;
|
||||
|
||||
case 23:
|
||||
ApplyLinearEnvelopeGain<23>(output, input, prev_volume, ramp, processor.sample_count);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth
|
||||
* out the transition.
|
||||
*/
|
||||
struct VolumeRampCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Fixed point precision
|
||||
u8 precision;
|
||||
/// Input mix buffer index
|
||||
s16 input_index;
|
||||
/// Output mix buffer index
|
||||
s16 output_index;
|
||||
/// Previous mix volume applied to the input
|
||||
f32 prev_volume;
|
||||
/// Current mix volume applied to the input
|
||||
f32 volume;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth
|
||||
* out the transition.
|
||||
*/
|
||||
struct VolumeRampCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Fixed point precision
|
||||
u8 precision;
|
||||
/// Input mix buffer index
|
||||
s16 input_index;
|
||||
/// Output mix buffer index
|
||||
s16 output_index;
|
||||
/// Previous mix volume applied to the input
|
||||
f32 prev_volume;
|
||||
/// Current mix volume applied to the input
|
||||
f32 volume;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/performance/performance.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state));
|
||||
}
|
||||
|
||||
void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto base{entry_address.translated_address};
|
||||
if (state == PerformanceState::Start) {
|
||||
auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
|
||||
*start_time_ptr = static_cast<u32>(
|
||||
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
|
||||
processor.start_time - processor.current_processing_time)
|
||||
.count());
|
||||
} else if (state == PerformanceState::Stop) {
|
||||
auto processed_time_ptr{
|
||||
reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
|
||||
auto entry_count_ptr{
|
||||
reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
|
||||
|
||||
*processed_time_ptr = static_cast<u32>(
|
||||
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
|
||||
processor.start_time - processor.current_processing_time)
|
||||
.count());
|
||||
(*entry_count_ptr)++;
|
||||
}
|
||||
}
|
||||
|
||||
bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/performance/performance.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state));
|
||||
}
|
||||
|
||||
void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto base{entry_address.translated_address};
|
||||
if (state == PerformanceState::Start) {
|
||||
auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
|
||||
*start_time_ptr = static_cast<u32>(
|
||||
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
|
||||
processor.start_time - processor.current_processing_time)
|
||||
.count());
|
||||
} else if (state == PerformanceState::Stop) {
|
||||
auto processed_time_ptr{
|
||||
reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
|
||||
auto entry_count_ptr{
|
||||
reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
|
||||
|
||||
*processed_time_ptr = static_cast<u32>(
|
||||
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
|
||||
processor.start_time - processor.current_processing_time)
|
||||
.count());
|
||||
(*entry_count_ptr)++;
|
||||
}
|
||||
}
|
||||
|
||||
bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/performance/performance_entry_addresses.h"
|
||||
#include "audio_core/renderer/performance/performance_manager.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule.
|
||||
*/
|
||||
struct PerformanceCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// State of the performance
|
||||
PerformanceState state;
|
||||
/// Pointers to be written
|
||||
PerformanceEntryAddresses entry_address;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "audio_core/renderer/performance/performance_entry_addresses.h"
|
||||
#include "audio_core/renderer/performance/performance_manager.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule.
|
||||
*/
|
||||
struct PerformanceCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// State of the performance
|
||||
PerformanceState state;
|
||||
/// Pointers to be written
|
||||
PerformanceEntryAddresses entry_address;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,74 +1,74 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("DownMix6chTo2chCommand\n\tinputs: ");
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto in_front_left{
|
||||
processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)};
|
||||
auto in_front_right{
|
||||
processor.mix_buffers.subspan(inputs[1] * processor.sample_count, processor.sample_count)};
|
||||
auto in_center{
|
||||
processor.mix_buffers.subspan(inputs[2] * processor.sample_count, processor.sample_count)};
|
||||
auto in_lfe{
|
||||
processor.mix_buffers.subspan(inputs[3] * processor.sample_count, processor.sample_count)};
|
||||
auto in_back_left{
|
||||
processor.mix_buffers.subspan(inputs[4] * processor.sample_count, processor.sample_count)};
|
||||
auto in_back_right{
|
||||
processor.mix_buffers.subspan(inputs[5] * processor.sample_count, processor.sample_count)};
|
||||
|
||||
auto out_front_left{
|
||||
processor.mix_buffers.subspan(outputs[0] * processor.sample_count, processor.sample_count)};
|
||||
auto out_front_right{
|
||||
processor.mix_buffers.subspan(outputs[1] * processor.sample_count, processor.sample_count)};
|
||||
auto out_center{
|
||||
processor.mix_buffers.subspan(outputs[2] * processor.sample_count, processor.sample_count)};
|
||||
auto out_lfe{
|
||||
processor.mix_buffers.subspan(outputs[3] * processor.sample_count, processor.sample_count)};
|
||||
auto out_back_left{
|
||||
processor.mix_buffers.subspan(outputs[4] * processor.sample_count, processor.sample_count)};
|
||||
auto out_back_right{
|
||||
processor.mix_buffers.subspan(outputs[5] * processor.sample_count, processor.sample_count)};
|
||||
|
||||
for (u32 i = 0; i < processor.sample_count; i++) {
|
||||
const auto left_sample{(in_front_left[i] * down_mix_coeff[0] +
|
||||
in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
|
||||
in_back_left[i] * down_mix_coeff[3])
|
||||
.to_int()};
|
||||
|
||||
const auto right_sample{(in_front_right[i] * down_mix_coeff[0] +
|
||||
in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
|
||||
in_back_right[i] * down_mix_coeff[3])
|
||||
.to_int()};
|
||||
|
||||
out_front_left[i] = left_sample;
|
||||
out_front_right[i] = right_sample;
|
||||
}
|
||||
|
||||
std::memset(out_center.data(), 0, out_center.size_bytes());
|
||||
std::memset(out_lfe.data(), 0, out_lfe.size_bytes());
|
||||
std::memset(out_back_left.data(), 0, out_back_left.size_bytes());
|
||||
std::memset(out_back_right.data(), 0, out_back_right.size_bytes());
|
||||
}
|
||||
|
||||
bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("DownMix6chTo2chCommand\n\tinputs: ");
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n\toutputs: ";
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
string += fmt::format("{:02X}, ", outputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto in_front_left{
|
||||
processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)};
|
||||
auto in_front_right{
|
||||
processor.mix_buffers.subspan(inputs[1] * processor.sample_count, processor.sample_count)};
|
||||
auto in_center{
|
||||
processor.mix_buffers.subspan(inputs[2] * processor.sample_count, processor.sample_count)};
|
||||
auto in_lfe{
|
||||
processor.mix_buffers.subspan(inputs[3] * processor.sample_count, processor.sample_count)};
|
||||
auto in_back_left{
|
||||
processor.mix_buffers.subspan(inputs[4] * processor.sample_count, processor.sample_count)};
|
||||
auto in_back_right{
|
||||
processor.mix_buffers.subspan(inputs[5] * processor.sample_count, processor.sample_count)};
|
||||
|
||||
auto out_front_left{
|
||||
processor.mix_buffers.subspan(outputs[0] * processor.sample_count, processor.sample_count)};
|
||||
auto out_front_right{
|
||||
processor.mix_buffers.subspan(outputs[1] * processor.sample_count, processor.sample_count)};
|
||||
auto out_center{
|
||||
processor.mix_buffers.subspan(outputs[2] * processor.sample_count, processor.sample_count)};
|
||||
auto out_lfe{
|
||||
processor.mix_buffers.subspan(outputs[3] * processor.sample_count, processor.sample_count)};
|
||||
auto out_back_left{
|
||||
processor.mix_buffers.subspan(outputs[4] * processor.sample_count, processor.sample_count)};
|
||||
auto out_back_right{
|
||||
processor.mix_buffers.subspan(outputs[5] * processor.sample_count, processor.sample_count)};
|
||||
|
||||
for (u32 i = 0; i < processor.sample_count; i++) {
|
||||
const auto left_sample{(in_front_left[i] * down_mix_coeff[0] +
|
||||
in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
|
||||
in_back_left[i] * down_mix_coeff[3])
|
||||
.to_int()};
|
||||
|
||||
const auto right_sample{(in_front_right[i] * down_mix_coeff[0] +
|
||||
in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
|
||||
in_back_right[i] * down_mix_coeff[3])
|
||||
.to_int()};
|
||||
|
||||
out_front_left[i] = left_sample;
|
||||
out_front_right[i] = right_sample;
|
||||
}
|
||||
|
||||
std::memset(out_center.data(), 0, out_center.size_bytes());
|
||||
std::memset(out_lfe.data(), 0, out_lfe.size_bytes());
|
||||
std::memset(out_back_left.data(), 0, out_back_left.size_bytes());
|
||||
std::memset(out_back_right.data(), 0, out_back_right.size_bytes());
|
||||
}
|
||||
|
||||
bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for downmixing 6 channels to 2.
|
||||
* Channel layout (SMPTE):
|
||||
* 0 - front left
|
||||
* 1 - front right
|
||||
* 2 - center
|
||||
* 3 - lfe
|
||||
* 4 - back left
|
||||
* 5 - back right
|
||||
*/
|
||||
struct DownMix6chTo2chCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Coefficients used for downmixing
|
||||
std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for downmixing 6 channels to 2.
|
||||
* Channel layout (SMPTE):
|
||||
* 0 - front left
|
||||
* 1 - front right
|
||||
* 2 - center
|
||||
* 3 - lfe
|
||||
* 4 - back left
|
||||
* 5 - back right
|
||||
*/
|
||||
struct DownMix6chTo2chCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Input mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Output mix buffer offsets for each channel
|
||||
std::array<s16, MaxChannels> outputs;
|
||||
/// Coefficients used for downmixing
|
||||
std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +1,29 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Resample an input buffer into an output buffer, according to the sample_rate_ratio.
|
||||
*
|
||||
* @param output - Output buffer.
|
||||
* @param input - Input buffer.
|
||||
* @param sample_rate_ratio - Ratio for resampling.
|
||||
e.g 32000/48000 = 0.666 input samples read per output.
|
||||
* @param fraction - Current read fraction, written to and should be passed back in for
|
||||
* multiple calls.
|
||||
* @param samples_to_write - Number of samples to write.
|
||||
* @param src_quality - Resampling quality.
|
||||
*/
|
||||
void Resample(std::span<s32> output, std::span<const s16> input,
|
||||
const Common::FixedPoint<49, 15>& sample_rate_ratio,
|
||||
Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality);
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Resample an input buffer into an output buffer, according to the sample_rate_ratio.
|
||||
*
|
||||
* @param output - Output buffer.
|
||||
* @param input - Input buffer.
|
||||
* @param sample_rate_ratio - Ratio for resampling.
|
||||
e.g 32000/48000 = 0.666 input samples read per output.
|
||||
* @param fraction - Current read fraction, written to and should be passed back in for
|
||||
* multiple calls.
|
||||
* @param samples_to_write - Number of samples to write.
|
||||
* @param src_quality - Resampling quality.
|
||||
*/
|
||||
void Resample(std::span<s32> output, std::span<const s16> input,
|
||||
const Common::FixedPoint<49, 15>& sample_rate_ratio,
|
||||
Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality);
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,262 +1,262 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/resample/upsample.h"
|
||||
#include "audio_core/renderer/upsampler/upsampler_info.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Upsampling impl. Input must be 8K, 16K or 32K, output is 48K.
|
||||
*
|
||||
* @param output - Output buffer.
|
||||
* @param input - Input buffer.
|
||||
* @param target_sample_count - Number of samples for output.
|
||||
* @param state - Upsampler state, updated each call.
|
||||
*/
|
||||
static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
|
||||
const u32 target_sample_count, const u32 source_sample_count,
|
||||
UpsamplerState* state) {
|
||||
constexpr u32 WindowSize = 10;
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{
|
||||
51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f,
|
||||
-1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{
|
||||
105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f,
|
||||
-1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{
|
||||
122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f,
|
||||
-1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{
|
||||
23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f,
|
||||
-0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{
|
||||
80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f,
|
||||
-1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f,
|
||||
};
|
||||
|
||||
if (!state->initialized) {
|
||||
switch (source_sample_count) {
|
||||
case 40:
|
||||
state->window_size = WindowSize;
|
||||
state->ratio = 6.0f;
|
||||
state->history.fill(0);
|
||||
break;
|
||||
|
||||
case 80:
|
||||
state->window_size = WindowSize;
|
||||
state->ratio = 3.0f;
|
||||
state->history.fill(0);
|
||||
break;
|
||||
|
||||
case 160:
|
||||
state->window_size = WindowSize;
|
||||
state->ratio = 1.5f;
|
||||
state->history.fill(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid upsampling source count {}!", source_sample_count);
|
||||
// This continues anyway, but let's assume 160 for sanity
|
||||
state->window_size = WindowSize;
|
||||
state->ratio = 1.5f;
|
||||
state->history.fill(0);
|
||||
break;
|
||||
}
|
||||
|
||||
state->history_input_index = 0;
|
||||
state->history_output_index = 9;
|
||||
state->history_start_index = 0;
|
||||
state->history_end_index = UpsamplerState::HistorySize - 1;
|
||||
state->initialized = true;
|
||||
}
|
||||
|
||||
if (target_sample_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
u32 read_index{0};
|
||||
|
||||
auto increment = [&]() -> void {
|
||||
state->history[state->history_input_index] = input[read_index++];
|
||||
state->history_input_index =
|
||||
static_cast<u16>((state->history_input_index + 1) % UpsamplerState::HistorySize);
|
||||
state->history_output_index =
|
||||
static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
|
||||
};
|
||||
|
||||
auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1,
|
||||
std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 {
|
||||
auto output_index{state->history_output_index};
|
||||
auto start_pos{output_index - state->history_start_index + 1U};
|
||||
auto end_pos{10U};
|
||||
|
||||
if (start_pos < 10) {
|
||||
end_pos = start_pos;
|
||||
}
|
||||
|
||||
u64 prev_contrib{0};
|
||||
u32 coeff_index{0};
|
||||
for (; coeff_index < end_pos; coeff_index++, output_index--) {
|
||||
prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
|
||||
coeffs1[coeff_index].to_raw();
|
||||
}
|
||||
|
||||
auto end_index{state->history_end_index};
|
||||
for (; start_pos < 9; start_pos++, coeff_index++, end_index--) {
|
||||
prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) *
|
||||
coeffs1[coeff_index].to_raw();
|
||||
}
|
||||
|
||||
output_index =
|
||||
static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
|
||||
start_pos = state->history_end_index - output_index + 1U;
|
||||
end_pos = 10U;
|
||||
|
||||
if (start_pos < 10) {
|
||||
end_pos = start_pos;
|
||||
}
|
||||
|
||||
u64 next_contrib{0};
|
||||
coeff_index = 0;
|
||||
for (; coeff_index < end_pos; coeff_index++, output_index++) {
|
||||
next_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
|
||||
coeffs2[coeff_index].to_raw();
|
||||
}
|
||||
|
||||
auto start_index{state->history_start_index};
|
||||
for (; start_pos < 9; start_pos++, start_index++, coeff_index++) {
|
||||
next_contrib += static_cast<u64>(state->history[start_index].to_raw()) *
|
||||
coeffs2[coeff_index].to_raw();
|
||||
}
|
||||
|
||||
return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8);
|
||||
};
|
||||
|
||||
switch (state->ratio.to_int_floor()) {
|
||||
// 40 -> 240
|
||||
case 6:
|
||||
for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
|
||||
switch (state->sample_index) {
|
||||
case 0:
|
||||
increment();
|
||||
output[write_index] = state->history[state->history_output_index].to_int_floor();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
output[write_index] = calculate_sample(SincWindow3, SincWindow4);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
output[write_index] = calculate_sample(SincWindow5, SincWindow5);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
output[write_index] = calculate_sample(SincWindow4, SincWindow3);
|
||||
break;
|
||||
}
|
||||
state->sample_index = static_cast<u8>((state->sample_index + 1) % 6);
|
||||
}
|
||||
break;
|
||||
|
||||
// 80 -> 240
|
||||
case 3:
|
||||
for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
|
||||
switch (state->sample_index) {
|
||||
case 0:
|
||||
increment();
|
||||
output[write_index] = state->history[state->history_output_index].to_int_floor();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
|
||||
break;
|
||||
}
|
||||
state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
|
||||
}
|
||||
break;
|
||||
|
||||
// 160 -> 240
|
||||
default:
|
||||
for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
|
||||
switch (state->sample_index) {
|
||||
case 0:
|
||||
increment();
|
||||
output[write_index] = state->history[state->history_output_index].to_int_floor();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
increment();
|
||||
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
|
||||
break;
|
||||
}
|
||||
state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) -> void {
|
||||
string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}",
|
||||
source_sample_count, source_sample_rate);
|
||||
const auto upsampler{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
|
||||
if (upsampler != nullptr) {
|
||||
string += fmt::format("\n\tUpsampler\n\t\tenabled {} sample count {}\n\tinputs: ",
|
||||
upsampler->enabled, upsampler->sample_count);
|
||||
for (u32 i = 0; i < upsampler->input_count; i++) {
|
||||
string += fmt::format("{:02X}, ", upsampler->inputs[i]);
|
||||
}
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
|
||||
const auto input_count{std::min(info->input_count, buffer_count)};
|
||||
const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count};
|
||||
|
||||
for (u32 i = 0; i < input_count; i++) {
|
||||
const auto channel{inputs_[i]};
|
||||
|
||||
if (channel >= 0 && channel < static_cast<s16>(processor.buffer_count)) {
|
||||
auto state{&info->states[i]};
|
||||
std::span<s32> output{
|
||||
reinterpret_cast<s32*>(samples_buffer + info->sample_count * channel * sizeof(s32)),
|
||||
info->sample_count};
|
||||
auto input{processor.mix_buffers.subspan(channel * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
SrcProcessFrame(output, input, info->sample_count, source_sample_count, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/resample/upsample.h"
|
||||
#include "audio_core/renderer/upsampler/upsampler_info.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Upsampling impl. Input must be 8K, 16K or 32K, output is 48K.
|
||||
*
|
||||
* @param output - Output buffer.
|
||||
* @param input - Input buffer.
|
||||
* @param target_sample_count - Number of samples for output.
|
||||
* @param state - Upsampler state, updated each call.
|
||||
*/
|
||||
static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
|
||||
const u32 target_sample_count, const u32 source_sample_count,
|
||||
UpsamplerState* state) {
|
||||
constexpr u32 WindowSize = 10;
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{
|
||||
51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f,
|
||||
-1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{
|
||||
105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f,
|
||||
-1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{
|
||||
122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f,
|
||||
-1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{
|
||||
23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f,
|
||||
-0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{
|
||||
80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f,
|
||||
-1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f,
|
||||
};
|
||||
|
||||
if (!state->initialized) {
|
||||
switch (source_sample_count) {
|
||||
case 40:
|
||||
state->window_size = WindowSize;
|
||||
state->ratio = 6.0f;
|
||||
state->history.fill(0);
|
||||
break;
|
||||
|
||||
case 80:
|
||||
state->window_size = WindowSize;
|
||||
state->ratio = 3.0f;
|
||||
state->history.fill(0);
|
||||
break;
|
||||
|
||||
case 160:
|
||||
state->window_size = WindowSize;
|
||||
state->ratio = 1.5f;
|
||||
state->history.fill(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid upsampling source count {}!", source_sample_count);
|
||||
// This continues anyway, but let's assume 160 for sanity
|
||||
state->window_size = WindowSize;
|
||||
state->ratio = 1.5f;
|
||||
state->history.fill(0);
|
||||
break;
|
||||
}
|
||||
|
||||
state->history_input_index = 0;
|
||||
state->history_output_index = 9;
|
||||
state->history_start_index = 0;
|
||||
state->history_end_index = UpsamplerState::HistorySize - 1;
|
||||
state->initialized = true;
|
||||
}
|
||||
|
||||
if (target_sample_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
u32 read_index{0};
|
||||
|
||||
auto increment = [&]() -> void {
|
||||
state->history[state->history_input_index] = input[read_index++];
|
||||
state->history_input_index =
|
||||
static_cast<u16>((state->history_input_index + 1) % UpsamplerState::HistorySize);
|
||||
state->history_output_index =
|
||||
static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
|
||||
};
|
||||
|
||||
auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1,
|
||||
std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 {
|
||||
auto output_index{state->history_output_index};
|
||||
auto start_pos{output_index - state->history_start_index + 1U};
|
||||
auto end_pos{10U};
|
||||
|
||||
if (start_pos < 10) {
|
||||
end_pos = start_pos;
|
||||
}
|
||||
|
||||
u64 prev_contrib{0};
|
||||
u32 coeff_index{0};
|
||||
for (; coeff_index < end_pos; coeff_index++, output_index--) {
|
||||
prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
|
||||
coeffs1[coeff_index].to_raw();
|
||||
}
|
||||
|
||||
auto end_index{state->history_end_index};
|
||||
for (; start_pos < 9; start_pos++, coeff_index++, end_index--) {
|
||||
prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) *
|
||||
coeffs1[coeff_index].to_raw();
|
||||
}
|
||||
|
||||
output_index =
|
||||
static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
|
||||
start_pos = state->history_end_index - output_index + 1U;
|
||||
end_pos = 10U;
|
||||
|
||||
if (start_pos < 10) {
|
||||
end_pos = start_pos;
|
||||
}
|
||||
|
||||
u64 next_contrib{0};
|
||||
coeff_index = 0;
|
||||
for (; coeff_index < end_pos; coeff_index++, output_index++) {
|
||||
next_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
|
||||
coeffs2[coeff_index].to_raw();
|
||||
}
|
||||
|
||||
auto start_index{state->history_start_index};
|
||||
for (; start_pos < 9; start_pos++, start_index++, coeff_index++) {
|
||||
next_contrib += static_cast<u64>(state->history[start_index].to_raw()) *
|
||||
coeffs2[coeff_index].to_raw();
|
||||
}
|
||||
|
||||
return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8);
|
||||
};
|
||||
|
||||
switch (state->ratio.to_int_floor()) {
|
||||
// 40 -> 240
|
||||
case 6:
|
||||
for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
|
||||
switch (state->sample_index) {
|
||||
case 0:
|
||||
increment();
|
||||
output[write_index] = state->history[state->history_output_index].to_int_floor();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
output[write_index] = calculate_sample(SincWindow3, SincWindow4);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
output[write_index] = calculate_sample(SincWindow5, SincWindow5);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
output[write_index] = calculate_sample(SincWindow4, SincWindow3);
|
||||
break;
|
||||
}
|
||||
state->sample_index = static_cast<u8>((state->sample_index + 1) % 6);
|
||||
}
|
||||
break;
|
||||
|
||||
// 80 -> 240
|
||||
case 3:
|
||||
for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
|
||||
switch (state->sample_index) {
|
||||
case 0:
|
||||
increment();
|
||||
output[write_index] = state->history[state->history_output_index].to_int_floor();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
|
||||
break;
|
||||
}
|
||||
state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
|
||||
}
|
||||
break;
|
||||
|
||||
// 160 -> 240
|
||||
default:
|
||||
for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
|
||||
switch (state->sample_index) {
|
||||
case 0:
|
||||
increment();
|
||||
output[write_index] = state->history[state->history_output_index].to_int_floor();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
increment();
|
||||
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
|
||||
break;
|
||||
}
|
||||
state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) -> void {
|
||||
string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}",
|
||||
source_sample_count, source_sample_rate);
|
||||
const auto upsampler{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
|
||||
if (upsampler != nullptr) {
|
||||
string += fmt::format("\n\tUpsampler\n\t\tenabled {} sample count {}\n\tinputs: ",
|
||||
upsampler->enabled, upsampler->sample_count);
|
||||
for (u32 i = 0; i < upsampler->input_count; i++) {
|
||||
string += fmt::format("{:02X}, ", upsampler->inputs[i]);
|
||||
}
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
|
||||
const auto input_count{std::min(info->input_count, buffer_count)};
|
||||
const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count};
|
||||
|
||||
for (u32 i = 0; i < input_count; i++) {
|
||||
const auto channel{inputs_[i]};
|
||||
|
||||
if (channel >= 0 && channel < static_cast<s16>(processor.buffer_count)) {
|
||||
auto state{&info->states[i]};
|
||||
std::span<s32> output{
|
||||
reinterpret_cast<s32*>(samples_buffer + info->sample_count * channel * sizeof(s32)),
|
||||
info->sample_count};
|
||||
auto input{processor.mix_buffers.subspan(channel * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
SrcProcessFrame(output, input, info->sample_count, source_sample_count, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for upsampling a mix buffer to 48Khz.
|
||||
* Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz.
|
||||
*/
|
||||
struct UpsampleCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Pointer to the output samples buffer.
|
||||
CpuAddr samples_buffer;
|
||||
/// Pointer to input mix buffer indexes.
|
||||
CpuAddr inputs;
|
||||
/// Number of input mix buffers.
|
||||
u32 buffer_count;
|
||||
/// Unknown, unused.
|
||||
u32 unk_20;
|
||||
/// Source data sample count.
|
||||
u32 source_sample_count;
|
||||
/// Source data sample rate.
|
||||
u32 source_sample_rate;
|
||||
/// Pointer to the upsampler info for this command.
|
||||
CpuAddr upsampler_info;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for upsampling a mix buffer to 48Khz.
|
||||
* Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz.
|
||||
*/
|
||||
struct UpsampleCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Pointer to the output samples buffer.
|
||||
CpuAddr samples_buffer;
|
||||
/// Pointer to input mix buffer indexes.
|
||||
CpuAddr inputs;
|
||||
/// Number of input mix buffers.
|
||||
u32 buffer_count;
|
||||
/// Unknown, unused.
|
||||
u32 unk_20;
|
||||
/// Source data sample count.
|
||||
u32 source_sample_count;
|
||||
/// Source data sample rate.
|
||||
u32 source_sample_rate;
|
||||
/// Pointer to the upsampler info for this command.
|
||||
CpuAddr upsampler_info;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/sink/circular_buffer.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format(
|
||||
"CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ",
|
||||
input_count, size, pos);
|
||||
for (u32 i = 0; i < input_count; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
constexpr s32 min{std::numeric_limits<s16>::min()};
|
||||
constexpr s32 max{std::numeric_limits<s16>::max()};
|
||||
|
||||
std::vector<s16> output(processor.sample_count);
|
||||
for (u32 channel = 0; channel < input_count; channel++) {
|
||||
auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
for (u32 sample_index = 0; sample_index < processor.sample_count; sample_index++) {
|
||||
output[sample_index] = static_cast<s16>(std::clamp(input[sample_index], min, max));
|
||||
}
|
||||
|
||||
processor.memory->WriteBlockUnsafe(address + pos, output.data(),
|
||||
output.size() * sizeof(s16));
|
||||
pos += static_cast<u32>(processor.sample_count * sizeof(s16));
|
||||
if (pos >= size) {
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/sink/circular_buffer.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format(
|
||||
"CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ",
|
||||
input_count, size, pos);
|
||||
for (u32 i = 0; i < input_count; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
constexpr s32 min{std::numeric_limits<s16>::min()};
|
||||
constexpr s32 max{std::numeric_limits<s16>::max()};
|
||||
|
||||
std::vector<s16> output(processor.sample_count);
|
||||
for (u32 channel = 0; channel < input_count; channel++) {
|
||||
auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
for (u32 sample_index = 0; sample_index < processor.sample_count; sample_index++) {
|
||||
output[sample_index] = static_cast<s16>(std::clamp(input[sample_index], min, max));
|
||||
}
|
||||
|
||||
processor.memory->WriteBlockUnsafe(address + pos, output.data(),
|
||||
output.size() * sizeof(s16));
|
||||
pos += static_cast<u32>(processor.sample_count * sizeof(s16));
|
||||
if (pos >= size) {
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for sinking samples to a circular buffer.
|
||||
*/
|
||||
struct CircularBufferSinkCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Number of input mix buffers
|
||||
u32 input_count;
|
||||
/// Input mix buffer indexes
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Circular buffer address
|
||||
CpuAddr address;
|
||||
/// Circular buffer size
|
||||
u32 size;
|
||||
/// Current buffer offset
|
||||
u32 pos;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for sinking samples to a circular buffer.
|
||||
*/
|
||||
struct CircularBufferSinkCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Number of input mix buffers
|
||||
u32 input_count;
|
||||
/// Input mix buffer indexes
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
/// Circular buffer address
|
||||
CpuAddr address;
|
||||
/// Circular buffer size
|
||||
u32 size;
|
||||
/// Current buffer offset
|
||||
u32 pos;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/sink/device.h"
|
||||
#include "audio_core/sink/sink.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ",
|
||||
std::string_view(name), session_id, input_count);
|
||||
for (u32 i = 0; i < input_count; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
constexpr s32 min = std::numeric_limits<s16>::min();
|
||||
constexpr s32 max = std::numeric_limits<s16>::max();
|
||||
|
||||
auto stream{processor.GetOutputSinkStream()};
|
||||
stream->SetSystemChannels(input_count);
|
||||
|
||||
Sink::SinkBuffer out_buffer{
|
||||
.frames{TargetSampleCount},
|
||||
.frames_played{0},
|
||||
.tag{0},
|
||||
.consumed{false},
|
||||
};
|
||||
|
||||
std::vector<s16> samples(out_buffer.frames * input_count);
|
||||
|
||||
for (u32 channel = 0; channel < input_count; channel++) {
|
||||
const auto offset{inputs[channel] * out_buffer.frames};
|
||||
|
||||
for (u32 index = 0; index < out_buffer.frames; index++) {
|
||||
samples[index * input_count + channel] =
|
||||
static_cast<s16>(std::clamp(sample_buffer[offset + index], min, max));
|
||||
}
|
||||
}
|
||||
|
||||
out_buffer.tag = reinterpret_cast<u64>(samples.data());
|
||||
stream->AppendBuffer(out_buffer, samples);
|
||||
|
||||
if (stream->IsPaused()) {
|
||||
stream->Start();
|
||||
}
|
||||
}
|
||||
|
||||
bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/sink/device.h"
|
||||
#include "audio_core/sink/sink.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
|
||||
std::string& string) {
|
||||
string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ",
|
||||
std::string_view(name), session_id, input_count);
|
||||
for (u32 i = 0; i < input_count; i++) {
|
||||
string += fmt::format("{:02X}, ", inputs[i]);
|
||||
}
|
||||
string += "\n";
|
||||
}
|
||||
|
||||
void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
constexpr s32 min = std::numeric_limits<s16>::min();
|
||||
constexpr s32 max = std::numeric_limits<s16>::max();
|
||||
|
||||
auto stream{processor.GetOutputSinkStream()};
|
||||
stream->SetSystemChannels(input_count);
|
||||
|
||||
Sink::SinkBuffer out_buffer{
|
||||
.frames{TargetSampleCount},
|
||||
.frames_played{0},
|
||||
.tag{0},
|
||||
.consumed{false},
|
||||
};
|
||||
|
||||
std::vector<s16> samples(out_buffer.frames * input_count);
|
||||
|
||||
for (u32 channel = 0; channel < input_count; channel++) {
|
||||
const auto offset{inputs[channel] * out_buffer.frames};
|
||||
|
||||
for (u32 index = 0; index < out_buffer.frames; index++) {
|
||||
samples[index * input_count + channel] =
|
||||
static_cast<s16>(std::clamp(sample_buffer[offset + index], min, max));
|
||||
}
|
||||
}
|
||||
|
||||
out_buffer.tag = reinterpret_cast<u64>(samples.data());
|
||||
stream->AppendBuffer(out_buffer, samples);
|
||||
|
||||
if (stream->IsPaused()) {
|
||||
stream->Start();
|
||||
}
|
||||
}
|
||||
|
||||
bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for sinking samples to an output device.
|
||||
*/
|
||||
struct DeviceSinkCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Device name
|
||||
char name[0x100];
|
||||
/// System session id (unused)
|
||||
s32 session_id;
|
||||
/// Sample buffer to sink
|
||||
std::span<s32> sample_buffer;
|
||||
/// Number of input channels
|
||||
u32 input_count;
|
||||
/// Mix buffer indexes for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "audio_core/renderer/command/icommand.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
namespace ADSP {
|
||||
class CommandListProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioRenderer command for sinking samples to an output device.
|
||||
*/
|
||||
struct DeviceSinkCommand : ICommand {
|
||||
/**
|
||||
* Print this command's information to a string.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @param string - The string to print into.
|
||||
*/
|
||||
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
|
||||
|
||||
/**
|
||||
* Process this command.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
*/
|
||||
void Process(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/**
|
||||
* Verify this command's data is valid.
|
||||
*
|
||||
* @param processor - The CommandListProcessor processing this command.
|
||||
* @return True if the command is valid, otherwise false.
|
||||
*/
|
||||
bool Verify(const ADSP::CommandListProcessor& processor) override;
|
||||
|
||||
/// Device name
|
||||
char name[0x100];
|
||||
/// System session id (unused)
|
||||
s32 session_id;
|
||||
/// Sample buffer to sink
|
||||
std::span<s32> sample_buffer;
|
||||
/// Number of input channels
|
||||
u32 input_count;
|
||||
/// Mix buffer indexes for each channel
|
||||
std::array<s16, MaxChannels> inputs;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/aux_.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
const bool send_unmapped{!pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_specific->send_buffer_info_address,
|
||||
sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
|
||||
const bool return_unmapped{!pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[1], in_specific->return_buffer_info_address,
|
||||
sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
|
||||
|
||||
buffer_unmapped = send_unmapped || return_unmapped;
|
||||
|
||||
if (!buffer_unmapped) {
|
||||
auto send{workbuffers[0].GetReference(false)};
|
||||
send_buffer_info = send + sizeof(AuxInfoDsp);
|
||||
send_buffer = send + sizeof(AuxBufferInfo);
|
||||
|
||||
auto ret{workbuffers[1].GetReference(false)};
|
||||
return_buffer_info = ret + sizeof(AuxInfoDsp);
|
||||
return_buffer = ret + sizeof(AuxBufferInfo);
|
||||
}
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion2));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
const bool send_unmapped{!pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], params->send_buffer_info_address,
|
||||
sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
|
||||
const bool return_unmapped{!pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[1], params->return_buffer_info_address,
|
||||
sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
|
||||
|
||||
buffer_unmapped = send_unmapped || return_unmapped;
|
||||
|
||||
if (!buffer_unmapped) {
|
||||
auto send{workbuffers[0].GetReference(false)};
|
||||
send_buffer_info = send + sizeof(AuxInfoDsp);
|
||||
send_buffer = send + sizeof(AuxBufferInfo);
|
||||
|
||||
auto ret{workbuffers[1].GetReference(false)};
|
||||
return_buffer_info = ret + sizeof(AuxInfoDsp);
|
||||
return_buffer = ret + sizeof(AuxBufferInfo);
|
||||
}
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void AuxInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
void AuxInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void AuxInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
|
||||
|
||||
CpuAddr AuxInfo::GetWorkbuffer(s32 index) {
|
||||
return workbuffers[index].GetReference(true);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/aux_.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
const bool send_unmapped{!pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_specific->send_buffer_info_address,
|
||||
sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
|
||||
const bool return_unmapped{!pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[1], in_specific->return_buffer_info_address,
|
||||
sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
|
||||
|
||||
buffer_unmapped = send_unmapped || return_unmapped;
|
||||
|
||||
if (!buffer_unmapped) {
|
||||
auto send{workbuffers[0].GetReference(false)};
|
||||
send_buffer_info = send + sizeof(AuxInfoDsp);
|
||||
send_buffer = send + sizeof(AuxBufferInfo);
|
||||
|
||||
auto ret{workbuffers[1].GetReference(false)};
|
||||
return_buffer_info = ret + sizeof(AuxInfoDsp);
|
||||
return_buffer = ret + sizeof(AuxBufferInfo);
|
||||
}
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion2));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
const bool send_unmapped{!pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], params->send_buffer_info_address,
|
||||
sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
|
||||
const bool return_unmapped{!pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[1], params->return_buffer_info_address,
|
||||
sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
|
||||
|
||||
buffer_unmapped = send_unmapped || return_unmapped;
|
||||
|
||||
if (!buffer_unmapped) {
|
||||
auto send{workbuffers[0].GetReference(false)};
|
||||
send_buffer_info = send + sizeof(AuxInfoDsp);
|
||||
send_buffer = send + sizeof(AuxBufferInfo);
|
||||
|
||||
auto ret{workbuffers[1].GetReference(false)};
|
||||
return_buffer_info = ret + sizeof(AuxInfoDsp);
|
||||
return_buffer = ret + sizeof(AuxBufferInfo);
|
||||
}
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void AuxInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
void AuxInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void AuxInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
|
||||
|
||||
CpuAddr AuxInfo::GetWorkbuffer(s32 index) {
|
||||
return workbuffers[index].GetReference(true);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,123 +1,123 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Auxiliary Buffer used for Aux commands.
|
||||
* Send and return buffers are available (names from the game's perspective).
|
||||
* Send is read by the host, containing a buffer of samples to be used for whatever purpose.
|
||||
* Return is written by the host, writing a mix buffer back to the game.
|
||||
* This allows the game to use pre-processed samples skipping the other render processing,
|
||||
* and to examine or modify what the audio renderer has generated.
|
||||
*/
|
||||
class AuxInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
|
||||
/* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
|
||||
/* 0x30 */ u32 mix_buffer_count;
|
||||
/* 0x34 */ u32 sample_rate;
|
||||
/* 0x38 */ u32 count_max;
|
||||
/* 0x3C */ u32 mix_buffer_count_max;
|
||||
/* 0x40 */ CpuAddr send_buffer_info_address;
|
||||
/* 0x48 */ CpuAddr send_buffer_address;
|
||||
/* 0x50 */ CpuAddr return_buffer_info_address;
|
||||
/* 0x58 */ CpuAddr return_buffer_address;
|
||||
/* 0x60 */ u32 mix_buffer_sample_size;
|
||||
/* 0x64 */ u32 sample_count;
|
||||
/* 0x68 */ u32 mix_buffer_sample_count;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"AuxInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
|
||||
/* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
|
||||
/* 0x30 */ u32 mix_buffer_count;
|
||||
/* 0x34 */ u32 sample_rate;
|
||||
/* 0x38 */ u32 count_max;
|
||||
/* 0x3C */ u32 mix_buffer_count_max;
|
||||
/* 0x40 */ CpuAddr send_buffer_info_address;
|
||||
/* 0x48 */ CpuAddr send_buffer_address;
|
||||
/* 0x50 */ CpuAddr return_buffer_info_address;
|
||||
/* 0x58 */ CpuAddr return_buffer_address;
|
||||
/* 0x60 */ u32 mix_buffer_sample_size;
|
||||
/* 0x64 */ u32 sample_count;
|
||||
/* 0x68 */ u32 mix_buffer_sample_count;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"AuxInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
struct AuxInfoDsp {
|
||||
/* 0x00 */ u32 read_offset;
|
||||
/* 0x04 */ u32 write_offset;
|
||||
/* 0x08 */ u32 lost_sample_count;
|
||||
/* 0x0C */ u32 total_sample_count;
|
||||
/* 0x10 */ char unk10[0x30];
|
||||
};
|
||||
static_assert(sizeof(AuxInfoDsp) == 0x40, "AuxInfo::AuxInfoDsp has the wrong size!");
|
||||
|
||||
struct AuxBufferInfo {
|
||||
/* 0x00 */ AuxInfoDsp cpu_info;
|
||||
/* 0x40 */ AuxInfoDsp dsp_info;
|
||||
};
|
||||
static_assert(sizeof(AuxBufferInfo) == 0x80, "AuxInfo::AuxBufferInfo has the wrong size!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetWorkbuffer(s32 index) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Auxiliary Buffer used for Aux commands.
|
||||
* Send and return buffers are available (names from the game's perspective).
|
||||
* Send is read by the host, containing a buffer of samples to be used for whatever purpose.
|
||||
* Return is written by the host, writing a mix buffer back to the game.
|
||||
* This allows the game to use pre-processed samples skipping the other render processing,
|
||||
* and to examine or modify what the audio renderer has generated.
|
||||
*/
|
||||
class AuxInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
|
||||
/* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
|
||||
/* 0x30 */ u32 mix_buffer_count;
|
||||
/* 0x34 */ u32 sample_rate;
|
||||
/* 0x38 */ u32 count_max;
|
||||
/* 0x3C */ u32 mix_buffer_count_max;
|
||||
/* 0x40 */ CpuAddr send_buffer_info_address;
|
||||
/* 0x48 */ CpuAddr send_buffer_address;
|
||||
/* 0x50 */ CpuAddr return_buffer_info_address;
|
||||
/* 0x58 */ CpuAddr return_buffer_address;
|
||||
/* 0x60 */ u32 mix_buffer_sample_size;
|
||||
/* 0x64 */ u32 sample_count;
|
||||
/* 0x68 */ u32 mix_buffer_sample_count;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"AuxInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
|
||||
/* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
|
||||
/* 0x30 */ u32 mix_buffer_count;
|
||||
/* 0x34 */ u32 sample_rate;
|
||||
/* 0x38 */ u32 count_max;
|
||||
/* 0x3C */ u32 mix_buffer_count_max;
|
||||
/* 0x40 */ CpuAddr send_buffer_info_address;
|
||||
/* 0x48 */ CpuAddr send_buffer_address;
|
||||
/* 0x50 */ CpuAddr return_buffer_info_address;
|
||||
/* 0x58 */ CpuAddr return_buffer_address;
|
||||
/* 0x60 */ u32 mix_buffer_sample_size;
|
||||
/* 0x64 */ u32 sample_count;
|
||||
/* 0x68 */ u32 mix_buffer_sample_count;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"AuxInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
struct AuxInfoDsp {
|
||||
/* 0x00 */ u32 read_offset;
|
||||
/* 0x04 */ u32 write_offset;
|
||||
/* 0x08 */ u32 lost_sample_count;
|
||||
/* 0x0C */ u32 total_sample_count;
|
||||
/* 0x10 */ char unk10[0x30];
|
||||
};
|
||||
static_assert(sizeof(AuxInfoDsp) == 0x40, "AuxInfo::AuxInfoDsp has the wrong size!");
|
||||
|
||||
struct AuxBufferInfo {
|
||||
/* 0x00 */ AuxInfoDsp cpu_info;
|
||||
/* 0x40 */ AuxInfoDsp dsp_info;
|
||||
};
|
||||
static_assert(sizeof(AuxBufferInfo) == 0x80, "AuxInfo::AuxBufferInfo has the wrong size!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetWorkbuffer(s32 index) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/biquad_filter.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion2));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void BiquadFilterInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
}
|
||||
|
||||
void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state,
|
||||
EffectResultState& dsp_state) {}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/biquad_filter.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion2));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void BiquadFilterInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
}
|
||||
|
||||
void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state,
|
||||
EffectResultState& dsp_state) {}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,79 +1,79 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class BiquadFilterInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ std::array<s16, 3> b;
|
||||
/* 0x12 */ std::array<s16, 2> a;
|
||||
/* 0x16 */ s8 channel_count;
|
||||
/* 0x17 */ ParameterState state;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"BiquadFilterInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ std::array<s16, 3> b;
|
||||
/* 0x12 */ std::array<s16, 2> a;
|
||||
/* 0x16 */ s8 channel_count;
|
||||
/* 0x17 */ ParameterState state;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"BiquadFilterInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class BiquadFilterInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ std::array<s16, 3> b;
|
||||
/* 0x12 */ std::array<s16, 2> a;
|
||||
/* 0x16 */ s8 channel_count;
|
||||
/* 0x17 */ ParameterState state;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"BiquadFilterInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ std::array<s16, 3> b;
|
||||
/* 0x12 */ std::array<s16, 2> a;
|
||||
/* 0x16 */ s8 channel_count;
|
||||
/* 0x17 */ ParameterState state;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"BiquadFilterInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/buffer_mixer.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion2));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void BufferMixerInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state,
|
||||
EffectResultState& dsp_state) {}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/buffer_mixer.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion2));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void BufferMixerInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state,
|
||||
EffectResultState& dsp_state) {}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class BufferMixerInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
|
||||
/* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
|
||||
/* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
|
||||
/* 0x90 */ u32 mix_count;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"BufferMixerInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
|
||||
/* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
|
||||
/* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
|
||||
/* 0x90 */ u32 mix_count;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"BufferMixerInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class BufferMixerInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
|
||||
/* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
|
||||
/* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
|
||||
/* 0x90 */ u32 mix_count;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"BufferMixerInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
|
||||
/* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
|
||||
/* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
|
||||
/* 0x90 */ u32 mix_count;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"BufferMixerInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,82 +1,82 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/aux_.h"
|
||||
#include "audio_core/renderer/effect/capture.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{
|
||||
reinterpret_cast<const AuxInfo::ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<AuxInfo::ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_specific->send_buffer_info_address,
|
||||
in_specific->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
|
||||
|
||||
if (!buffer_unmapped) {
|
||||
const auto send_address{workbuffers[0].GetReference(false)};
|
||||
send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
|
||||
send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
|
||||
return_buffer_info = 0;
|
||||
return_buffer = 0;
|
||||
}
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{
|
||||
reinterpret_cast<const AuxInfo::ParameterVersion2*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<AuxInfo::ParameterVersion2*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion2));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], params->send_buffer_info_address,
|
||||
params->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
|
||||
|
||||
if (!buffer_unmapped) {
|
||||
const auto send_address{workbuffers[0].GetReference(false)};
|
||||
send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
|
||||
send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
|
||||
return_buffer_info = 0;
|
||||
return_buffer = 0;
|
||||
}
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void CaptureInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
|
||||
|
||||
CpuAddr CaptureInfo::GetWorkbuffer(s32 index) {
|
||||
return workbuffers[index].GetReference(true);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/aux_.h"
|
||||
#include "audio_core/renderer/effect/capture.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{
|
||||
reinterpret_cast<const AuxInfo::ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<AuxInfo::ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_specific->send_buffer_info_address,
|
||||
in_specific->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
|
||||
|
||||
if (!buffer_unmapped) {
|
||||
const auto send_address{workbuffers[0].GetReference(false)};
|
||||
send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
|
||||
send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
|
||||
return_buffer_info = 0;
|
||||
return_buffer = 0;
|
||||
}
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{
|
||||
reinterpret_cast<const AuxInfo::ParameterVersion2*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<AuxInfo::ParameterVersion2*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion2));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], params->send_buffer_info_address,
|
||||
params->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
|
||||
|
||||
if (!buffer_unmapped) {
|
||||
const auto send_address{workbuffers[0].GetReference(false)};
|
||||
send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
|
||||
send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
|
||||
return_buffer_info = 0;
|
||||
return_buffer = 0;
|
||||
}
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void CaptureInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
|
||||
|
||||
CpuAddr CaptureInfo::GetWorkbuffer(s32 index) {
|
||||
return workbuffers[index].GetReference(true);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class CaptureInfo : public EffectInfoBase {
|
||||
public:
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetWorkbuffer(s32 index) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class CaptureInfo : public EffectInfoBase {
|
||||
public:
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetWorkbuffer(s32 index) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/compressor.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {}
|
||||
|
||||
void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void CompressorInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
}
|
||||
|
||||
CpuAddr CompressorInfo::GetWorkbuffer(s32 index) {
|
||||
return GetSingleBuffer(index);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/compressor.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {}
|
||||
|
||||
void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void CompressorInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
}
|
||||
|
||||
CpuAddr CompressorInfo::GetWorkbuffer(s32 index) {
|
||||
return GetSingleBuffer(index);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,106 +1,106 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class CompressorInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ s16 channel_count_max;
|
||||
/* 0x0E */ s16 channel_count;
|
||||
/* 0x10 */ s32 sample_rate;
|
||||
/* 0x14 */ f32 threshold;
|
||||
/* 0x18 */ f32 compressor_ratio;
|
||||
/* 0x1C */ s32 attack_time;
|
||||
/* 0x20 */ s32 release_time;
|
||||
/* 0x24 */ f32 unk_24;
|
||||
/* 0x28 */ f32 unk_28;
|
||||
/* 0x2C */ f32 unk_2C;
|
||||
/* 0x30 */ f32 out_gain;
|
||||
/* 0x34 */ ParameterState state;
|
||||
/* 0x35 */ bool makeup_gain_enabled;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"CompressorInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ s16 channel_count_max;
|
||||
/* 0x0E */ s16 channel_count;
|
||||
/* 0x10 */ s32 sample_rate;
|
||||
/* 0x14 */ f32 threshold;
|
||||
/* 0x18 */ f32 compressor_ratio;
|
||||
/* 0x1C */ s32 attack_time;
|
||||
/* 0x20 */ s32 release_time;
|
||||
/* 0x24 */ f32 unk_24;
|
||||
/* 0x28 */ f32 unk_28;
|
||||
/* 0x2C */ f32 unk_2C;
|
||||
/* 0x30 */ f32 out_gain;
|
||||
/* 0x34 */ ParameterState state;
|
||||
/* 0x35 */ bool makeup_gain_enabled;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"CompressorInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
struct State {
|
||||
f32 unk_00;
|
||||
f32 unk_04;
|
||||
f32 unk_08;
|
||||
f32 unk_0C;
|
||||
f32 unk_10;
|
||||
f32 unk_14;
|
||||
f32 unk_18;
|
||||
f32 makeup_gain;
|
||||
f32 unk_20;
|
||||
char unk_24[0x1C];
|
||||
};
|
||||
static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
|
||||
"CompressorInfo::State has the wrong size!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetWorkbuffer(s32 index) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class CompressorInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ s16 channel_count_max;
|
||||
/* 0x0E */ s16 channel_count;
|
||||
/* 0x10 */ s32 sample_rate;
|
||||
/* 0x14 */ f32 threshold;
|
||||
/* 0x18 */ f32 compressor_ratio;
|
||||
/* 0x1C */ s32 attack_time;
|
||||
/* 0x20 */ s32 release_time;
|
||||
/* 0x24 */ f32 unk_24;
|
||||
/* 0x28 */ f32 unk_28;
|
||||
/* 0x2C */ f32 unk_2C;
|
||||
/* 0x30 */ f32 out_gain;
|
||||
/* 0x34 */ ParameterState state;
|
||||
/* 0x35 */ bool makeup_gain_enabled;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"CompressorInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ s16 channel_count_max;
|
||||
/* 0x0E */ s16 channel_count;
|
||||
/* 0x10 */ s32 sample_rate;
|
||||
/* 0x14 */ f32 threshold;
|
||||
/* 0x18 */ f32 compressor_ratio;
|
||||
/* 0x1C */ s32 attack_time;
|
||||
/* 0x20 */ s32 release_time;
|
||||
/* 0x24 */ f32 unk_24;
|
||||
/* 0x28 */ f32 unk_28;
|
||||
/* 0x2C */ f32 unk_2C;
|
||||
/* 0x30 */ f32 out_gain;
|
||||
/* 0x34 */ ParameterState state;
|
||||
/* 0x35 */ bool makeup_gain_enabled;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"CompressorInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
struct State {
|
||||
f32 unk_00;
|
||||
f32 unk_04;
|
||||
f32 unk_08;
|
||||
f32 unk_0C;
|
||||
f32 unk_10;
|
||||
f32 unk_14;
|
||||
f32 unk_18;
|
||||
f32 makeup_gain;
|
||||
f32 unk_20;
|
||||
char unk_24[0x1C];
|
||||
};
|
||||
static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
|
||||
"CompressorInfo::State has the wrong size!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetWorkbuffer(s32 index) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/delay.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
if (IsChannelCountValid(in_specific->channel_count_max)) {
|
||||
const auto old_state{params->state};
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count)) {
|
||||
params->channel_count = params->channel_count_max;
|
||||
}
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count) ||
|
||||
old_state != ParameterState::Updated) {
|
||||
params->state = old_state;
|
||||
}
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
if (IsChannelCountValid(in_specific->channel_count_max)) {
|
||||
const auto old_state{params->state};
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count)) {
|
||||
params->channel_count = params->channel_count_max;
|
||||
}
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count) ||
|
||||
old_state != ParameterState::Updated) {
|
||||
params->state = old_state;
|
||||
}
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void DelayInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
}
|
||||
|
||||
void DelayInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void DelayInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
|
||||
|
||||
CpuAddr DelayInfo::GetWorkbuffer(s32 index) {
|
||||
return GetSingleBuffer(index);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/delay.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
if (IsChannelCountValid(in_specific->channel_count_max)) {
|
||||
const auto old_state{params->state};
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count)) {
|
||||
params->channel_count = params->channel_count_max;
|
||||
}
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count) ||
|
||||
old_state != ParameterState::Updated) {
|
||||
params->state = old_state;
|
||||
}
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
if (IsChannelCountValid(in_specific->channel_count_max)) {
|
||||
const auto old_state{params->state};
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count)) {
|
||||
params->channel_count = params->channel_count_max;
|
||||
}
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count) ||
|
||||
old_state != ParameterState::Updated) {
|
||||
params->state = old_state;
|
||||
}
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void DelayInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
}
|
||||
|
||||
void DelayInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void DelayInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
|
||||
|
||||
CpuAddr DelayInfo::GetWorkbuffer(s32 index) {
|
||||
return GetSingleBuffer(index);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,135 +1,135 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class DelayInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ u16 channel_count_max;
|
||||
/* 0x0E */ u16 channel_count;
|
||||
/* 0x10 */ u32 delay_time_max;
|
||||
/* 0x14 */ u32 delay_time;
|
||||
/* 0x18 */ Common::FixedPoint<18, 14> sample_rate;
|
||||
/* 0x1C */ Common::FixedPoint<18, 14> in_gain;
|
||||
/* 0x20 */ Common::FixedPoint<18, 14> feedback_gain;
|
||||
/* 0x24 */ Common::FixedPoint<18, 14> wet_gain;
|
||||
/* 0x28 */ Common::FixedPoint<18, 14> dry_gain;
|
||||
/* 0x2C */ Common::FixedPoint<18, 14> channel_spread;
|
||||
/* 0x30 */ Common::FixedPoint<18, 14> lowpass_amount;
|
||||
/* 0x34 */ ParameterState state;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"DelayInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ s16 channel_count_max;
|
||||
/* 0x0E */ s16 channel_count;
|
||||
/* 0x10 */ s32 delay_time_max;
|
||||
/* 0x14 */ s32 delay_time;
|
||||
/* 0x18 */ s32 sample_rate;
|
||||
/* 0x1C */ s32 in_gain;
|
||||
/* 0x20 */ s32 feedback_gain;
|
||||
/* 0x24 */ s32 wet_gain;
|
||||
/* 0x28 */ s32 dry_gain;
|
||||
/* 0x2C */ s32 channel_spread;
|
||||
/* 0x30 */ s32 lowpass_amount;
|
||||
/* 0x34 */ ParameterState state;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"DelayInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
struct DelayLine {
|
||||
Common::FixedPoint<50, 14> Read() const {
|
||||
return buffer[buffer_pos];
|
||||
}
|
||||
|
||||
void Write(const Common::FixedPoint<50, 14> value) {
|
||||
buffer[buffer_pos] = value;
|
||||
buffer_pos = static_cast<u32>((buffer_pos + 1) % buffer.size());
|
||||
}
|
||||
|
||||
s32 sample_count_max{};
|
||||
s32 sample_count{};
|
||||
std::vector<Common::FixedPoint<50, 14>> buffer{};
|
||||
u32 buffer_pos{};
|
||||
Common::FixedPoint<18, 14> decay_rate{};
|
||||
};
|
||||
|
||||
struct State {
|
||||
/* 0x000 */ std::array<s32, 8> unk_000;
|
||||
/* 0x020 */ std::array<DelayLine, MaxChannels> delay_lines;
|
||||
/* 0x0B0 */ Common::FixedPoint<18, 14> feedback_gain;
|
||||
/* 0x0B4 */ Common::FixedPoint<18, 14> delay_feedback_gain;
|
||||
/* 0x0B8 */ Common::FixedPoint<18, 14> delay_feedback_cross_gain;
|
||||
/* 0x0BC */ Common::FixedPoint<18, 14> lowpass_gain;
|
||||
/* 0x0C0 */ Common::FixedPoint<18, 14> lowpass_feedback_gain;
|
||||
/* 0x0C4 */ std::array<Common::FixedPoint<50, 14>, MaxChannels> lowpass_z;
|
||||
};
|
||||
static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
|
||||
"DelayInfo::State has the wrong size!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetWorkbuffer(s32 index) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class DelayInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ u16 channel_count_max;
|
||||
/* 0x0E */ u16 channel_count;
|
||||
/* 0x10 */ u32 delay_time_max;
|
||||
/* 0x14 */ u32 delay_time;
|
||||
/* 0x18 */ Common::FixedPoint<18, 14> sample_rate;
|
||||
/* 0x1C */ Common::FixedPoint<18, 14> in_gain;
|
||||
/* 0x20 */ Common::FixedPoint<18, 14> feedback_gain;
|
||||
/* 0x24 */ Common::FixedPoint<18, 14> wet_gain;
|
||||
/* 0x28 */ Common::FixedPoint<18, 14> dry_gain;
|
||||
/* 0x2C */ Common::FixedPoint<18, 14> channel_spread;
|
||||
/* 0x30 */ Common::FixedPoint<18, 14> lowpass_amount;
|
||||
/* 0x34 */ ParameterState state;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"DelayInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ s16 channel_count_max;
|
||||
/* 0x0E */ s16 channel_count;
|
||||
/* 0x10 */ s32 delay_time_max;
|
||||
/* 0x14 */ s32 delay_time;
|
||||
/* 0x18 */ s32 sample_rate;
|
||||
/* 0x1C */ s32 in_gain;
|
||||
/* 0x20 */ s32 feedback_gain;
|
||||
/* 0x24 */ s32 wet_gain;
|
||||
/* 0x28 */ s32 dry_gain;
|
||||
/* 0x2C */ s32 channel_spread;
|
||||
/* 0x30 */ s32 lowpass_amount;
|
||||
/* 0x34 */ ParameterState state;
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"DelayInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
struct DelayLine {
|
||||
Common::FixedPoint<50, 14> Read() const {
|
||||
return buffer[buffer_pos];
|
||||
}
|
||||
|
||||
void Write(const Common::FixedPoint<50, 14> value) {
|
||||
buffer[buffer_pos] = value;
|
||||
buffer_pos = static_cast<u32>((buffer_pos + 1) % buffer.size());
|
||||
}
|
||||
|
||||
s32 sample_count_max{};
|
||||
s32 sample_count{};
|
||||
std::vector<Common::FixedPoint<50, 14>> buffer{};
|
||||
u32 buffer_pos{};
|
||||
Common::FixedPoint<18, 14> decay_rate{};
|
||||
};
|
||||
|
||||
struct State {
|
||||
/* 0x000 */ std::array<s32, 8> unk_000;
|
||||
/* 0x020 */ std::array<DelayLine, MaxChannels> delay_lines;
|
||||
/* 0x0B0 */ Common::FixedPoint<18, 14> feedback_gain;
|
||||
/* 0x0B4 */ Common::FixedPoint<18, 14> delay_feedback_gain;
|
||||
/* 0x0B8 */ Common::FixedPoint<18, 14> delay_feedback_cross_gain;
|
||||
/* 0x0BC */ Common::FixedPoint<18, 14> lowpass_gain;
|
||||
/* 0x0C0 */ Common::FixedPoint<18, 14> lowpass_feedback_gain;
|
||||
/* 0x0C4 */ std::array<Common::FixedPoint<50, 14>, MaxChannels> lowpass_z;
|
||||
};
|
||||
static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
|
||||
"DelayInfo::State has the wrong size!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetWorkbuffer(s32 index) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/effect_context.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
|
||||
std::span<EffectResultState> result_states_cpu_,
|
||||
std::span<EffectResultState> result_states_dsp_,
|
||||
const size_t dsp_state_count_) {
|
||||
effect_infos = effect_infos_;
|
||||
effect_count = effect_count_;
|
||||
result_states_cpu = result_states_cpu_;
|
||||
result_states_dsp = result_states_dsp_;
|
||||
dsp_state_count = dsp_state_count_;
|
||||
}
|
||||
|
||||
EffectInfoBase& EffectContext::GetInfo(const u32 index) {
|
||||
return effect_infos[index];
|
||||
}
|
||||
|
||||
EffectResultState& EffectContext::GetResultState(const u32 index) {
|
||||
return result_states_cpu[index];
|
||||
}
|
||||
|
||||
EffectResultState& EffectContext::GetDspSharedResultState(const u32 index) {
|
||||
return result_states_dsp[index];
|
||||
}
|
||||
|
||||
u32 EffectContext::GetCount() const {
|
||||
return effect_count;
|
||||
}
|
||||
|
||||
void EffectContext::UpdateStateByDspShared() {
|
||||
for (size_t i = 0; i < dsp_state_count; i++) {
|
||||
effect_infos[i].UpdateResultState(result_states_cpu[i], result_states_dsp[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/effect_context.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
|
||||
std::span<EffectResultState> result_states_cpu_,
|
||||
std::span<EffectResultState> result_states_dsp_,
|
||||
const size_t dsp_state_count_) {
|
||||
effect_infos = effect_infos_;
|
||||
effect_count = effect_count_;
|
||||
result_states_cpu = result_states_cpu_;
|
||||
result_states_dsp = result_states_dsp_;
|
||||
dsp_state_count = dsp_state_count_;
|
||||
}
|
||||
|
||||
EffectInfoBase& EffectContext::GetInfo(const u32 index) {
|
||||
return effect_infos[index];
|
||||
}
|
||||
|
||||
EffectResultState& EffectContext::GetResultState(const u32 index) {
|
||||
return result_states_cpu[index];
|
||||
}
|
||||
|
||||
EffectResultState& EffectContext::GetDspSharedResultState(const u32 index) {
|
||||
return result_states_dsp[index];
|
||||
}
|
||||
|
||||
u32 EffectContext::GetCount() const {
|
||||
return effect_count;
|
||||
}
|
||||
|
||||
void EffectContext::UpdateStateByDspShared() {
|
||||
for (size_t i = 0; i < dsp_state_count; i++) {
|
||||
effect_infos[i].UpdateResultState(result_states_cpu[i], result_states_dsp[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "audio_core/renderer/effect/effect_result_state.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class EffectContext {
|
||||
public:
|
||||
/**
|
||||
* Initialize the effect context
|
||||
* @param effect_infos_ - List of effect infos for this context
|
||||
* @param effect_count_ - The number of effects in the list
|
||||
* @param result_states_cpu_ - The workbuffer of result states for the CPU for this context
|
||||
* @param result_states_dsp_ - The workbuffer of result states for the DSP for this context
|
||||
* @param dsp_state_count - The number of result states
|
||||
*/
|
||||
void Initialize(std::span<EffectInfoBase> effect_infos_, u32 effect_count_,
|
||||
std::span<EffectResultState> result_states_cpu_,
|
||||
std::span<EffectResultState> result_states_dsp_, size_t dsp_state_count);
|
||||
|
||||
/**
|
||||
* Get the EffectInfo for a given index
|
||||
* @param index Which effect to return
|
||||
* @return Pointer to the effect
|
||||
*/
|
||||
EffectInfoBase& GetInfo(const u32 index);
|
||||
|
||||
/**
|
||||
* Get the CPU result state for a given index
|
||||
* @param index Which result to return
|
||||
* @return Pointer to the effect result state
|
||||
*/
|
||||
EffectResultState& GetResultState(const u32 index);
|
||||
|
||||
/**
|
||||
* Get the DSP result state for a given index
|
||||
* @param index Which result to return
|
||||
* @return Pointer to the effect result state
|
||||
*/
|
||||
EffectResultState& GetDspSharedResultState(const u32 index);
|
||||
|
||||
/**
|
||||
* Get the number of effects in this context
|
||||
* @return The number of effects
|
||||
*/
|
||||
u32 GetCount() const;
|
||||
|
||||
/**
|
||||
* Update the CPU and DSP result states for all effects
|
||||
*/
|
||||
void UpdateStateByDspShared();
|
||||
|
||||
private:
|
||||
/// Workbuffer for all of the effects
|
||||
std::span<EffectInfoBase> effect_infos{};
|
||||
/// Number of effects in the workbuffer
|
||||
u32 effect_count{};
|
||||
/// Workbuffer of states for all effects, kept host-side and not directly modified, dsp states
|
||||
/// are copied here on the next render frame
|
||||
std::span<EffectResultState> result_states_cpu{};
|
||||
/// Workbuffer of states for all effects, used by the AudioRenderer to track effect state
|
||||
/// between calls
|
||||
std::span<EffectResultState> result_states_dsp{};
|
||||
/// Number of result states in the workbuffers
|
||||
size_t dsp_state_count{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "audio_core/renderer/effect/effect_result_state.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class EffectContext {
|
||||
public:
|
||||
/**
|
||||
* Initialize the effect context
|
||||
* @param effect_infos_ - List of effect infos for this context
|
||||
* @param effect_count_ - The number of effects in the list
|
||||
* @param result_states_cpu_ - The workbuffer of result states for the CPU for this context
|
||||
* @param result_states_dsp_ - The workbuffer of result states for the DSP for this context
|
||||
* @param dsp_state_count - The number of result states
|
||||
*/
|
||||
void Initialize(std::span<EffectInfoBase> effect_infos_, u32 effect_count_,
|
||||
std::span<EffectResultState> result_states_cpu_,
|
||||
std::span<EffectResultState> result_states_dsp_, size_t dsp_state_count);
|
||||
|
||||
/**
|
||||
* Get the EffectInfo for a given index
|
||||
* @param index Which effect to return
|
||||
* @return Pointer to the effect
|
||||
*/
|
||||
EffectInfoBase& GetInfo(const u32 index);
|
||||
|
||||
/**
|
||||
* Get the CPU result state for a given index
|
||||
* @param index Which result to return
|
||||
* @return Pointer to the effect result state
|
||||
*/
|
||||
EffectResultState& GetResultState(const u32 index);
|
||||
|
||||
/**
|
||||
* Get the DSP result state for a given index
|
||||
* @param index Which result to return
|
||||
* @return Pointer to the effect result state
|
||||
*/
|
||||
EffectResultState& GetDspSharedResultState(const u32 index);
|
||||
|
||||
/**
|
||||
* Get the number of effects in this context
|
||||
* @return The number of effects
|
||||
*/
|
||||
u32 GetCount() const;
|
||||
|
||||
/**
|
||||
* Update the CPU and DSP result states for all effects
|
||||
*/
|
||||
void UpdateStateByDspShared();
|
||||
|
||||
private:
|
||||
/// Workbuffer for all of the effects
|
||||
std::span<EffectInfoBase> effect_infos{};
|
||||
/// Number of effects in the workbuffer
|
||||
u32 effect_count{};
|
||||
/// Workbuffer of states for all effects, kept host-side and not directly modified, dsp states
|
||||
/// are copied here on the next render frame
|
||||
std::span<EffectResultState> result_states_cpu{};
|
||||
/// Workbuffer of states for all effects, used by the AudioRenderer to track effect state
|
||||
/// between calls
|
||||
std::span<EffectResultState> result_states_dsp{};
|
||||
/// Number of result states in the workbuffers
|
||||
size_t dsp_state_count{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,435 +1,435 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/behavior/behavior_info.h"
|
||||
#include "audio_core/renderer/effect/effect_result_state.h"
|
||||
#include "audio_core/renderer/memory/address_info.h"
|
||||
#include "audio_core/renderer/memory/pool_mapper.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Base of all effects. Holds various data and functions used for all derived effects.
|
||||
* Should not be used directly.
|
||||
*/
|
||||
class EffectInfoBase {
|
||||
public:
|
||||
enum class Type : u8 {
|
||||
Invalid,
|
||||
Mix,
|
||||
Aux,
|
||||
Delay,
|
||||
Reverb,
|
||||
I3dl2Reverb,
|
||||
BiquadFilter,
|
||||
LightLimiter,
|
||||
Capture,
|
||||
Compressor,
|
||||
};
|
||||
|
||||
enum class UsageState {
|
||||
Invalid,
|
||||
New,
|
||||
Enabled,
|
||||
Disabled,
|
||||
};
|
||||
|
||||
enum class OutStatus : u8 {
|
||||
Invalid,
|
||||
New,
|
||||
Initialized,
|
||||
Used,
|
||||
Removed,
|
||||
};
|
||||
|
||||
enum class ParameterState : u8 {
|
||||
Initialized,
|
||||
Updating,
|
||||
Updated,
|
||||
};
|
||||
|
||||
struct InParameterVersion1 {
|
||||
/* 0x00 */ Type type;
|
||||
/* 0x01 */ bool is_new;
|
||||
/* 0x02 */ bool enabled;
|
||||
/* 0x04 */ u32 mix_id;
|
||||
/* 0x08 */ CpuAddr workbuffer;
|
||||
/* 0x10 */ CpuAddr workbuffer_size;
|
||||
/* 0x18 */ u32 process_order;
|
||||
/* 0x1C */ char unk1C[0x4];
|
||||
/* 0x20 */ std::array<u8, 0xA0> specific;
|
||||
};
|
||||
static_assert(sizeof(InParameterVersion1) == 0xC0,
|
||||
"EffectInfoBase::InParameterVersion1 has the wrong size!");
|
||||
|
||||
struct InParameterVersion2 {
|
||||
/* 0x00 */ Type type;
|
||||
/* 0x01 */ bool is_new;
|
||||
/* 0x02 */ bool enabled;
|
||||
/* 0x04 */ u32 mix_id;
|
||||
/* 0x08 */ CpuAddr workbuffer;
|
||||
/* 0x10 */ CpuAddr workbuffer_size;
|
||||
/* 0x18 */ u32 process_order;
|
||||
/* 0x1C */ char unk1C[0x4];
|
||||
/* 0x20 */ std::array<u8, 0xA0> specific;
|
||||
};
|
||||
static_assert(sizeof(InParameterVersion2) == 0xC0,
|
||||
"EffectInfoBase::InParameterVersion2 has the wrong size!");
|
||||
|
||||
struct OutStatusVersion1 {
|
||||
/* 0x00 */ OutStatus state;
|
||||
/* 0x01 */ char unk01[0xF];
|
||||
};
|
||||
static_assert(sizeof(OutStatusVersion1) == 0x10,
|
||||
"EffectInfoBase::OutStatusVersion1 has the wrong size!");
|
||||
|
||||
struct OutStatusVersion2 {
|
||||
/* 0x00 */ OutStatus state;
|
||||
/* 0x01 */ char unk01[0xF];
|
||||
/* 0x10 */ EffectResultState result_state;
|
||||
};
|
||||
static_assert(sizeof(OutStatusVersion2) == 0x90,
|
||||
"EffectInfoBase::OutStatusVersion2 has the wrong size!");
|
||||
|
||||
struct State {
|
||||
std::array<u8, 0x500> buffer;
|
||||
};
|
||||
static_assert(sizeof(State) == 0x500, "EffectInfoBase::State has the wrong size!");
|
||||
|
||||
EffectInfoBase() {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
virtual ~EffectInfoBase() = default;
|
||||
|
||||
/**
|
||||
* Cleanup this effect, resetting it to a starting state.
|
||||
*/
|
||||
void Cleanup() {
|
||||
type = Type::Invalid;
|
||||
enabled = false;
|
||||
mix_id = UnusedMixId;
|
||||
process_order = InvalidProcessOrder;
|
||||
buffer_unmapped = false;
|
||||
parameter = {};
|
||||
for (auto& workbuffer : workbuffers) {
|
||||
workbuffer.Setup(CpuAddr(0), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcibly unmap all assigned workbuffers from the AudioRenderer.
|
||||
*
|
||||
* @param pool_mapper - Mapper to unmap the buffers.
|
||||
*/
|
||||
void ForceUnmapBuffers(const PoolMapper& pool_mapper) {
|
||||
for (auto& workbuffer : workbuffers) {
|
||||
if (workbuffer.GetReference(false) != 0) {
|
||||
pool_mapper.ForceUnmapPointer(workbuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this effect is enabled.
|
||||
*
|
||||
* @return True if effect is enabled, otherwise false.
|
||||
*/
|
||||
bool IsEnabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this effect should not be generated.
|
||||
*
|
||||
* @return True if effect should be skipped, otherwise false.
|
||||
*/
|
||||
bool ShouldSkip() const {
|
||||
return buffer_unmapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of this effect.
|
||||
*
|
||||
* @return The type of this effect. See EffectInfoBase::Type
|
||||
*/
|
||||
Type GetType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of this effect.
|
||||
*
|
||||
* @param type_ - The new type of this effect.
|
||||
*/
|
||||
void SetType(const Type type_) {
|
||||
type = type_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mix id of this effect.
|
||||
*
|
||||
* @return Mix id of this effect.
|
||||
*/
|
||||
s32 GetMixId() const {
|
||||
return mix_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the processing order of this effect.
|
||||
*
|
||||
* @return Process order of this effect.
|
||||
*/
|
||||
s32 GetProcessingOrder() const {
|
||||
return process_order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this effect's parameter data.
|
||||
*
|
||||
* @return Pointer to the parametter, must be cast to the correct type.
|
||||
*/
|
||||
u8* GetParameter() {
|
||||
return parameter.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this effect's parameter data.
|
||||
*
|
||||
* @return Pointer to the parametter, must be cast to the correct type.
|
||||
*/
|
||||
u8* GetStateBuffer() {
|
||||
return state.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this effect's usage state.
|
||||
*
|
||||
* @param usage - new usage state of this effect.
|
||||
*/
|
||||
void SetUsage(const UsageState usage) {
|
||||
usage_state = usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this effects need to have its workbuffer information updated.
|
||||
* Version 1.
|
||||
*
|
||||
* @param params - Input parameters.
|
||||
* @return True if workbuffers need updating, otherwise false.
|
||||
*/
|
||||
bool ShouldUpdateWorkBufferInfo(const InParameterVersion1& params) const {
|
||||
return buffer_unmapped || params.is_new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this effects need to have its workbuffer information updated.
|
||||
* Version 2.
|
||||
*
|
||||
* @param params - Input parameters.
|
||||
* @return True if workbuffers need updating, otherwise false.
|
||||
*/
|
||||
bool ShouldUpdateWorkBufferInfo(const InParameterVersion2& params) const {
|
||||
return buffer_unmapped || params.is_new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current usage state of this effect.
|
||||
*
|
||||
* @return The current usage state.
|
||||
*/
|
||||
UsageState GetUsage() const {
|
||||
return usage_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the current state. Version 1.
|
||||
*
|
||||
* @param out_status - Status to write.
|
||||
* @param renderer_active - Is the AudioRenderer active?
|
||||
*/
|
||||
void StoreStatus(OutStatusVersion1& out_status, const bool renderer_active) const {
|
||||
if (renderer_active) {
|
||||
if (usage_state != UsageState::Disabled) {
|
||||
out_status.state = OutStatus::Used;
|
||||
} else {
|
||||
out_status.state = OutStatus::Removed;
|
||||
}
|
||||
} else if (usage_state == UsageState::New) {
|
||||
out_status.state = OutStatus::Used;
|
||||
} else {
|
||||
out_status.state = OutStatus::Removed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the current state. Version 2.
|
||||
*
|
||||
* @param out_status - Status to write.
|
||||
* @param renderer_active - Is the AudioRenderer active?
|
||||
*/
|
||||
void StoreStatus(OutStatusVersion2& out_status, const bool renderer_active) const {
|
||||
if (renderer_active) {
|
||||
if (usage_state != UsageState::Disabled) {
|
||||
out_status.state = OutStatus::Used;
|
||||
} else {
|
||||
out_status.state = OutStatus::Removed;
|
||||
}
|
||||
} else if (usage_state == UsageState::New) {
|
||||
out_status.state = OutStatus::Used;
|
||||
} else {
|
||||
out_status.state = OutStatus::Removed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
virtual void Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
[[maybe_unused]] const InParameterVersion1& params,
|
||||
[[maybe_unused]] const PoolMapper& pool_mapper) {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
virtual void Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
[[maybe_unused]] const InParameterVersion2& params,
|
||||
[[maybe_unused]] const PoolMapper& pool_mapper) {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
virtual void UpdateForCommandGeneration() {}
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
virtual void InitializeResultState([[maybe_unused]] EffectResultState& result_state) {}
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
virtual void UpdateResultState([[maybe_unused]] EffectResultState& cpu_state,
|
||||
[[maybe_unused]] EffectResultState& dsp_state) {}
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
virtual CpuAddr GetWorkbuffer([[maybe_unused]] s32 index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first workbuffer assigned to this effect.
|
||||
*
|
||||
* @param index - Workbuffer index. Unused.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetSingleBuffer([[maybe_unused]] const s32 index) {
|
||||
if (enabled) {
|
||||
return workbuffers[0].GetReference(true);
|
||||
}
|
||||
|
||||
if (usage_state != UsageState::Disabled) {
|
||||
const auto ref{workbuffers[0].GetReference(false)};
|
||||
const auto size{workbuffers[0].GetSize()};
|
||||
if (ref != 0 && size > 0) {
|
||||
// Invalidate DSP cache
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the send buffer info, used by Aux and Capture.
|
||||
*
|
||||
* @return Address of the buffer info.
|
||||
*/
|
||||
CpuAddr GetSendBufferInfo() const {
|
||||
return send_buffer_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the send buffer, used by Aux and Capture.
|
||||
*
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetSendBuffer() const {
|
||||
return send_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the return buffer info, used by Aux and Capture.
|
||||
*
|
||||
* @return Address of the buffer info.
|
||||
*/
|
||||
CpuAddr GetReturnBufferInfo() const {
|
||||
return return_buffer_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the return buffer, used by Aux and Capture.
|
||||
*
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetReturnBuffer() const {
|
||||
return return_buffer;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Type of this effect. May be changed
|
||||
Type type{Type::Invalid};
|
||||
/// Is this effect enabled?
|
||||
bool enabled{};
|
||||
/// Are this effect's buffers unmapped?
|
||||
bool buffer_unmapped{};
|
||||
/// Current usage state
|
||||
UsageState usage_state{UsageState::Invalid};
|
||||
/// Mix id of this effect
|
||||
s32 mix_id{UnusedMixId};
|
||||
/// Process order of this effect
|
||||
s32 process_order{InvalidProcessOrder};
|
||||
/// Workbuffers assigned to this effect
|
||||
std::array<AddressInfo, 2> workbuffers{AddressInfo(CpuAddr(0), 0), AddressInfo(CpuAddr(0), 0)};
|
||||
/// Aux/Capture buffer info for reading
|
||||
CpuAddr send_buffer_info{};
|
||||
/// Aux/Capture buffer for reading
|
||||
CpuAddr send_buffer{};
|
||||
/// Aux/Capture buffer info for writing
|
||||
CpuAddr return_buffer_info{};
|
||||
/// Aux/Capture buffer for writing
|
||||
CpuAddr return_buffer{};
|
||||
/// Parameters of this effect
|
||||
std::array<u8, sizeof(InParameterVersion2)> parameter{};
|
||||
/// State of this effect used by the AudioRenderer across calls
|
||||
std::array<u8, sizeof(State)> state{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/behavior/behavior_info.h"
|
||||
#include "audio_core/renderer/effect/effect_result_state.h"
|
||||
#include "audio_core/renderer/memory/address_info.h"
|
||||
#include "audio_core/renderer/memory/pool_mapper.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Base of all effects. Holds various data and functions used for all derived effects.
|
||||
* Should not be used directly.
|
||||
*/
|
||||
class EffectInfoBase {
|
||||
public:
|
||||
enum class Type : u8 {
|
||||
Invalid,
|
||||
Mix,
|
||||
Aux,
|
||||
Delay,
|
||||
Reverb,
|
||||
I3dl2Reverb,
|
||||
BiquadFilter,
|
||||
LightLimiter,
|
||||
Capture,
|
||||
Compressor,
|
||||
};
|
||||
|
||||
enum class UsageState {
|
||||
Invalid,
|
||||
New,
|
||||
Enabled,
|
||||
Disabled,
|
||||
};
|
||||
|
||||
enum class OutStatus : u8 {
|
||||
Invalid,
|
||||
New,
|
||||
Initialized,
|
||||
Used,
|
||||
Removed,
|
||||
};
|
||||
|
||||
enum class ParameterState : u8 {
|
||||
Initialized,
|
||||
Updating,
|
||||
Updated,
|
||||
};
|
||||
|
||||
struct InParameterVersion1 {
|
||||
/* 0x00 */ Type type;
|
||||
/* 0x01 */ bool is_new;
|
||||
/* 0x02 */ bool enabled;
|
||||
/* 0x04 */ u32 mix_id;
|
||||
/* 0x08 */ CpuAddr workbuffer;
|
||||
/* 0x10 */ CpuAddr workbuffer_size;
|
||||
/* 0x18 */ u32 process_order;
|
||||
/* 0x1C */ char unk1C[0x4];
|
||||
/* 0x20 */ std::array<u8, 0xA0> specific;
|
||||
};
|
||||
static_assert(sizeof(InParameterVersion1) == 0xC0,
|
||||
"EffectInfoBase::InParameterVersion1 has the wrong size!");
|
||||
|
||||
struct InParameterVersion2 {
|
||||
/* 0x00 */ Type type;
|
||||
/* 0x01 */ bool is_new;
|
||||
/* 0x02 */ bool enabled;
|
||||
/* 0x04 */ u32 mix_id;
|
||||
/* 0x08 */ CpuAddr workbuffer;
|
||||
/* 0x10 */ CpuAddr workbuffer_size;
|
||||
/* 0x18 */ u32 process_order;
|
||||
/* 0x1C */ char unk1C[0x4];
|
||||
/* 0x20 */ std::array<u8, 0xA0> specific;
|
||||
};
|
||||
static_assert(sizeof(InParameterVersion2) == 0xC0,
|
||||
"EffectInfoBase::InParameterVersion2 has the wrong size!");
|
||||
|
||||
struct OutStatusVersion1 {
|
||||
/* 0x00 */ OutStatus state;
|
||||
/* 0x01 */ char unk01[0xF];
|
||||
};
|
||||
static_assert(sizeof(OutStatusVersion1) == 0x10,
|
||||
"EffectInfoBase::OutStatusVersion1 has the wrong size!");
|
||||
|
||||
struct OutStatusVersion2 {
|
||||
/* 0x00 */ OutStatus state;
|
||||
/* 0x01 */ char unk01[0xF];
|
||||
/* 0x10 */ EffectResultState result_state;
|
||||
};
|
||||
static_assert(sizeof(OutStatusVersion2) == 0x90,
|
||||
"EffectInfoBase::OutStatusVersion2 has the wrong size!");
|
||||
|
||||
struct State {
|
||||
std::array<u8, 0x500> buffer;
|
||||
};
|
||||
static_assert(sizeof(State) == 0x500, "EffectInfoBase::State has the wrong size!");
|
||||
|
||||
EffectInfoBase() {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
virtual ~EffectInfoBase() = default;
|
||||
|
||||
/**
|
||||
* Cleanup this effect, resetting it to a starting state.
|
||||
*/
|
||||
void Cleanup() {
|
||||
type = Type::Invalid;
|
||||
enabled = false;
|
||||
mix_id = UnusedMixId;
|
||||
process_order = InvalidProcessOrder;
|
||||
buffer_unmapped = false;
|
||||
parameter = {};
|
||||
for (auto& workbuffer : workbuffers) {
|
||||
workbuffer.Setup(CpuAddr(0), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcibly unmap all assigned workbuffers from the AudioRenderer.
|
||||
*
|
||||
* @param pool_mapper - Mapper to unmap the buffers.
|
||||
*/
|
||||
void ForceUnmapBuffers(const PoolMapper& pool_mapper) {
|
||||
for (auto& workbuffer : workbuffers) {
|
||||
if (workbuffer.GetReference(false) != 0) {
|
||||
pool_mapper.ForceUnmapPointer(workbuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this effect is enabled.
|
||||
*
|
||||
* @return True if effect is enabled, otherwise false.
|
||||
*/
|
||||
bool IsEnabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this effect should not be generated.
|
||||
*
|
||||
* @return True if effect should be skipped, otherwise false.
|
||||
*/
|
||||
bool ShouldSkip() const {
|
||||
return buffer_unmapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of this effect.
|
||||
*
|
||||
* @return The type of this effect. See EffectInfoBase::Type
|
||||
*/
|
||||
Type GetType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of this effect.
|
||||
*
|
||||
* @param type_ - The new type of this effect.
|
||||
*/
|
||||
void SetType(const Type type_) {
|
||||
type = type_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mix id of this effect.
|
||||
*
|
||||
* @return Mix id of this effect.
|
||||
*/
|
||||
s32 GetMixId() const {
|
||||
return mix_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the processing order of this effect.
|
||||
*
|
||||
* @return Process order of this effect.
|
||||
*/
|
||||
s32 GetProcessingOrder() const {
|
||||
return process_order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this effect's parameter data.
|
||||
*
|
||||
* @return Pointer to the parametter, must be cast to the correct type.
|
||||
*/
|
||||
u8* GetParameter() {
|
||||
return parameter.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this effect's parameter data.
|
||||
*
|
||||
* @return Pointer to the parametter, must be cast to the correct type.
|
||||
*/
|
||||
u8* GetStateBuffer() {
|
||||
return state.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this effect's usage state.
|
||||
*
|
||||
* @param usage - new usage state of this effect.
|
||||
*/
|
||||
void SetUsage(const UsageState usage) {
|
||||
usage_state = usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this effects need to have its workbuffer information updated.
|
||||
* Version 1.
|
||||
*
|
||||
* @param params - Input parameters.
|
||||
* @return True if workbuffers need updating, otherwise false.
|
||||
*/
|
||||
bool ShouldUpdateWorkBufferInfo(const InParameterVersion1& params) const {
|
||||
return buffer_unmapped || params.is_new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this effects need to have its workbuffer information updated.
|
||||
* Version 2.
|
||||
*
|
||||
* @param params - Input parameters.
|
||||
* @return True if workbuffers need updating, otherwise false.
|
||||
*/
|
||||
bool ShouldUpdateWorkBufferInfo(const InParameterVersion2& params) const {
|
||||
return buffer_unmapped || params.is_new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current usage state of this effect.
|
||||
*
|
||||
* @return The current usage state.
|
||||
*/
|
||||
UsageState GetUsage() const {
|
||||
return usage_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the current state. Version 1.
|
||||
*
|
||||
* @param out_status - Status to write.
|
||||
* @param renderer_active - Is the AudioRenderer active?
|
||||
*/
|
||||
void StoreStatus(OutStatusVersion1& out_status, const bool renderer_active) const {
|
||||
if (renderer_active) {
|
||||
if (usage_state != UsageState::Disabled) {
|
||||
out_status.state = OutStatus::Used;
|
||||
} else {
|
||||
out_status.state = OutStatus::Removed;
|
||||
}
|
||||
} else if (usage_state == UsageState::New) {
|
||||
out_status.state = OutStatus::Used;
|
||||
} else {
|
||||
out_status.state = OutStatus::Removed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the current state. Version 2.
|
||||
*
|
||||
* @param out_status - Status to write.
|
||||
* @param renderer_active - Is the AudioRenderer active?
|
||||
*/
|
||||
void StoreStatus(OutStatusVersion2& out_status, const bool renderer_active) const {
|
||||
if (renderer_active) {
|
||||
if (usage_state != UsageState::Disabled) {
|
||||
out_status.state = OutStatus::Used;
|
||||
} else {
|
||||
out_status.state = OutStatus::Removed;
|
||||
}
|
||||
} else if (usage_state == UsageState::New) {
|
||||
out_status.state = OutStatus::Used;
|
||||
} else {
|
||||
out_status.state = OutStatus::Removed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
virtual void Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
[[maybe_unused]] const InParameterVersion1& params,
|
||||
[[maybe_unused]] const PoolMapper& pool_mapper) {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
virtual void Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
[[maybe_unused]] const InParameterVersion2& params,
|
||||
[[maybe_unused]] const PoolMapper& pool_mapper) {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
virtual void UpdateForCommandGeneration() {}
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
virtual void InitializeResultState([[maybe_unused]] EffectResultState& result_state) {}
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
virtual void UpdateResultState([[maybe_unused]] EffectResultState& cpu_state,
|
||||
[[maybe_unused]] EffectResultState& dsp_state) {}
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
virtual CpuAddr GetWorkbuffer([[maybe_unused]] s32 index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first workbuffer assigned to this effect.
|
||||
*
|
||||
* @param index - Workbuffer index. Unused.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetSingleBuffer([[maybe_unused]] const s32 index) {
|
||||
if (enabled) {
|
||||
return workbuffers[0].GetReference(true);
|
||||
}
|
||||
|
||||
if (usage_state != UsageState::Disabled) {
|
||||
const auto ref{workbuffers[0].GetReference(false)};
|
||||
const auto size{workbuffers[0].GetSize()};
|
||||
if (ref != 0 && size > 0) {
|
||||
// Invalidate DSP cache
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the send buffer info, used by Aux and Capture.
|
||||
*
|
||||
* @return Address of the buffer info.
|
||||
*/
|
||||
CpuAddr GetSendBufferInfo() const {
|
||||
return send_buffer_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the send buffer, used by Aux and Capture.
|
||||
*
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetSendBuffer() const {
|
||||
return send_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the return buffer info, used by Aux and Capture.
|
||||
*
|
||||
* @return Address of the buffer info.
|
||||
*/
|
||||
CpuAddr GetReturnBufferInfo() const {
|
||||
return return_buffer_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the return buffer, used by Aux and Capture.
|
||||
*
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetReturnBuffer() const {
|
||||
return return_buffer;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Type of this effect. May be changed
|
||||
Type type{Type::Invalid};
|
||||
/// Is this effect enabled?
|
||||
bool enabled{};
|
||||
/// Are this effect's buffers unmapped?
|
||||
bool buffer_unmapped{};
|
||||
/// Current usage state
|
||||
UsageState usage_state{UsageState::Invalid};
|
||||
/// Mix id of this effect
|
||||
s32 mix_id{UnusedMixId};
|
||||
/// Process order of this effect
|
||||
s32 process_order{InvalidProcessOrder};
|
||||
/// Workbuffers assigned to this effect
|
||||
std::array<AddressInfo, 2> workbuffers{AddressInfo(CpuAddr(0), 0), AddressInfo(CpuAddr(0), 0)};
|
||||
/// Aux/Capture buffer info for reading
|
||||
CpuAddr send_buffer_info{};
|
||||
/// Aux/Capture buffer for reading
|
||||
CpuAddr send_buffer{};
|
||||
/// Aux/Capture buffer info for writing
|
||||
CpuAddr return_buffer_info{};
|
||||
/// Aux/Capture buffer for writing
|
||||
CpuAddr return_buffer{};
|
||||
/// Parameters of this effect
|
||||
std::array<u8, sizeof(InParameterVersion2)> parameter{};
|
||||
/// State of this effect used by the AudioRenderer across calls
|
||||
std::array<u8, sizeof(State)> state{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/renderer/effect/aux_.h"
|
||||
#include "audio_core/renderer/effect/biquad_filter.h"
|
||||
#include "audio_core/renderer/effect/buffer_mixer.h"
|
||||
#include "audio_core/renderer/effect/capture.h"
|
||||
#include "audio_core/renderer/effect/compressor.h"
|
||||
#include "audio_core/renderer/effect/delay.h"
|
||||
#include "audio_core/renderer/effect/i3dl2.h"
|
||||
#include "audio_core/renderer/effect/light_limiter.h"
|
||||
#include "audio_core/renderer/effect/reverb.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Reset an effect, and create a new one of the given type.
|
||||
*
|
||||
* @param effect - Effect to reset and re-construct.
|
||||
* @param type - Type of the new effect to create.
|
||||
*/
|
||||
static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type) {
|
||||
*effect = {};
|
||||
|
||||
switch (type) {
|
||||
case EffectInfoBase::Type::Invalid:
|
||||
std::construct_at<EffectInfoBase>(effect);
|
||||
effect->SetType(EffectInfoBase::Type::Invalid);
|
||||
break;
|
||||
case EffectInfoBase::Type::Mix:
|
||||
std::construct_at<BufferMixerInfo>(reinterpret_cast<BufferMixerInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Mix);
|
||||
break;
|
||||
case EffectInfoBase::Type::Aux:
|
||||
std::construct_at<AuxInfo>(reinterpret_cast<AuxInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Aux);
|
||||
break;
|
||||
case EffectInfoBase::Type::Delay:
|
||||
std::construct_at<DelayInfo>(reinterpret_cast<DelayInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Delay);
|
||||
break;
|
||||
case EffectInfoBase::Type::Reverb:
|
||||
std::construct_at<ReverbInfo>(reinterpret_cast<ReverbInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Reverb);
|
||||
break;
|
||||
case EffectInfoBase::Type::I3dl2Reverb:
|
||||
std::construct_at<I3dl2ReverbInfo>(reinterpret_cast<I3dl2ReverbInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::I3dl2Reverb);
|
||||
break;
|
||||
case EffectInfoBase::Type::BiquadFilter:
|
||||
std::construct_at<BiquadFilterInfo>(reinterpret_cast<BiquadFilterInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::BiquadFilter);
|
||||
break;
|
||||
case EffectInfoBase::Type::LightLimiter:
|
||||
std::construct_at<LightLimiterInfo>(reinterpret_cast<LightLimiterInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::LightLimiter);
|
||||
break;
|
||||
case EffectInfoBase::Type::Capture:
|
||||
std::construct_at<CaptureInfo>(reinterpret_cast<CaptureInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Capture);
|
||||
break;
|
||||
case EffectInfoBase::Type::Compressor:
|
||||
std::construct_at<CompressorInfo>(reinterpret_cast<CompressorInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Compressor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/renderer/effect/aux_.h"
|
||||
#include "audio_core/renderer/effect/biquad_filter.h"
|
||||
#include "audio_core/renderer/effect/buffer_mixer.h"
|
||||
#include "audio_core/renderer/effect/capture.h"
|
||||
#include "audio_core/renderer/effect/compressor.h"
|
||||
#include "audio_core/renderer/effect/delay.h"
|
||||
#include "audio_core/renderer/effect/i3dl2.h"
|
||||
#include "audio_core/renderer/effect/light_limiter.h"
|
||||
#include "audio_core/renderer/effect/reverb.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Reset an effect, and create a new one of the given type.
|
||||
*
|
||||
* @param effect - Effect to reset and re-construct.
|
||||
* @param type - Type of the new effect to create.
|
||||
*/
|
||||
static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type) {
|
||||
*effect = {};
|
||||
|
||||
switch (type) {
|
||||
case EffectInfoBase::Type::Invalid:
|
||||
std::construct_at<EffectInfoBase>(effect);
|
||||
effect->SetType(EffectInfoBase::Type::Invalid);
|
||||
break;
|
||||
case EffectInfoBase::Type::Mix:
|
||||
std::construct_at<BufferMixerInfo>(reinterpret_cast<BufferMixerInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Mix);
|
||||
break;
|
||||
case EffectInfoBase::Type::Aux:
|
||||
std::construct_at<AuxInfo>(reinterpret_cast<AuxInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Aux);
|
||||
break;
|
||||
case EffectInfoBase::Type::Delay:
|
||||
std::construct_at<DelayInfo>(reinterpret_cast<DelayInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Delay);
|
||||
break;
|
||||
case EffectInfoBase::Type::Reverb:
|
||||
std::construct_at<ReverbInfo>(reinterpret_cast<ReverbInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Reverb);
|
||||
break;
|
||||
case EffectInfoBase::Type::I3dl2Reverb:
|
||||
std::construct_at<I3dl2ReverbInfo>(reinterpret_cast<I3dl2ReverbInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::I3dl2Reverb);
|
||||
break;
|
||||
case EffectInfoBase::Type::BiquadFilter:
|
||||
std::construct_at<BiquadFilterInfo>(reinterpret_cast<BiquadFilterInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::BiquadFilter);
|
||||
break;
|
||||
case EffectInfoBase::Type::LightLimiter:
|
||||
std::construct_at<LightLimiterInfo>(reinterpret_cast<LightLimiterInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::LightLimiter);
|
||||
break;
|
||||
case EffectInfoBase::Type::Capture:
|
||||
std::construct_at<CaptureInfo>(reinterpret_cast<CaptureInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Capture);
|
||||
break;
|
||||
case EffectInfoBase::Type::Compressor:
|
||||
std::construct_at<CompressorInfo>(reinterpret_cast<CompressorInfo*>(effect));
|
||||
effect->SetType(EffectInfoBase::Type::Compressor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
struct EffectResultState {
|
||||
std::array<u8, 0x80> state;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
struct EffectResultState {
|
||||
std::array<u8, 0x80> state;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,94 +1,94 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/i3dl2.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
if (IsChannelCountValid(in_specific->channel_count_max)) {
|
||||
const auto old_state{params->state};
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count)) {
|
||||
params->channel_count = params->channel_count_max;
|
||||
}
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count) ||
|
||||
old_state != ParameterState::Updated) {
|
||||
params->state = old_state;
|
||||
}
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
if (IsChannelCountValid(in_specific->channel_count_max)) {
|
||||
const auto old_state{params->state};
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count)) {
|
||||
params->channel_count = params->channel_count_max;
|
||||
}
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count) ||
|
||||
old_state != ParameterState::Updated) {
|
||||
params->state = old_state;
|
||||
}
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void I3dl2ReverbInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
}
|
||||
|
||||
void I3dl2ReverbInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void I3dl2ReverbInfo::UpdateResultState(EffectResultState& cpu_state,
|
||||
EffectResultState& dsp_state) {}
|
||||
|
||||
CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) {
|
||||
return GetSingleBuffer(index);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/i3dl2.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
if (IsChannelCountValid(in_specific->channel_count_max)) {
|
||||
const auto old_state{params->state};
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count)) {
|
||||
params->channel_count = params->channel_count_max;
|
||||
}
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count) ||
|
||||
old_state != ParameterState::Updated) {
|
||||
params->state = old_state;
|
||||
}
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
if (IsChannelCountValid(in_specific->channel_count_max)) {
|
||||
const auto old_state{params->state};
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count)) {
|
||||
params->channel_count = params->channel_count_max;
|
||||
}
|
||||
|
||||
if (!IsChannelCountValid(in_specific->channel_count) ||
|
||||
old_state != ParameterState::Updated) {
|
||||
params->state = old_state;
|
||||
}
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void I3dl2ReverbInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
}
|
||||
|
||||
void I3dl2ReverbInfo::InitializeResultState(EffectResultState& result_state) {}
|
||||
|
||||
void I3dl2ReverbInfo::UpdateResultState(EffectResultState& cpu_state,
|
||||
EffectResultState& dsp_state) {}
|
||||
|
||||
CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) {
|
||||
return GetSingleBuffer(index);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,200 +1,200 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class I3dl2ReverbInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ u16 channel_count_max;
|
||||
/* 0x0E */ u16 channel_count;
|
||||
/* 0x10 */ char unk10[0x4];
|
||||
/* 0x14 */ u32 sample_rate;
|
||||
/* 0x18 */ f32 room_HF_gain;
|
||||
/* 0x1C */ f32 reference_HF;
|
||||
/* 0x20 */ f32 late_reverb_decay_time;
|
||||
/* 0x24 */ f32 late_reverb_HF_decay_ratio;
|
||||
/* 0x28 */ f32 room_gain;
|
||||
/* 0x2C */ f32 reflection_gain;
|
||||
/* 0x30 */ f32 reverb_gain;
|
||||
/* 0x34 */ f32 late_reverb_diffusion;
|
||||
/* 0x38 */ f32 reflection_delay;
|
||||
/* 0x3C */ f32 late_reverb_delay_time;
|
||||
/* 0x40 */ f32 late_reverb_density;
|
||||
/* 0x44 */ f32 dry_gain;
|
||||
/* 0x48 */ ParameterState state;
|
||||
/* 0x49 */ char unk49[0x3];
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"I3dl2ReverbInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ u16 channel_count_max;
|
||||
/* 0x0E */ u16 channel_count;
|
||||
/* 0x10 */ char unk10[0x4];
|
||||
/* 0x14 */ u32 sample_rate;
|
||||
/* 0x18 */ f32 room_HF_gain;
|
||||
/* 0x1C */ f32 reference_HF;
|
||||
/* 0x20 */ f32 late_reverb_decay_time;
|
||||
/* 0x24 */ f32 late_reverb_HF_decay_ratio;
|
||||
/* 0x28 */ f32 room_gain;
|
||||
/* 0x2C */ f32 reflection_gain;
|
||||
/* 0x30 */ f32 reverb_gain;
|
||||
/* 0x34 */ f32 late_reverb_diffusion;
|
||||
/* 0x38 */ f32 reflection_delay;
|
||||
/* 0x3C */ f32 late_reverb_delay_time;
|
||||
/* 0x40 */ f32 late_reverb_density;
|
||||
/* 0x44 */ f32 dry_gain;
|
||||
/* 0x48 */ ParameterState state;
|
||||
/* 0x49 */ char unk49[0x3];
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"I3dl2ReverbInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
static constexpr u32 MaxDelayLines = 4;
|
||||
static constexpr u32 MaxDelayTaps = 20;
|
||||
|
||||
struct I3dl2DelayLine {
|
||||
void Initialize(const s32 delay_time) {
|
||||
max_delay = delay_time;
|
||||
buffer.resize(delay_time + 1, 0);
|
||||
buffer_end = &buffer[delay_time];
|
||||
output = &buffer[0];
|
||||
SetDelay(delay_time);
|
||||
wet_gain = 0.0f;
|
||||
}
|
||||
|
||||
void SetDelay(const s32 delay_time) {
|
||||
if (max_delay < delay_time) {
|
||||
return;
|
||||
}
|
||||
delay = delay_time;
|
||||
input = &buffer[(output - buffer.data() + delay) % (max_delay + 1)];
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) {
|
||||
Write(sample);
|
||||
|
||||
auto out_sample{Read()};
|
||||
|
||||
output++;
|
||||
if (output >= buffer_end) {
|
||||
output = buffer.data();
|
||||
}
|
||||
|
||||
return out_sample;
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> Read() const {
|
||||
return *output;
|
||||
}
|
||||
|
||||
void Write(const Common::FixedPoint<50, 14> sample) {
|
||||
*(input++) = sample;
|
||||
if (input >= buffer_end) {
|
||||
input = buffer.data();
|
||||
}
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> TapOut(const s32 index) const {
|
||||
auto out{input - (index + 1)};
|
||||
if (out < buffer.data()) {
|
||||
out += max_delay + 1;
|
||||
}
|
||||
return *out;
|
||||
}
|
||||
|
||||
std::vector<Common::FixedPoint<50, 14>> buffer{};
|
||||
Common::FixedPoint<50, 14>* buffer_end{};
|
||||
s32 max_delay{};
|
||||
Common::FixedPoint<50, 14>* input{};
|
||||
Common::FixedPoint<50, 14>* output{};
|
||||
s32 delay{};
|
||||
f32 wet_gain{};
|
||||
};
|
||||
|
||||
struct State {
|
||||
f32 lowpass_0;
|
||||
f32 lowpass_1;
|
||||
f32 lowpass_2;
|
||||
I3dl2DelayLine early_delay_line;
|
||||
std::array<s32, MaxDelayTaps> early_tap_steps;
|
||||
f32 early_gain;
|
||||
f32 late_gain;
|
||||
s32 early_to_late_taps;
|
||||
std::array<I3dl2DelayLine, MaxDelayLines> fdn_delay_lines;
|
||||
std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines0;
|
||||
std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines1;
|
||||
f32 last_reverb_echo;
|
||||
I3dl2DelayLine center_delay_line;
|
||||
std::array<std::array<f32, 3>, MaxDelayLines> lowpass_coeff;
|
||||
std::array<f32, MaxDelayLines> shelf_filter;
|
||||
f32 dry_gain;
|
||||
};
|
||||
static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
|
||||
"I3dl2ReverbInfo::State is too large!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetWorkbuffer(s32 index) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
class I3dl2ReverbInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ u16 channel_count_max;
|
||||
/* 0x0E */ u16 channel_count;
|
||||
/* 0x10 */ char unk10[0x4];
|
||||
/* 0x14 */ u32 sample_rate;
|
||||
/* 0x18 */ f32 room_HF_gain;
|
||||
/* 0x1C */ f32 reference_HF;
|
||||
/* 0x20 */ f32 late_reverb_decay_time;
|
||||
/* 0x24 */ f32 late_reverb_HF_decay_ratio;
|
||||
/* 0x28 */ f32 room_gain;
|
||||
/* 0x2C */ f32 reflection_gain;
|
||||
/* 0x30 */ f32 reverb_gain;
|
||||
/* 0x34 */ f32 late_reverb_diffusion;
|
||||
/* 0x38 */ f32 reflection_delay;
|
||||
/* 0x3C */ f32 late_reverb_delay_time;
|
||||
/* 0x40 */ f32 late_reverb_density;
|
||||
/* 0x44 */ f32 dry_gain;
|
||||
/* 0x48 */ ParameterState state;
|
||||
/* 0x49 */ char unk49[0x3];
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"I3dl2ReverbInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
struct ParameterVersion2 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ u16 channel_count_max;
|
||||
/* 0x0E */ u16 channel_count;
|
||||
/* 0x10 */ char unk10[0x4];
|
||||
/* 0x14 */ u32 sample_rate;
|
||||
/* 0x18 */ f32 room_HF_gain;
|
||||
/* 0x1C */ f32 reference_HF;
|
||||
/* 0x20 */ f32 late_reverb_decay_time;
|
||||
/* 0x24 */ f32 late_reverb_HF_decay_ratio;
|
||||
/* 0x28 */ f32 room_gain;
|
||||
/* 0x2C */ f32 reflection_gain;
|
||||
/* 0x30 */ f32 reverb_gain;
|
||||
/* 0x34 */ f32 late_reverb_diffusion;
|
||||
/* 0x38 */ f32 reflection_delay;
|
||||
/* 0x3C */ f32 late_reverb_delay_time;
|
||||
/* 0x40 */ f32 late_reverb_density;
|
||||
/* 0x44 */ f32 dry_gain;
|
||||
/* 0x48 */ ParameterState state;
|
||||
/* 0x49 */ char unk49[0x3];
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
|
||||
"I3dl2ReverbInfo::ParameterVersion2 has the wrong size!");
|
||||
|
||||
static constexpr u32 MaxDelayLines = 4;
|
||||
static constexpr u32 MaxDelayTaps = 20;
|
||||
|
||||
struct I3dl2DelayLine {
|
||||
void Initialize(const s32 delay_time) {
|
||||
max_delay = delay_time;
|
||||
buffer.resize(delay_time + 1, 0);
|
||||
buffer_end = &buffer[delay_time];
|
||||
output = &buffer[0];
|
||||
SetDelay(delay_time);
|
||||
wet_gain = 0.0f;
|
||||
}
|
||||
|
||||
void SetDelay(const s32 delay_time) {
|
||||
if (max_delay < delay_time) {
|
||||
return;
|
||||
}
|
||||
delay = delay_time;
|
||||
input = &buffer[(output - buffer.data() + delay) % (max_delay + 1)];
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) {
|
||||
Write(sample);
|
||||
|
||||
auto out_sample{Read()};
|
||||
|
||||
output++;
|
||||
if (output >= buffer_end) {
|
||||
output = buffer.data();
|
||||
}
|
||||
|
||||
return out_sample;
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> Read() const {
|
||||
return *output;
|
||||
}
|
||||
|
||||
void Write(const Common::FixedPoint<50, 14> sample) {
|
||||
*(input++) = sample;
|
||||
if (input >= buffer_end) {
|
||||
input = buffer.data();
|
||||
}
|
||||
}
|
||||
|
||||
Common::FixedPoint<50, 14> TapOut(const s32 index) const {
|
||||
auto out{input - (index + 1)};
|
||||
if (out < buffer.data()) {
|
||||
out += max_delay + 1;
|
||||
}
|
||||
return *out;
|
||||
}
|
||||
|
||||
std::vector<Common::FixedPoint<50, 14>> buffer{};
|
||||
Common::FixedPoint<50, 14>* buffer_end{};
|
||||
s32 max_delay{};
|
||||
Common::FixedPoint<50, 14>* input{};
|
||||
Common::FixedPoint<50, 14>* output{};
|
||||
s32 delay{};
|
||||
f32 wet_gain{};
|
||||
};
|
||||
|
||||
struct State {
|
||||
f32 lowpass_0;
|
||||
f32 lowpass_1;
|
||||
f32 lowpass_2;
|
||||
I3dl2DelayLine early_delay_line;
|
||||
std::array<s32, MaxDelayTaps> early_tap_steps;
|
||||
f32 early_gain;
|
||||
f32 late_gain;
|
||||
s32 early_to_late_taps;
|
||||
std::array<I3dl2DelayLine, MaxDelayLines> fdn_delay_lines;
|
||||
std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines0;
|
||||
std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines1;
|
||||
f32 last_reverb_echo;
|
||||
I3dl2DelayLine center_delay_line;
|
||||
std::array<std::array<f32, 3>, MaxDelayLines> lowpass_coeff;
|
||||
std::array<f32, MaxDelayLines> shelf_filter;
|
||||
f32 dry_gain;
|
||||
};
|
||||
static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
|
||||
"I3dl2ReverbInfo::State is too large!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 1.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info with new parameters, version 2.
|
||||
*
|
||||
* @param error_info - Used to write call result code.
|
||||
* @param in_params - New parameters to update the info with.
|
||||
* @param pool_mapper - Pool for mapping buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) override;
|
||||
|
||||
/**
|
||||
* Update the info after command generation. Usually only changes its state.
|
||||
*/
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
/**
|
||||
* Initialize a new result state. Version 2 only, unused.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state) override;
|
||||
|
||||
/**
|
||||
* Update the host-side state with the ADSP-side state. Version 2 only, unused.
|
||||
*
|
||||
* @param cpu_state - Host-side result state to update.
|
||||
* @param dsp_state - AudioRenderer-side result state to update from.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
|
||||
|
||||
/**
|
||||
* Get a workbuffer assigned to this effect with the given index.
|
||||
*
|
||||
* @param index - Workbuffer index.
|
||||
* @return Address of the buffer.
|
||||
*/
|
||||
CpuAddr GetWorkbuffer(s32 index) override;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/light_limiter.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void LightLimiterInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
params->statistics_reset_required = false;
|
||||
}
|
||||
|
||||
void LightLimiterInfo::InitializeResultState(EffectResultState& result_state) {
|
||||
auto result_state_{reinterpret_cast<StatisticsInternal*>(result_state.state.data())};
|
||||
|
||||
result_state_->channel_max_sample.fill(0);
|
||||
result_state_->channel_compression_gain_min.fill(1.0f);
|
||||
}
|
||||
|
||||
void LightLimiterInfo::UpdateResultState(EffectResultState& cpu_state,
|
||||
EffectResultState& dsp_state) {
|
||||
auto cpu_statistics{reinterpret_cast<StatisticsInternal*>(cpu_state.state.data())};
|
||||
auto dsp_statistics{reinterpret_cast<StatisticsInternal*>(dsp_state.state.data())};
|
||||
|
||||
*cpu_statistics = *dsp_statistics;
|
||||
}
|
||||
|
||||
CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) {
|
||||
return GetSingleBuffer(index);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/light_limiter.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
if (buffer_unmapped || in_params.is_new) {
|
||||
usage_state = UsageState::New;
|
||||
params->state = ParameterState::Initialized;
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(
|
||||
error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void LightLimiterInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
params->statistics_reset_required = false;
|
||||
}
|
||||
|
||||
void LightLimiterInfo::InitializeResultState(EffectResultState& result_state) {
|
||||
auto result_state_{reinterpret_cast<StatisticsInternal*>(result_state.state.data())};
|
||||
|
||||
result_state_->channel_max_sample.fill(0);
|
||||
result_state_->channel_compression_gain_min.fill(1.0f);
|
||||
}
|
||||
|
||||
void LightLimiterInfo::UpdateResultState(EffectResultState& cpu_state,
|
||||
EffectResultState& dsp_state) {
|
||||
auto cpu_statistics{reinterpret_cast<StatisticsInternal*>(cpu_state.state.data())};
|
||||
auto dsp_statistics{reinterpret_cast<StatisticsInternal*>(dsp_state.state.data())};
|
||||
|
||||
*cpu_statistics = *dsp_statistics;
|
||||
}
|
||||
|
||||
CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) {
|
||||
return GetSingleBuffer(index);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user