// 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::Renderer { 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.GetDestinationData(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() && mix_id != UnusedSplitterId) { 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.GetDestinationData(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(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(voice_info.sample_format)); break; } } } void CommandGenerator::GenerateVoiceMixCommand(std::span mix_volumes, std::span 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.GetDestinationData(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(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.GetDestinationData(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(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(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(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(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(effect_info.GetStateBuffer())}; if (render_context.behavior->IsEffectInfoVersion2Supported()) { const auto& parameter{ *reinterpret_cast(effect_info.GetParameter())}; const auto& result_state{*reinterpret_cast( &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(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(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(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.GetDestinationData(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(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.GetDestinationData(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(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(sink_info.GetType())); break; } sink_info.UpdateForCommandGeneration(); } void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { auto& parameter{ *reinterpret_cast(sink_info.GetParameter())}; auto state{*reinterpret_cast(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::Renderer