summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/audio_core/audio_renderer.cpp9
-rw-r--r--src/audio_core/command_generator.cpp187
-rw-r--r--src/audio_core/command_generator.h17
-rw-r--r--src/audio_core/common.h1
-rw-r--r--src/audio_core/effect_context.cpp271
-rw-r--r--src/audio_core/effect_context.h215
-rw-r--r--src/audio_core/info_updater.cpp18
-rw-r--r--src/audio_core/info_updater.h2
-rw-r--r--src/audio_core/mix_context.cpp35
-rw-r--r--src/audio_core/mix_context.h11
10 files changed, 731 insertions, 35 deletions
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index a3ff819e1..56dc892b1 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -27,12 +27,13 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
sink_context(params.sink_count), splitter_context(),
voices(params.voice_count), memory{memory_},
- command_generator(worker_params, voice_context, mix_context, splitter_context, memory),
+ command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
+ memory),
temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) {
behavior_info.SetUserRevision(params.revision);
splitter_context.Initialize(behavior_info, params.splitter_count,
params.num_splitter_send_channels);
- mix_context.Initialize(behavior_info, params.submix_count + 1);
+ mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
audio_out = std::make_unique<AudioCore::AudioOut>();
stream =
audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
@@ -106,8 +107,8 @@ ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_param
}
}
- auto mix_result =
- info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, splitter_context);
+ auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count,
+ splitter_context, effect_context);
if (mix_result.IsError()) {
LOG_ERROR(Audio, "Failed to update mix parameters");
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
index 73608c9ed..84782cde6 100644
--- a/src/audio_core/command_generator.cpp
+++ b/src/audio_core/command_generator.cpp
@@ -4,6 +4,7 @@
#include "audio_core/algorithm/interpolate.h"
#include "audio_core/command_generator.h"
+#include "audio_core/effect_context.h"
#include "audio_core/mix_context.h"
#include "audio_core/voice_context.h"
#include "core/memory.h"
@@ -68,9 +69,10 @@ s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) {
CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
VoiceContext& voice_context, MixContext& mix_context,
- SplitterContext& splitter_context, Core::Memory::Memory& memory)
+ SplitterContext& splitter_context, EffectContext& effect_context,
+ Core::Memory::Memory& memory)
: worker_params(worker_params), voice_context(voice_context), mix_context(mix_context),
- splitter_context(splitter_context), memory(memory),
+ splitter_context(splitter_context), effect_context(effect_context), memory(memory),
mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
worker_params.sample_count),
sample_buffer(MIX_BUFFER_SIZE),
@@ -338,6 +340,120 @@ void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_
}
}
+void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
+ const std::size_t effect_count = effect_context.GetCount();
+ const auto buffer_offset = mix_info.GetInParams().buffer_offset;
+ for (std::size_t i = 0; i < effect_count; i++) {
+ const auto index = mix_info.GetEffectOrder(i);
+ if (index == AudioCommon::NO_EFFECT_ORDER) {
+ break;
+ }
+ auto* info = effect_context.GetInfo(index);
+ const auto type = info->GetType();
+
+ // TODO(ogniK): Finish remaining effects
+ switch (type) {
+ case EffectType::Aux:
+ GenerateAuxCommand(buffer_offset, info, info->IsEnabled());
+ break;
+ case EffectType::I3dl2Reverb:
+ GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled());
+ break;
+ case EffectType::BiquadFilter:
+ GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled());
+ break;
+ default:
+ break;
+ }
+
+ info->UpdateForCommandGeneration();
+ }
+}
+
+void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
+ bool enabled) {
+ if (!enabled) {
+ return;
+ }
+ const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams();
+ const auto channel_count = params.channel_count;
+ for (s32 i = 0; i < channel_count; i++) {
+ // TODO(ogniK): Actually implement reverb
+ if (params.input[i] != params.output[i]) {
+ const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
+ auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
+ ApplyMix<1>(output, input, 32768, worker_params.sample_count);
+ }
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info,
+ bool enabled) {
+ if (!enabled) {
+ return;
+ }
+ const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams();
+ const auto channel_count = params.channel_count;
+ for (s32 i = 0; i < channel_count; i++) {
+ // TODO(ogniK): Actually implement biquad filter
+ if (params.input[i] != params.output[i]) {
+ const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
+ auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
+ ApplyMix<1>(output, input, 32768, worker_params.sample_count);
+ }
+ }
+}
+
+void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
+ auto aux = dynamic_cast<EffectAuxInfo*>(info);
+ const auto& params = aux->GetParams();
+ if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
+ const auto max_channels = params.count;
+ u32 offset{};
+ for (u32 channel = 0; channel < max_channels; channel++) {
+ u32 write_count = 0;
+ if (channel == (max_channels - 1)) {
+ write_count = offset + worker_params.sample_count;
+ }
+
+ const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset;
+ const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset;
+
+ if (enabled) {
+ AuxInfoDSP send_info{};
+ AuxInfoDSP recv_info{};
+ memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
+ memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
+
+ WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count,
+ GetMixBuffer(input_index), worker_params.sample_count, offset,
+ write_count);
+ memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
+
+ const auto samples_read = ReadAuxBuffer(
+ recv_info, aux->GetRecvBuffer(), params.sample_count,
+ GetMixBuffer(output_index), worker_params.sample_count, offset, write_count);
+ memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
+
+ if (samples_read != worker_params.sample_count &&
+ samples_read <= params.sample_count) {
+ std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read);
+ }
+ } else {
+ AuxInfoDSP empty{};
+ memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP));
+ memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP));
+ if (output_index != input_index) {
+ std::memcpy(GetMixBuffer(output_index), GetMixBuffer(input_index),
+ worker_params.sample_count * sizeof(s32));
+ }
+ }
+
+ offset += worker_params.sample_count;
+ }
+ }
+}
+
ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
if (splitter_id == AudioCommon::NO_SPLITTER) {
return nullptr;
@@ -345,6 +461,66 @@ ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter
return splitter_context.GetDestinationData(splitter_id, index);
}
+s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
+ const s32* data, u32 sample_count, u32 write_offset,
+ u32 write_count) {
+ if (max_samples == 0) {
+ return 0;
+ }
+ u32 offset = dsp_info.write_offset + write_offset;
+ if (send_buffer == 0 || offset > max_samples) {
+ return 0;
+ }
+
+ std::size_t data_offset{};
+ u32 remaining = sample_count;
+ while (remaining > 0) {
+ // Get position in buffer
+ const auto base = send_buffer + (offset * sizeof(u32));
+ const auto samples_to_grab = std::min(max_samples - offset, remaining);
+ // Write to output
+ memory.WriteBlock(base, (data + data_offset), samples_to_grab * sizeof(u32));
+ offset = (offset + samples_to_grab) % max_samples;
+ remaining -= samples_to_grab;
+ data_offset += samples_to_grab;
+ }
+
+ if (write_count != 0) {
+ dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples;
+ }
+ return sample_count;
+}
+
+s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
+ s32* out_data, u32 sample_count, u32 read_offset,
+ u32 read_count) {
+ if (max_samples == 0) {
+ return 0;
+ }
+
+ u32 offset = recv_info.read_offset + read_offset;
+ if (recv_buffer == 0 || offset > max_samples) {
+ return 0;
+ }
+
+ u32 remaining = sample_count;
+ while (remaining > 0) {
+ const auto base = recv_buffer + (offset * sizeof(u32));
+ const auto samples_to_grab = std::min(max_samples - offset, remaining);
+ std::vector<s32> buffer(samples_to_grab);
+ memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32));
+ std::memcpy(out_data, buffer.data(), buffer.size() * sizeof(u32));
+ out_data += samples_to_grab;
+ offset = (offset + samples_to_grab) % max_samples;
+ remaining -= samples_to_grab;
+ }
+
+ if (read_count != 0) {
+ recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples;
+ }
+ return sample_count;
+}
+
void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
s32 channel, s32 node_id) {
const auto last = static_cast<s32>(last_volume * 32768.0f);
@@ -398,7 +574,9 @@ void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
auto& in_params = mix_info.GetInParams();
GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
in_params.sample_rate);
- // TODO(ogniK): Effects
+
+ GenerateEffectCommand(mix_info);
+
GenerateMixCommands(mix_info);
}
@@ -476,7 +654,8 @@ void CommandGenerator::GenerateFinalMixCommand() {
GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
in_params.sample_rate);
- // TODO(ogniK): Effects
+
+ GenerateEffectCommand(mix_info);
for (s32 i = 0; i < in_params.buffer_count; i++) {
const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h
index 656ad8143..967d24078 100644
--- a/src/audio_core/command_generator.h
+++ b/src/audio_core/command_generator.h
@@ -19,14 +19,17 @@ class MixContext;
class SplitterContext;
class ServerSplitterDestinationData;
class ServerMixInfo;
-
+class EffectContext;
+class EffectBase;
+struct AuxInfoDSP;
using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
class CommandGenerator {
public:
explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
VoiceContext& voice_context, MixContext& mix_context,
- SplitterContext& splitter_context, Core::Memory::Memory& memory);
+ SplitterContext& splitter_context, EffectContext& effect_context,
+ Core::Memory::Memory& memory);
~CommandGenerator();
void ClearMixBuffers();
@@ -67,8 +70,17 @@ private:
std::size_t mix_buffer_offset);
void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
std::size_t mix_buffer_offset, s32 sample_rate);
+ void GenerateEffectCommand(ServerMixInfo& mix_info);
+ void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
+ void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
+ void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
+ s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data,
+ u32 sample_count, u32 write_offset, u32 write_count);
+ s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
+ u32 sample_count, u32 read_offset, u32 read_count);
+
// DSP Code
s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
s32 channel, std::size_t mix_offset);
@@ -81,6 +93,7 @@ private:
VoiceContext& voice_context;
MixContext& mix_context;
SplitterContext& splitter_context;
+ EffectContext& effect_context;
Core::Memory::Memory& memory;
std::vector<s32> mix_buffer{};
std::vector<s32> sample_buffer{};
diff --git a/src/audio_core/common.h b/src/audio_core/common.h
index 0731d3eb3..72ebce221 100644
--- a/src/audio_core/common.h
+++ b/src/audio_core/common.h
@@ -26,6 +26,7 @@ constexpr s32 NO_SPLITTER = -1;
constexpr s32 NO_MIX = 0x7fffffff;
constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
constexpr s32 FINAL_MIX = 0;
+constexpr s32 NO_EFFECT_ORDER = -1;
constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
// Any size checks seem to take the sample history into account
// and our const ends up being 0x3f04, the 4 bytes are most
diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp
index 2497d2f32..adfec3df5 100644
--- a/src/audio_core/effect_context.cpp
+++ b/src/audio_core/effect_context.cpp
@@ -6,6 +6,12 @@
#include "audio_core/effect_context.h"
namespace AudioCore {
+namespace {
+bool ValidChannelCountForEffect(s32 channel_count) {
+ return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6;
+}
+} // namespace
+
EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) {
effects.reserve(effect_count);
std::generate_n(std::back_inserter(effects), effect_count,
@@ -21,24 +27,273 @@ EffectBase* EffectContext::GetInfo(std::size_t i) {
return effects.at(i).get();
}
+EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) {
+ switch (effect) {
+ case EffectType::Invalid:
+ effects[i] = std::make_unique<EffectStubbed>();
+ break;
+ case EffectType::BufferMixer:
+ effects[i] = std::make_unique<EffectBufferMixer>();
+ break;
+ case EffectType::Aux:
+ effects[i] = std::make_unique<EffectAuxInfo>();
+ break;
+ case EffectType::Delay:
+ effects[i] = std::make_unique<EffectDelay>();
+ break;
+ case EffectType::Reverb:
+ effects[i] = std::make_unique<EffectReverb>();
+ break;
+ case EffectType::I3dl2Reverb:
+ effects[i] = std::make_unique<EffectI3dl2Reverb>();
+ break;
+ case EffectType::BiquadFilter:
+ effects[i] = std::make_unique<EffectBiquadFilter>();
+ break;
+ default:
+ UNREACHABLE_MSG("Unimplemented effect {}", effect);
+ effects[i] = std::make_unique<EffectStubbed>();
+ }
+ return GetInfo(i);
+}
+
const EffectBase* EffectContext::GetInfo(std::size_t i) const {
return effects.at(i).get();
}
-EffectStubbed::EffectStubbed() : EffectBase::EffectBase() {}
+EffectStubbed::EffectStubbed() : EffectBase::EffectBase(EffectType::Invalid) {}
EffectStubbed::~EffectStubbed() = default;
-void EffectStubbed::Update(EffectInfo::InParams& in_params) {
- if (in_params.is_new) {
- usage = UsageStatus::New;
- }
-}
+void EffectStubbed::Update(EffectInfo::InParams& in_params) {}
+void EffectStubbed::UpdateForCommandGeneration() {}
-EffectBase::EffectBase() = default;
+EffectBase::EffectBase(EffectType effect_type) : effect_type(effect_type) {}
EffectBase::~EffectBase() = default;
-UsageStatus EffectBase::GetUsage() const {
+UsageState EffectBase::GetUsage() const {
return usage;
}
+EffectType EffectBase::GetType() const {
+ return effect_type;
+}
+
+bool EffectBase::IsEnabled() const {
+ return enabled;
+}
+
+s32 EffectBase::GetMixID() const {
+ return mix_id;
+}
+
+s32 EffectBase::GetProcessingOrder() const {
+ return processing_order;
+}
+
+EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric::EffectGeneric(EffectType::I3dl2Reverb) {}
+EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
+
+void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
+ auto& internal_params = GetParams();
+ const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data());
+ if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
+ UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels);
+ return;
+ }
+
+ const auto last_status = internal_params.status;
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ internal_params = *reverb_params;
+ if (!ValidChannelCountForEffect(reverb_params->channel_count)) {
+ internal_params.channel_count = internal_params.max_channels;
+ }
+ enabled = in_params.is_enabled;
+ if (last_status != ParameterStatus::Updated) {
+ internal_params.status = last_status;
+ }
+
+ if (in_params.is_new || skipped) {
+ usage = UsageState::Initialized;
+ internal_params.status = ParameterStatus::Initialized;
+ skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+ }
+}
+
+void EffectI3dl2Reverb::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
+EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric::EffectGeneric(EffectType::BiquadFilter) {}
+EffectBiquadFilter::~EffectBiquadFilter() = default;
+
+void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) {
+ auto& internal_params = GetParams();
+ const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data());
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ internal_params = *biquad_params;
+ enabled = in_params.is_enabled;
+}
+
+void EffectBiquadFilter::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
+EffectAuxInfo::EffectAuxInfo() : EffectGeneric::EffectGeneric(EffectType::Aux) {}
+EffectAuxInfo::~EffectAuxInfo() = default;
+
+void EffectAuxInfo::Update(EffectInfo::InParams& in_params) {
+ const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data());
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ GetParams() = *aux_params;
+ enabled = in_params.is_enabled;
+
+ if (in_params.is_new || skipped) {
+ skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0;
+ if (skipped) {
+ return;
+ }
+
+ // There's two AuxInfos which are an identical size, the first one is managed by the cpu,
+ // the second is managed by the dsp. All we care about is managing the DSP one
+ send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP);
+ send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2);
+
+ recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP);
+ recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2);
+ }
+}
+
+void EffectAuxInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+}
+
+const VAddr EffectAuxInfo::GetSendInfo() const {
+ return send_info;
+}
+
+const VAddr EffectAuxInfo::GetSendBuffer() const {
+ return send_buffer;
+}
+
+const VAddr EffectAuxInfo::GetRecvInfo() const {
+ return recv_info;
+}
+
+const VAddr EffectAuxInfo::GetRecvBuffer() const {
+ return recv_buffer;
+}
+
+EffectDelay::EffectDelay() : EffectGeneric::EffectGeneric(EffectType::Delay) {}
+EffectDelay::~EffectDelay() = default;
+
+void EffectDelay::Update(EffectInfo::InParams& in_params) {
+ const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data());
+ auto& internal_params = GetParams();
+ if (!ValidChannelCountForEffect(delay_params->max_channels)) {
+ return;
+ }
+
+ const auto last_status = internal_params.status;
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ internal_params = *delay_params;
+ if (!ValidChannelCountForEffect(delay_params->channels)) {
+ internal_params.channels = internal_params.max_channels;
+ }
+ enabled = in_params.is_enabled;
+
+ if (last_status != ParameterStatus::Updated) {
+ internal_params.status = last_status;
+ }
+
+ if (in_params.is_new || skipped) {
+ usage = UsageState::Initialized;
+ internal_params.status = ParameterStatus::Initialized;
+ skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+ }
+}
+
+void EffectDelay::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
+EffectBufferMixer::EffectBufferMixer() : EffectGeneric::EffectGeneric(EffectType::BufferMixer) {}
+EffectBufferMixer::~EffectBufferMixer() = default;
+
+void EffectBufferMixer::Update(EffectInfo::InParams& in_params) {
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data());
+ enabled = in_params.is_enabled;
+}
+
+void EffectBufferMixer::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+}
+
+EffectReverb::EffectReverb() : EffectGeneric::EffectGeneric(EffectType::Reverb) {}
+EffectReverb::~EffectReverb() = default;
+
+void EffectReverb::Update(EffectInfo::InParams& in_params) {
+ const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data());
+ auto& internal_params = GetParams();
+ if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
+ return;
+ }
+
+ const auto last_status = internal_params.status;
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ internal_params = *reverb_params;
+ if (!ValidChannelCountForEffect(reverb_params->channels)) {
+ internal_params.channels = internal_params.max_channels;
+ }
+ enabled = in_params.is_enabled;
+
+ if (last_status != ParameterStatus::Updated) {
+ internal_params.status = last_status;
+ }
+
+ if (in_params.is_new || skipped) {
+ usage = UsageState::Initialized;
+ internal_params.status = ParameterStatus::Initialized;
+ skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+ }
+}
+
+void EffectReverb::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
} // namespace AudioCore
diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h
index e3c367296..2f2da72dd 100644
--- a/src/audio_core/effect_context.h
+++ b/src/audio_core/effect_context.h
@@ -31,6 +31,19 @@ enum class UsageStatus : u8 {
Removed = 4,
};
+enum class UsageState {
+ Invalid = 0,
+ Initialized = 1,
+ Running = 2,
+ Stopped = 3,
+};
+
+enum class ParameterStatus : u8 {
+ Initialized = 0,
+ Updating = 1,
+ Updated = 2,
+};
+
struct BufferMixerParams {
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
@@ -39,6 +52,14 @@ struct BufferMixerParams {
};
static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
+struct AuxInfoDSP {
+ u32_le read_offset{};
+ u32_le write_offset{};
+ u32_le remaining{};
+ INSERT_PADDING_WORDS(13);
+};
+static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size");
+
struct AuxInfo {
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
@@ -54,6 +75,81 @@ struct AuxInfo {
};
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
+struct I3dl2ReverbParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ u16_le max_channels{};
+ u16_le channel_count{};
+ INSERT_PADDING_BYTES(1);
+ u32_le sample_rate{};
+ f32 room_hf{};
+ f32 hf_reference{};
+ f32 decay_time{};
+ f32 hf_decay_ratio{};
+ f32 room{};
+ f32 reflection{};
+ f32 reverb{};
+ f32 diffusion{};
+ f32 reflection_delay{};
+ f32 reverb_delay{};
+ f32 density{};
+ f32 dry_gain{};
+ ParameterStatus status{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size");
+
+struct BiquadFilterParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ std::array<s16_le, 3> numerator;
+ std::array<s16_le, 2> denominator;
+ s8 channel_count{};
+ ParameterStatus status{};
+};
+static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size");
+
+struct DelayParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ u16_le max_channels{};
+ u16_le channels{};
+ s32_le max_delay{};
+ s32_le delay{};
+ s32_le sample_rate{};
+ s32_le gain{};
+ s32_le feedback_gain{};
+ s32_le out_gain{};
+ s32_le dry_gain{};
+ s32_le channel_spread{};
+ s32_le low_pass{};
+ ParameterStatus status{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size");
+
+struct ReverbParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ u16_le max_channels{};
+ u16_le channels{};
+ s32_le sample_rate{};
+ s32_le mode0{};
+ s32_le mode0_gain{};
+ s32_le pre_delay{};
+ s32_le mode1{};
+ s32_le mode1_gain{};
+ s32_le decay{};
+ s32_le hf_decay_ratio{};
+ s32_le coloration{};
+ s32_le reverb_gain{};
+ s32_le out_gain{};
+ s32_le dry_gain{};
+ ParameterStatus status{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size");
+
class EffectInfo {
public:
struct InParams {
@@ -64,7 +160,7 @@ public:
s32_le mix_id{};
u64_le buffer_address{};
u64_le buffer_size{};
- s32_le priority{};
+ s32_le processing_order{};
INSERT_PADDING_BYTES(4);
union {
std::array<u8, 0xa0> raw;
@@ -79,16 +175,50 @@ public:
static_assert(sizeof(EffectInfo::OutParams) == 0x10, "OutParams is an invalid size");
};
+struct AuxAddress {
+ VAddr send_dsp_info{};
+ VAddr send_buffer_base{};
+ VAddr return_dsp_info{};
+ VAddr return_buffer_base{};
+};
+
class EffectBase {
public:
- EffectBase();
+ EffectBase(EffectType effect_type);
~EffectBase();
virtual void Update(EffectInfo::InParams& in_params) = 0;
- UsageStatus GetUsage() const;
+ virtual void UpdateForCommandGeneration() = 0;
+ UsageState GetUsage() const;
+ EffectType GetType() const;
+ bool IsEnabled() const;
+ s32 GetMixID() const;
+ s32 GetProcessingOrder() const;
protected:
- UsageStatus usage{UsageStatus::Invalid};
+ UsageState usage{UsageState::Invalid};
+ EffectType effect_type{};
+ s32 mix_id{};
+ s32 processing_order{};
+ bool enabled = false;
+};
+
+template <typename T>
+class EffectGeneric : public EffectBase {
+public:
+ EffectGeneric(EffectType effect_type) : EffectBase::EffectBase(effect_type) {}
+ ~EffectGeneric() = default;
+
+ T& GetParams() {
+ return internal_params;
+ }
+
+ const I3dl2ReverbParams& GetParams() const {
+ return internal_params;
+ }
+
+private:
+ T internal_params{};
};
class EffectStubbed : public EffectBase {
@@ -97,6 +227,82 @@ public:
~EffectStubbed();
void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+};
+
+class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
+public:
+ explicit EffectI3dl2Reverb();
+ ~EffectI3dl2Reverb();
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+
+private:
+ bool skipped = false;
+};
+
+class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
+public:
+ explicit EffectBiquadFilter();
+ ~EffectBiquadFilter();
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+};
+
+class EffectAuxInfo : public EffectGeneric<AuxInfo> {
+public:
+ explicit EffectAuxInfo();
+ ~EffectAuxInfo();
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+ const VAddr GetSendInfo() const;
+ const VAddr GetSendBuffer() const;
+ const VAddr GetRecvInfo() const;
+ const VAddr GetRecvBuffer() const;
+
+private:
+ VAddr send_info{};
+ VAddr send_buffer{};
+ VAddr recv_info{};
+ VAddr recv_buffer{};
+ bool skipped = false;
+ AuxAddress addresses{};
+};
+
+class EffectDelay : public EffectGeneric<DelayParams> {
+public:
+ explicit EffectDelay();
+ ~EffectDelay();
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+
+private:
+ bool skipped = false;
+};
+
+class EffectBufferMixer : public EffectGeneric<BufferMixerParams> {
+public:
+ explicit EffectBufferMixer();
+ ~EffectBufferMixer();
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+};
+
+class EffectReverb : public EffectGeneric<ReverbParams> {
+public:
+ explicit EffectReverb();
+ ~EffectReverb();
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+
+private:
+ bool skipped = false;
};
class EffectContext {
@@ -106,6 +312,7 @@ public:
std::size_t GetCount() const;
EffectBase* GetInfo(std::size_t i);
+ EffectBase* RetargetEffect(std::size_t i, EffectType effect);
const EffectBase* GetInfo(std::size_t i) const;
private:
diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp
index 286aa0321..f53ce21a5 100644
--- a/src/audio_core/info_updater.cpp
+++ b/src/audio_core/info_updater.cpp
@@ -244,14 +244,15 @@ bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
// Update effects
for (std::size_t i = 0; i < effect_count; i++) {
auto* info = effect_context.GetInfo(i);
+ if (effect_in[i].type != info->GetType()) {
+ info = effect_context.RetargetEffect(i, effect_in[i].type);
+ }
+
info->Update(effect_in[i]);
- // TODO(ogniK): Update individual effects
- if ((!is_active && info->GetUsage() != UsageStatus::New) ||
- info->GetUsage() == UsageStatus::Removed) {
+ if ((!is_active && info->GetUsage() != UsageState::Initialized) ||
+ info->GetUsage() == UsageState::Stopped) {
effect_out[i].status = UsageStatus::Removed;
- } else if (info->GetUsage() == UsageStatus::New) {
- effect_out[i].status = UsageStatus::New;
} else {
effect_out[i].status = UsageStatus::Used;
}
@@ -290,7 +291,8 @@ bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
}
ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
- SplitterContext& splitter_context) {
+ SplitterContext& splitter_context,
+ EffectContext& effect_context) {
std::vector<MixInfo::InParams> mix_in_params;
if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
@@ -387,13 +389,13 @@ ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buf
auto& mix_info_params = mix_info.GetInParams();
if (mix_info_params.in_use != mix_in.in_use) {
mix_info_params.in_use = mix_in.in_use;
- // TODO(ogniK): Update effect processing order
+ mix_info.ResetEffectProcessingOrder();
should_sort = true;
}
if (mix_in.in_use) {
should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
- splitter_context);
+ splitter_context, effect_context);
}
}
diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h
index 6969de67d..06f9d770f 100644
--- a/src/audio_core/info_updater.h
+++ b/src/audio_core/info_updater.h
@@ -34,7 +34,7 @@ public:
bool UpdateEffects(EffectContext& effect_context, bool is_active);
bool UpdateSplitterInfo(SplitterContext& splitter_context);
ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
- SplitterContext& splitter_context);
+ SplitterContext& splitter_context, EffectContext& effect_context);
bool UpdateSinks(SinkContext& sink_context);
bool UpdatePerformanceBuffer();
bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp
index f6f119a11..042891490 100644
--- a/src/audio_core/mix_context.cpp
+++ b/src/audio_core/mix_context.cpp
@@ -4,6 +4,7 @@
#include "audio_core/behavior_info.h"
#include "audio_core/common.h"
+#include "audio_core/effect_context.h"
#include "audio_core/mix_context.h"
#include "audio_core/splitter_context.h"
@@ -11,7 +12,8 @@ namespace AudioCore {
MixContext::MixContext() = default;
MixContext::~MixContext() = default;
-void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count) {
+void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
+ std::size_t effect_count) {
info_count = mix_count;
infos.resize(info_count);
auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
@@ -21,6 +23,10 @@ void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_c
sorted_info.push_back(&info);
}
+ for (auto& info : infos) {
+ info.SetEffectCount(effect_count);
+ }
+
// Only initialize our edge matrix and node states if splitters are supported
if (behavior_info.IsSplitterSupported()) {
node_states.Initialize(mix_count);
@@ -185,7 +191,8 @@ ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
}
bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
- BehaviorInfo& behavior_info, SplitterContext& splitter_context) {
+ BehaviorInfo& behavior_info, SplitterContext& splitter_context,
+ EffectContext& effect_context) {
in_params.volume = mix_in.volume;
in_params.sample_rate = mix_in.sample_rate;
in_params.buffer_count = mix_in.buffer_count;
@@ -206,6 +213,15 @@ bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix
in_params.splitter_id = AudioCommon::NO_SPLITTER;
}
+ ResetEffectProcessingOrder();
+ const auto effect_count = effect_context.GetCount();
+ for (std::size_t i = 0; i < effect_count; i++) {
+ auto* effect_info = effect_context.GetInfo(i);
+ if (effect_info->GetMixID() == in_params.mix_id) {
+ effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
+ }
+ }
+
// TODO(ogniK): Update effect processing order
return require_sort;
}
@@ -228,6 +244,21 @@ void ServerMixInfo::Cleanup() {
std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
}
+void ServerMixInfo::SetEffectCount(std::size_t count) {
+ effect_processing_order.resize(count);
+ ResetEffectProcessingOrder();
+}
+
+void ServerMixInfo::ResetEffectProcessingOrder() {
+ for (auto& order : effect_processing_order) {
+ order = AudioCommon::NO_EFFECT_ORDER;
+ }
+}
+
+s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
+ return effect_processing_order.at(i);
+}
+
bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
SplitterContext& splitter_context) {
// Mixes are identical
diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h
index 381566699..6a588eeb4 100644
--- a/src/audio_core/mix_context.h
+++ b/src/audio_core/mix_context.h
@@ -13,6 +13,7 @@
namespace AudioCore {
class BehaviorInfo;
+class EffectContext;
class MixInfo {
public:
@@ -65,11 +66,16 @@ public:
ServerMixInfo::InParams& GetInParams();
bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
- BehaviorInfo& behavior_info, SplitterContext& splitter_context);
+ BehaviorInfo& behavior_info, SplitterContext& splitter_context,
+ EffectContext& effect_context);
bool HasAnyConnection() const;
void Cleanup();
+ void SetEffectCount(std::size_t count);
+ void ResetEffectProcessingOrder();
+ s32 GetEffectOrder(std::size_t i) const;
private:
+ std::vector<s32> effect_processing_order;
InParams in_params{};
bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
SplitterContext& splitter_context);
@@ -80,7 +86,8 @@ public:
MixContext();
~MixContext();
- void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count);
+ void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
+ std::size_t effect_count);
void SortInfo();
bool TsortInfo(SplitterContext& splitter_context);