summaryrefslogtreecommitdiffstats
path: root/src/audio_core/renderer/command/command_generator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio_core/renderer/command/command_generator.cpp')
-rw-r--r--src/audio_core/renderer/command/command_generator.cpp796
1 files changed, 796 insertions, 0 deletions
diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp
new file mode 100644
index 000000000..2ea50d128
--- /dev/null
+++ b/src/audio_core/renderer/command/command_generator.cpp
@@ -0,0 +1,796 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/common/audio_renderer_parameter.h"
+#include "audio_core/renderer/behavior/behavior_info.h"
+#include "audio_core/renderer/command/command_buffer.h"
+#include "audio_core/renderer/command/command_generator.h"
+#include "audio_core/renderer/command/command_list_header.h"
+#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/effect_context.h"
+#include "audio_core/renderer/effect/light_limiter.h"
+#include "audio_core/renderer/mix/mix_context.h"
+#include "audio_core/renderer/performance/detail_aspect.h"
+#include "audio_core/renderer/performance/entry_aspect.h"
+#include "audio_core/renderer/sink/device_sink_info.h"
+#include "audio_core/renderer/sink/sink_context.h"
+#include "audio_core/renderer/splitter/splitter_context.h"
+#include "audio_core/renderer/voice/voice_context.h"
+#include "common/alignment.h"
+
+namespace AudioCore::AudioRenderer {
+
+CommandGenerator::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_)
+ : command_buffer{command_buffer_}, command_header{command_list_header_},
+ render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_},
+ effect_context{effect_context_}, sink_context{sink_context_},
+ splitter_context{splitter_context_}, performance_manager{performance_manager_} {
+ command_buffer.GenerateClearMixCommand(InvalidNodeId);
+}
+
+void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info,
+ const VoiceState& voice_state, const s8 channel) {
+ if (voice_info.mix_id == UnusedMixId) {
+ if (voice_info.splitter_id != UnusedSplitterId) {
+ auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)};
+ u32 dest_id{0};
+ while (destination != nullptr) {
+ if (destination->IsConfigured()) {
+ auto mix_id{destination->GetMixId()};
+ if (mix_id < mix_context.GetCount()) {
+ auto mix_info{mix_context.GetInfo(mix_id)};
+ command_buffer.GenerateDepopPrepareCommand(
+ voice_info.node_id, voice_state, render_context.depop_buffer,
+ mix_info->buffer_count, mix_info->buffer_offset,
+ voice_info.was_playing);
+ }
+ }
+ dest_id++;
+ destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id);
+ }
+ }
+ } else {
+ auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
+ command_buffer.GenerateDepopPrepareCommand(
+ voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count,
+ mix_info->buffer_offset, voice_info.was_playing);
+ }
+
+ if (voice_info.was_playing) {
+ return;
+ }
+
+ if (render_context.behavior->IsWaveBufferVer2Supported()) {
+ switch (voice_info.sample_format) {
+ case SampleFormat::PcmInt16:
+ command_buffer.GeneratePcmInt16Version2Command(
+ voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
+ channel);
+ break;
+ case SampleFormat::PcmFloat:
+ command_buffer.GeneratePcmFloatVersion2Command(
+ voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
+ channel);
+ break;
+ case SampleFormat::Adpcm:
+ command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
+ static_cast<u32>(voice_info.sample_format));
+ break;
+ }
+ } else {
+ switch (voice_info.sample_format) {
+ case SampleFormat::PcmInt16:
+ command_buffer.GeneratePcmInt16Version1Command(
+ voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ case SampleFormat::PcmFloat:
+ command_buffer.GeneratePcmFloatVersion1Command(
+ voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ case SampleFormat::Adpcm:
+ command_buffer.GenerateAdpcmVersion1Command(
+ voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
+ render_context.mix_buffer_count, channel);
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
+ static_cast<u32>(voice_info.sample_format));
+ break;
+ }
+ }
+}
+
+void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
+ std::span<const f32> prev_mix_volumes,
+ const VoiceState& voice_state, s16 output_index,
+ const s16 buffer_count, const s16 input_index,
+ const s32 node_id) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ if (buffer_count > 8) {
+ const auto prev_samples{render_context.memory_pool_info->Translate(
+ CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))};
+ command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index,
+ output_index, mix_volumes, prev_mix_volumes,
+ prev_samples, precision);
+ } else {
+ for (s16 i = 0; i < buffer_count; i++, output_index++) {
+ const auto prev_samples{render_context.memory_pool_info->Translate(
+ CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))};
+
+ command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index,
+ mix_volumes[i], prev_mix_volumes[i], prev_samples,
+ precision);
+ }
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info,
+ const VoiceState& voice_state,
+ const s16 buffer_count, const s8 channel,
+ const s32 node_id) {
+ const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled};
+ const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()};
+
+ if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() &&
+ use_float_processing) {
+ command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state,
+ buffer_count, channel);
+ } else {
+ for (u32 i = 0; i < MaxBiquadFilters; i++) {
+ if (voice_info.biquads[i].enabled) {
+ command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state,
+ buffer_count, channel, i,
+ use_float_processing);
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ for (s8 channel = 0; channel < voice_info.channel_count; channel++) {
+ const auto resource_id{voice_info.channel_resource_ids[channel]};
+ auto& voice_state{voice_context.GetDspSharedState(resource_id)};
+ auto& channel_resource{voice_context.GetChannelResource(resource_id)};
+
+ PerformanceDetailType detail_type{PerformanceDetailType::Invalid};
+ switch (voice_info.sample_format) {
+ case SampleFormat::PcmInt16:
+ detail_type = PerformanceDetailType::Unk1;
+ break;
+ case SampleFormat::PcmFloat:
+ detail_type = PerformanceDetailType::Unk10;
+ break;
+ default:
+ detail_type = PerformanceDetailType::Unk2;
+ break;
+ }
+
+ DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id,
+ detail_type);
+ GenerateDataSourceCommand(voice_info, voice_state, channel);
+
+ if (data_source_detail.initialized) {
+ command_buffer.GeneratePerformanceCommand(data_source_detail.node_id,
+ PerformanceState::Stop,
+ data_source_detail.performance_entry_address);
+ }
+
+ if (voice_info.was_playing) {
+ voice_info.prev_volume = 0.0f;
+ continue;
+ }
+
+ if (!voice_info.HasAnyConnection()) {
+ continue;
+ }
+
+ DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id,
+ PerformanceDetailType::Unk4);
+ GenerateBiquadFilterCommandForVoice(
+ voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id);
+
+ if (biquad_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ biquad_detail_aspect.node_id, PerformanceState::Stop,
+ biquad_detail_aspect.performance_entry_address);
+ }
+
+ DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice,
+ voice_info.node_id, PerformanceDetailType::Unk3);
+ command_buffer.GenerateVolumeRampCommand(
+ voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision);
+ if (volume_ramp_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ volume_ramp_detail_aspect.node_id, PerformanceState::Stop,
+ volume_ramp_detail_aspect.performance_entry_address);
+ }
+
+ voice_info.prev_volume = voice_info.volume;
+
+ if (voice_info.mix_id == UnusedMixId) {
+ if (voice_info.splitter_id != UnusedSplitterId) {
+ auto i{channel};
+ auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)};
+ while (destination != nullptr) {
+ if (destination->IsConfigured()) {
+ const auto mix_id{destination->GetMixId()};
+ if (mix_id < mix_context.GetCount() &&
+ static_cast<s32>(mix_id) != UnusedSplitterId) {
+ auto mix_info{mix_context.GetInfo(mix_id)};
+ GenerateVoiceMixCommand(
+ destination->GetMixVolume(), destination->GetMixVolumePrev(),
+ voice_state, mix_info->buffer_offset, mix_info->buffer_count,
+ render_context.mix_buffer_count + channel, voice_info.node_id);
+ destination->MarkAsNeedToUpdateInternalState();
+ }
+ }
+ i += voice_info.channel_count;
+ destination = splitter_context.GetDesintationData(voice_info.splitter_id, i);
+ }
+ }
+ } else {
+ DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice,
+ voice_info.node_id, PerformanceDetailType::Unk3);
+ auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
+ GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes,
+ voice_state, mix_info->buffer_offset, mix_info->buffer_count,
+ render_context.mix_buffer_count + channel, voice_info.node_id);
+ if (volume_mix_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ volume_mix_detail_aspect.node_id, PerformanceState::Stop,
+ volume_mix_detail_aspect.performance_entry_address);
+ }
+
+ channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
+ }
+ voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled;
+ voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled;
+ }
+}
+
+void CommandGenerator::GenerateVoiceCommands() {
+ const auto voice_count{voice_context.GetCount()};
+
+ for (u32 i = 0; i < voice_count; i++) {
+ auto sorted_info{voice_context.GetSortedInfo(i)};
+
+ if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) {
+ continue;
+ }
+
+ EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id);
+
+ GenerateVoiceCommand(*sorted_info);
+
+ if (voice_entry_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id,
+ PerformanceState::Stop,
+ voice_entry_aspect.performance_entry_address);
+ }
+ }
+
+ splitter_context.UpdateInternalState();
+}
+
+void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info, const s32 node_id) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ if (effect_info.IsEnabled()) {
+ const auto& parameter{
+ *reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ for (u32 i = 0; i < parameter.mix_count; i++) {
+ if (parameter.volumes[i] != 0.0f) {
+ command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i],
+ buffer_offset + parameter.outputs[i],
+ buffer_offset, parameter.volumes[i], precision);
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id) {
+ command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset);
+}
+
+void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id,
+ const bool long_size_pre_delay_supported) {
+ command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset,
+ long_size_pre_delay_supported);
+}
+
+void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info,
+ const s32 node_id) {
+ command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset);
+}
+
+void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id) {
+
+ if (effect_info.IsEnabled()) {
+ effect_info.GetWorkbuffer(0);
+ effect_info.GetWorkbuffer(1);
+ }
+
+ if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
+ const auto& parameter{
+ *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ auto channel_index{parameter.mix_buffer_count - 1};
+ u32 write_offset{0};
+ for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
+ auto new_update_count{command_header.sample_count + write_offset};
+ const auto update_count{channel_index > 0 ? 0 : new_update_count};
+ command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i],
+ parameter.outputs[i], buffer_offset, update_count,
+ parameter.count_max, write_offset);
+ write_offset = new_update_count;
+ }
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info,
+ const s32 node_id) {
+ const auto& parameter{
+ *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ if (effect_info.IsEnabled()) {
+ bool needs_init{false};
+
+ switch (parameter.state) {
+ case EffectInfoBase::ParameterState::Initialized:
+ needs_init = true;
+ break;
+ case EffectInfoBase::ParameterState::Updating:
+ case EffectInfoBase::ParameterState::Updated:
+ if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) {
+ needs_init = false;
+ } else {
+ needs_init = parameter.state == EffectInfoBase::ParameterState::Updating;
+ }
+ break;
+ default:
+ LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}",
+ static_cast<u32>(parameter.state));
+ break;
+ }
+
+ for (s8 channel = 0; channel < parameter.channel_count; channel++) {
+ command_buffer.GenerateBiquadFilterCommand(
+ node_id, effect_info, buffer_offset, channel, needs_init,
+ render_context.behavior->UseBiquadFilterFloatProcessing());
+ }
+ } else {
+ for (s8 channel = 0; channel < parameter.channel_count; channel++) {
+ command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset,
+ channel);
+ }
+ }
+}
+
+void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info,
+ const s32 node_id,
+ const u32 effect_index) {
+
+ const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())};
+
+ if (render_context.behavior->IsEffectInfoVersion2Supported()) {
+ const auto& parameter{
+ *reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())};
+ const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(
+ &effect_context.GetDspSharedResultState(effect_index))};
+ command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state,
+ state, effect_info.IsEnabled(),
+ effect_info.GetWorkbuffer(-1));
+ } else {
+ const auto& parameter{
+ *reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state,
+ effect_info.IsEnabled(),
+ effect_info.GetWorkbuffer(-1));
+ }
+}
+
+void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
+ const s32 node_id) {
+ if (effect_info.IsEnabled()) {
+ effect_info.GetWorkbuffer(0);
+ }
+
+ if (effect_info.GetSendBuffer()) {
+ const auto& parameter{
+ *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
+ auto channel_index{parameter.mix_buffer_count - 1};
+ u32 write_offset{0};
+ for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
+ auto new_update_count{command_header.sample_count + write_offset};
+ const auto update_count{channel_index > 0 ? 0 : new_update_count};
+ command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i],
+ parameter.outputs[i], buffer_offset, update_count,
+ parameter.count_max, write_offset);
+ write_offset = new_update_count;
+ }
+ }
+}
+
+void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset,
+ EffectInfoBase& effect_info, const s32 node_id) {
+ command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id);
+}
+
+void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) {
+ const auto effect_count{effect_context.GetCount()};
+ for (u32 i = 0; i < effect_count; i++) {
+ const auto effect_index{mix_info.effect_order_buffer[i]};
+ if (effect_index == -1) {
+ break;
+ }
+
+ auto& effect_info = effect_context.GetInfo(effect_index);
+ if (effect_info.ShouldSkip()) {
+ continue;
+ }
+
+ const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix
+ : PerformanceEntryType::SubMix};
+
+ switch (effect_info.GetType()) {
+ case EffectInfoBase::Type::Mix: {
+ DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk5);
+ GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (mix_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ mix_detail_aspect.node_id, PerformanceState::Stop,
+ mix_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Aux: {
+ DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk7);
+ GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (aux_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ aux_detail_aspect.node_id, PerformanceState::Stop,
+ aux_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Delay: {
+ DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk6);
+ GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (delay_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ delay_detail_aspect.node_id, PerformanceState::Stop,
+ delay_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Reverb: {
+ DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk8);
+ GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
+ render_context.behavior->IsLongSizePreDelaySupported());
+ if (reverb_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ reverb_detail_aspect.node_id, PerformanceState::Stop,
+ reverb_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::I3dl2Reverb: {
+ DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk9);
+ GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (i3dl2_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ i3dl2_detail_aspect.node_id, PerformanceState::Stop,
+ i3dl2_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::BiquadFilter: {
+ DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk4);
+ GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info,
+ mix_info.node_id);
+ if (biquad_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ biquad_detail_aspect.node_id, PerformanceState::Stop,
+ biquad_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::LightLimiter: {
+ DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk11);
+ GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
+ effect_index);
+ if (light_limiter_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ light_limiter_detail_aspect.node_id, PerformanceState::Stop,
+ light_limiter_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Capture: {
+ DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk12);
+ GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (capture_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ capture_detail_aspect.node_id, PerformanceState::Stop,
+ capture_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ case EffectInfoBase::Type::Compressor: {
+ DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
+ PerformanceDetailType::Unk13);
+ GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
+ if (capture_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ capture_detail_aspect.node_id, PerformanceState::Stop,
+ capture_detail_aspect.performance_entry_address);
+ }
+ } break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid effect type {}",
+ static_cast<u32>(effect_info.GetType()));
+ break;
+ }
+
+ effect_info.UpdateForCommandGeneration();
+ }
+}
+
+void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) {
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ if (!mix_info.HasAnyConnection()) {
+ return;
+ }
+
+ if (mix_info.dst_mix_id == UnusedMixId) {
+ if (mix_info.dst_splitter_id != UnusedSplitterId) {
+ s16 dest_id{0};
+ auto destination{
+ splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)};
+ while (destination != nullptr) {
+ if (destination->IsConfigured()) {
+ auto splitter_mix_id{destination->GetMixId()};
+ if (splitter_mix_id < mix_context.GetCount()) {
+ auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)};
+ const s16 input_index{static_cast<s16>(mix_info.buffer_offset +
+ (dest_id % mix_info.buffer_count))};
+ for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) {
+ auto volume{mix_info.volume * destination->GetMixVolume(i)};
+ if (volume != 0.0f) {
+ command_buffer.GenerateMixCommand(
+ mix_info.node_id, input_index,
+ splitter_mix_info->buffer_offset + i, mix_info.buffer_offset,
+ volume, precision);
+ }
+ }
+ }
+ }
+ dest_id++;
+ destination =
+ splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id);
+ }
+ }
+ } else {
+ auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)};
+ for (s16 i = 0; i < mix_info.buffer_count; i++) {
+ for (s16 j = 0; j < dest_mix_info->buffer_count; j++) {
+ auto volume{mix_info.volume * mix_info.mix_volumes[i][j]};
+ if (volume != 0.0f) {
+ command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i,
+ dest_mix_info->buffer_offset + j,
+ mix_info.buffer_offset, volume, precision);
+ }
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) {
+ command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info,
+ render_context.depop_buffer);
+ GenerateEffectCommand(mix_info);
+
+ DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id,
+ PerformanceDetailType::Unk5);
+
+ GenerateMixCommands(mix_info);
+
+ if (mix_detail_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop,
+ mix_detail_aspect.performance_entry_address);
+ }
+}
+
+void CommandGenerator::GenerateSubMixCommands() {
+ const auto submix_count{mix_context.GetCount()};
+ for (s32 i = 0; i < submix_count; i++) {
+ auto sorted_info{mix_context.GetSortedInfo(i)};
+ if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) {
+ continue;
+ }
+
+ EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id);
+
+ GenerateSubMixCommand(*sorted_info);
+
+ if (submix_entry_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ submix_entry_aspect.node_id, PerformanceState::Stop,
+ submix_entry_aspect.performance_entry_address);
+ }
+ }
+}
+
+void CommandGenerator::GenerateFinalMixCommand() {
+ auto& final_mix_info{*mix_context.GetFinalMixInfo()};
+
+ command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info,
+ render_context.depop_buffer);
+ GenerateEffectCommand(final_mix_info);
+
+ u8 precision{15};
+ if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
+ precision = 23;
+ }
+
+ for (s16 i = 0; i < final_mix_info.buffer_count; i++) {
+ DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id,
+ PerformanceDetailType::Unk3);
+ command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset,
+ i, final_mix_info.volume, precision);
+ if (volume_aspect.initialized) {
+ command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop,
+ volume_aspect.performance_entry_address);
+ }
+ }
+}
+
+void CommandGenerator::GenerateFinalMixCommands() {
+ auto final_mix_info{mix_context.GetFinalMixInfo()};
+ EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id);
+ GenerateFinalMixCommand();
+ if (final_mix_entry.initialized) {
+ command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop,
+ final_mix_entry.performance_entry_address);
+ }
+}
+
+void CommandGenerator::GenerateSinkCommands() {
+ const auto sink_count{sink_context.GetCount()};
+
+ for (u32 i = 0; i < sink_count; i++) {
+ auto sink_info{sink_context.GetInfo(i)};
+ if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) {
+ auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())};
+ if (command_header.sample_rate != TargetSampleRate &&
+ state->upsampler_info == nullptr) {
+ auto device_state{sink_info->GetDeviceState()};
+ device_state->upsampler_info = render_context.upsampler_manager->Allocate();
+ }
+
+ EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink,
+ sink_info->GetNodeId());
+ auto final_mix{mix_context.GetFinalMixInfo()};
+ GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
+
+ if (device_sink_entry.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ device_sink_entry.node_id, PerformanceState::Stop,
+ device_sink_entry.performance_entry_address);
+ }
+ }
+ }
+
+ for (u32 i = 0; i < sink_count; i++) {
+ auto sink_info{sink_context.GetInfo(i)};
+ if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) {
+ EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink,
+ sink_info->GetNodeId());
+ auto final_mix{mix_context.GetFinalMixInfo()};
+ GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
+
+ if (circular_buffer_entry.initialized) {
+ command_buffer.GeneratePerformanceCommand(
+ circular_buffer_entry.node_id, PerformanceState::Stop,
+ circular_buffer_entry.performance_entry_address);
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
+ if (sink_info.ShouldSkip()) {
+ return;
+ }
+
+ switch (sink_info.GetType()) {
+ case SinkInfoBase::Type::DeviceSink:
+ GenerateDeviceSinkCommand(buffer_offset, sink_info);
+ break;
+
+ case SinkInfoBase::Type::CircularBufferSink:
+ command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info,
+ buffer_offset);
+ break;
+
+ default:
+ LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType()));
+ break;
+ }
+
+ sink_info.UpdateForCommandGeneration();
+}
+
+void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
+ auto& parameter{
+ *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
+ auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
+
+ if (render_context.channels == 2 && parameter.downmix_enabled) {
+ command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs,
+ buffer_offset, parameter.downmix_coeff);
+ }
+
+ if (state.upsampler_info != nullptr) {
+ command_buffer.GenerateUpsampleCommand(
+ InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count,
+ parameter.inputs, command_header.buffer_count, command_header.sample_count,
+ command_header.sample_rate);
+ }
+
+ command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info,
+ render_context.session_id,
+ command_header.samples_buffer);
+}
+
+void CommandGenerator::GeneratePerformanceCommand(
+ s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) {
+ command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses);
+}
+
+} // namespace AudioCore::AudioRenderer