From 80ac1331b545d993aa7c205dc24f8b20a4d6d44e Mon Sep 17 00:00:00 2001 From: David Marcec Date: Mon, 17 Aug 2020 01:23:55 +1000 Subject: [PATCH] Preliminary effects --- src/audio_core/audio_renderer.cpp | 9 +- src/audio_core/command_generator.cpp | 187 +++++++++++++++++- src/audio_core/command_generator.h | 17 +- src/audio_core/common.h | 1 + src/audio_core/effect_context.cpp | 271 ++++++++++++++++++++++++++- src/audio_core/effect_context.h | 215 ++++++++++++++++++++- src/audio_core/info_updater.cpp | 18 +- src/audio_core/info_updater.h | 2 +- src/audio_core/mix_context.cpp | 35 +++- src/audio_core/mix_context.h | 11 +- 10 files changed, 731 insertions(+), 35 deletions(-) diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index a3ff819e11..56dc892b14 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(); stream = audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, @@ -106,8 +107,8 @@ ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector& 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 73608c9ed7..84782cde6e 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(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(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(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 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(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(in_params.volume * 32768.0f); diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h index 656ad81438..967d24078e 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; 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 mix_buffer{}; std::vector sample_buffer{}; diff --git a/src/audio_core/common.h b/src/audio_core/common.h index 0731d3eb3e..72ebce2218 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::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 2497d2f323..adfec3df55 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(); + break; + case EffectType::BufferMixer: + effects[i] = std::make_unique(); + break; + case EffectType::Aux: + effects[i] = std::make_unique(); + break; + case EffectType::Delay: + effects[i] = std::make_unique(); + break; + case EffectType::Reverb: + effects[i] = std::make_unique(); + break; + case EffectType::I3dl2Reverb: + effects[i] = std::make_unique(); + break; + case EffectType::BiquadFilter: + effects[i] = std::make_unique(); + break; + default: + UNREACHABLE_MSG("Unimplemented effect {}", effect); + effects[i] = std::make_unique(); + } + 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(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(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(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(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(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(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 e3c3672966..2f2da72dd2 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 input{}; std::array 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 input_mix_buffers{}; std::array output_mix_buffers{}; @@ -54,6 +75,81 @@ struct AuxInfo { }; static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); +struct I3dl2ReverbParams { + std::array input{}; + std::array 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 input{}; + std::array output{}; + std::array numerator; + std::array denominator; + s8 channel_count{}; + ParameterStatus status{}; +}; +static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size"); + +struct DelayParams { + std::array input{}; + std::array 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 input{}; + std::array 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 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 +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 { +public: + explicit EffectI3dl2Reverb(); + ~EffectI3dl2Reverb(); + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; + +private: + bool skipped = false; +}; + +class EffectBiquadFilter : public EffectGeneric { +public: + explicit EffectBiquadFilter(); + ~EffectBiquadFilter(); + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; +}; + +class EffectAuxInfo : public EffectGeneric { +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 { +public: + explicit EffectDelay(); + ~EffectDelay(); + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; + +private: + bool skipped = false; +}; + +class EffectBufferMixer : public EffectGeneric { +public: + explicit EffectBufferMixer(); + ~EffectBufferMixer(); + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; +}; + +class EffectReverb : public EffectGeneric { +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 286aa03211..f53ce21a5a 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 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 6969de67dd..06f9d770f0 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 f6f119a113..0428914907 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(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 3815666999..6a588eeb4e 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 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);