// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "audio_core/renderer/behavior/behavior_info.h" #include "audio_core/renderer/command/command_buffer.h" #include "audio_core/renderer/command/command_list_header.h" #include "audio_core/renderer/command/command_processing_time_estimator.h" #include "audio_core/renderer/effect/biquad_filter.h" #include "audio_core/renderer/effect/delay.h" #include "audio_core/renderer/effect/reverb.h" #include "audio_core/renderer/memory/memory_pool_info.h" #include "audio_core/renderer/mix/mix_info.h" #include "audio_core/renderer/sink/circular_buffer_sink_info.h" #include "audio_core/renderer/sink/device_sink_info.h" #include "audio_core/renderer/sink/sink_info_base.h" #include "audio_core/renderer/voice/voice_info.h" #include "audio_core/renderer/voice/voice_state.h" namespace AudioCore::AudioRenderer { template T& CommandBuffer::GenerateStart(const s32 node_id) { if (size + sizeof(T) >= command_list.size_bytes()) { LOG_ERROR( Service_Audio, "Attempting to write commands beyond the end of allocated command buffer memory!"); UNREACHABLE(); } auto& cmd{*std::construct_at(reinterpret_cast(&command_list[size]))}; cmd.magic = CommandMagic; cmd.enabled = true; cmd.type = Id; cmd.size = sizeof(T); cmd.node_id = node_id; return cmd; } template void CommandBuffer::GenerateEnd(T& cmd) { cmd.estimated_process_time = time_estimator->Estimate(cmd); estimated_process_time += cmd.estimated_process_time; size += sizeof(T); count++; } void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id, const MemoryPoolInfo& memory_pool_, VoiceInfo& voice_info, const VoiceState& voice_state, const s16 buffer_count, const s8 channel) { auto& cmd{ GenerateStart( node_id)}; cmd.src_quality = voice_info.src_quality; cmd.output_index = buffer_count + channel; cmd.flags = voice_info.flags & 3; cmd.sample_rate = voice_info.sample_rate; cmd.pitch = voice_info.pitch; cmd.channel_index = channel; cmd.channel_count = voice_info.channel_count; for (u32 i = 0; i < MaxWaveBuffers; i++) { voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); } cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); GenerateEnd(cmd); } void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info, const VoiceState& voice_state, const s16 buffer_count, const s8 channel) { auto& cmd{ GenerateStart( node_id)}; cmd.src_quality = voice_info.src_quality; cmd.output_index = buffer_count + channel; cmd.flags = voice_info.flags & 3; cmd.sample_rate = voice_info.sample_rate; cmd.pitch = voice_info.pitch; cmd.channel_index = channel; cmd.channel_count = voice_info.channel_count; for (u32 i = 0; i < MaxWaveBuffers; i++) { voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); } cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); GenerateEnd(cmd); } void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id, const MemoryPoolInfo& memory_pool_, VoiceInfo& voice_info, const VoiceState& voice_state, const s16 buffer_count, const s8 channel) { auto& cmd{ GenerateStart( node_id)}; cmd.src_quality = voice_info.src_quality; cmd.output_index = buffer_count + channel; cmd.flags = voice_info.flags & 3; cmd.sample_rate = voice_info.sample_rate; cmd.pitch = voice_info.pitch; cmd.channel_index = channel; cmd.channel_count = voice_info.channel_count; for (u32 i = 0; i < MaxWaveBuffers; i++) { voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); } cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); GenerateEnd(cmd); } void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info, const VoiceState& voice_state, const s16 buffer_count, const s8 channel) { auto& cmd{ GenerateStart( node_id)}; cmd.src_quality = voice_info.src_quality; cmd.output_index = buffer_count + channel; cmd.flags = voice_info.flags & 3; cmd.sample_rate = voice_info.sample_rate; cmd.pitch = voice_info.pitch; cmd.channel_index = channel; cmd.channel_count = voice_info.channel_count; for (u32 i = 0; i < MaxWaveBuffers; i++) { voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); } cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); GenerateEnd(cmd); } void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id, const MemoryPoolInfo& memory_pool_, VoiceInfo& voice_info, const VoiceState& voice_state, const s16 buffer_count, const s8 channel) { auto& cmd{ GenerateStart(node_id)}; cmd.src_quality = voice_info.src_quality; cmd.output_index = buffer_count + channel; cmd.flags = voice_info.flags & 3; cmd.sample_rate = voice_info.sample_rate; cmd.pitch = voice_info.pitch; for (u32 i = 0; i < MaxWaveBuffers; i++) { voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); } cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); cmd.data_address = voice_info.data_address.GetReference(true); cmd.data_size = voice_info.data_address.GetSize(); GenerateEnd(cmd); } void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info, const VoiceState& voice_state, const s16 buffer_count, const s8 channel) { auto& cmd{ GenerateStart(node_id)}; cmd.src_quality = voice_info.src_quality; cmd.output_index = buffer_count + channel; cmd.flags = voice_info.flags & 3; cmd.sample_rate = voice_info.sample_rate; cmd.pitch = voice_info.pitch; cmd.channel_index = channel; cmd.channel_count = voice_info.channel_count; for (u32 i = 0; i < MaxWaveBuffers; i++) { voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); } cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); cmd.data_address = voice_info.data_address.GetReference(true); cmd.data_size = voice_info.data_address.GetSize(); GenerateEnd(cmd); } void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset, const s16 input_index, const f32 volume, const u8 precision) { auto& cmd{GenerateStart(node_id)}; cmd.precision = precision; cmd.input_index = buffer_offset + input_index; cmd.output_index = buffer_offset + input_index; cmd.volume = volume; GenerateEnd(cmd); } void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info, const s16 buffer_count, const u8 precision) { auto& cmd{GenerateStart(node_id)}; cmd.input_index = buffer_count; cmd.output_index = buffer_count; cmd.prev_volume = voice_info.prev_volume; cmd.volume = voice_info.volume; cmd.precision = precision; GenerateEnd(cmd); } void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, const VoiceState& voice_state, const s16 buffer_count, const s8 channel, const u32 biquad_index, const bool use_float_processing) { auto& cmd{GenerateStart(node_id)}; cmd.input = buffer_count + channel; cmd.output = buffer_count + channel; cmd.biquad = voice_info.biquads[biquad_index]; cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); cmd.needs_init = !voice_info.biquad_initialized[biquad_index]; cmd.use_float_processing = use_float_processing; GenerateEnd(cmd); } void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info, const s16 buffer_offset, const s8 channel, const bool needs_init, const bool use_float_processing) { auto& cmd{GenerateStart(node_id)}; const auto& parameter{ *reinterpret_cast(effect_info.GetParameter())}; const auto state{reinterpret_cast( effect_info.GetStateBuffer() + channel * sizeof(VoiceState::BiquadFilterState))}; cmd.input = buffer_offset + parameter.inputs[channel]; cmd.output = buffer_offset + parameter.outputs[channel]; cmd.biquad.b = parameter.b; cmd.biquad.a = parameter.a; cmd.state = memory_pool->Translate(CpuAddr(state), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); cmd.needs_init = needs_init; cmd.use_float_processing = use_float_processing; GenerateEnd(cmd); } void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index, const s16 output_index, const s16 buffer_offset, const f32 volume, const u8 precision) { auto& cmd{GenerateStart(node_id)}; cmd.input_index = input_index; cmd.output_index = output_index; cmd.volume = volume; cmd.precision = precision; GenerateEnd(cmd); } void CommandBuffer::GenerateMixRampCommand(const s32 node_id, [[maybe_unused]] const s16 buffer_count, const s16 input_index, const s16 output_index, const f32 volume, const f32 prev_volume, const CpuAddr prev_samples, const u8 precision) { if (volume == 0.0f && prev_volume == 0.0f) { return; } auto& cmd{GenerateStart(node_id)}; cmd.input_index = input_index; cmd.output_index = output_index; cmd.prev_volume = prev_volume; cmd.volume = volume; cmd.previous_sample = prev_samples; cmd.precision = precision; GenerateEnd(cmd); } void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count, const s16 input_index, s16 output_index, std::span volumes, std::span prev_volumes, const CpuAddr prev_samples, const u8 precision) { auto& cmd{GenerateStart(node_id)}; cmd.buffer_count = buffer_count; for (s32 i = 0; i < buffer_count; i++) { cmd.inputs[i] = input_index; cmd.outputs[i] = output_index++; cmd.prev_volumes[i] = prev_volumes[i]; cmd.volumes[i] = volumes[i]; } cmd.previous_samples = prev_samples; cmd.precision = precision; GenerateEnd(cmd); } void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state, std::span buffer, const s16 buffer_count, s16 buffer_offset, const bool was_playing) { auto& cmd{GenerateStart(node_id)}; cmd.enabled = was_playing; for (u32 i = 0; i < MaxMixBuffers; i++) { cmd.inputs[i] = buffer_offset++; } cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()), MaxMixBuffers * sizeof(s32)); cmd.buffer_count = buffer_count; cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer.size_bytes()); GenerateEnd(cmd); } void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info, std::span depop_buffer) { auto& cmd{GenerateStart(node_id)}; cmd.input = mix_info.buffer_offset; cmd.count = mix_info.buffer_count; cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f; cmd.depop_buffer = memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32)); GenerateEnd(cmd); } void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info, const s16 buffer_offset) { auto& cmd{GenerateStart(node_id)}; const auto& parameter{ *reinterpret_cast(effect_info.GetParameter())}; const auto state{effect_info.GetStateBuffer()}; if (IsChannelCountValid(parameter.channel_count)) { const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))}; if (state_buffer) { for (s16 channel = 0; channel < parameter.channel_count; channel++) { cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; } if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) { UseOldChannelMapping(cmd.inputs, cmd.outputs); } cmd.parameter = parameter; cmd.effect_enabled = effect_info.IsEnabled(); cmd.state = state_buffer; cmd.workbuffer = effect_info.GetWorkbuffer(-1); } } GenerateEnd(cmd); } void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset, UpsamplerInfo& upsampler_info, const u32 input_count, std::span inputs, const s16 buffer_count, const u32 sample_count_, const u32 sample_rate_) { auto& cmd{GenerateStart(node_id)}; cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos, upsampler_info.sample_count * sizeof(s32)); cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels); cmd.buffer_count = buffer_count; cmd.unk_20 = 0; cmd.source_sample_count = sample_count_; cmd.source_sample_rate = sample_rate_; upsampler_info.input_count = input_count; for (u32 i = 0; i < input_count; i++) { upsampler_info.inputs[i] = buffer_offset + inputs[i]; } cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo)); GenerateEnd(cmd); } void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span inputs, const s16 buffer_offset, std::span downmix_coeff) { auto& cmd{GenerateStart(node_id)}; for (u32 i = 0; i < MaxChannels; i++) { cmd.inputs[i] = buffer_offset + inputs[i]; cmd.outputs[i] = buffer_offset + inputs[i]; } for (u32 i = 0; i < 4; i++) { cmd.down_mix_coeff[i] = downmix_coeff[i]; } GenerateEnd(cmd); } void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info, const s16 input_index, const s16 output_index, const s16 buffer_offset, const u32 update_count, const u32 count_max, const u32 write_offset) { auto& cmd{GenerateStart(node_id)}; if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) { cmd.input = buffer_offset + input_index; cmd.output = buffer_offset + output_index; cmd.send_buffer_info = effect_info.GetSendBufferInfo(); cmd.send_buffer = effect_info.GetSendBuffer(); cmd.return_buffer_info = effect_info.GetReturnBufferInfo(); cmd.return_buffer = effect_info.GetReturnBuffer(); cmd.count_max = count_max; cmd.write_offset = write_offset; cmd.update_count = update_count; cmd.effect_enabled = effect_info.IsEnabled(); } GenerateEnd(cmd); } void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset, SinkInfoBase& sink_info, const u32 session_id, std::span samples_buffer) { auto& cmd{GenerateStart(node_id)}; const auto& parameter{ *reinterpret_cast(sink_info.GetParameter())}; auto state{*reinterpret_cast(sink_info.GetState())}; cmd.session_id = session_id; cmd.input_count = parameter.input_count; s16 max_input{0}; for (u32 i = 0; i < parameter.input_count; i++) { cmd.inputs[i] = buffer_offset + parameter.inputs[i]; max_input = std::max(max_input, cmd.inputs[i]); } if (state.upsampler_info != nullptr) { const auto size_{state.upsampler_info->sample_count * parameter.input_count}; const auto size_bytes{size_ * sizeof(s32)}; const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)}; cmd.sample_buffer = {reinterpret_cast(addr), (max_input + 1) * state.upsampler_info->sample_count}; } else { cmd.sample_buffer = samples_buffer; } GenerateEnd(cmd); } void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info, const s16 buffer_offset) { auto& cmd{GenerateStart(node_id)}; const auto& parameter{*reinterpret_cast( sink_info.GetParameter())}; auto state{ *reinterpret_cast(sink_info.GetState())}; cmd.input_count = parameter.input_count; for (u32 i = 0; i < parameter.input_count; i++) { cmd.inputs[i] = buffer_offset + parameter.inputs[i]; } cmd.address = state.address_info.GetReference(true); cmd.size = parameter.size; cmd.pos = state.current_pos; GenerateEnd(cmd); } void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info, const s16 buffer_offset, const bool long_size_pre_delay_supported) { auto& cmd{GenerateStart(node_id)}; const auto& parameter{ *reinterpret_cast(effect_info.GetParameter())}; const auto state{effect_info.GetStateBuffer()}; if (IsChannelCountValid(parameter.channel_count)) { const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))}; if (state_buffer) { for (s16 channel = 0; channel < parameter.channel_count; channel++) { cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; } if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) { UseOldChannelMapping(cmd.inputs, cmd.outputs); } cmd.parameter = parameter; cmd.effect_enabled = effect_info.IsEnabled(); cmd.state = state_buffer; cmd.workbuffer = effect_info.GetWorkbuffer(-1); cmd.long_size_pre_delay_supported = long_size_pre_delay_supported; } } GenerateEnd(cmd); } void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info, const s16 buffer_offset) { auto& cmd{GenerateStart(node_id)}; const auto& parameter{ *reinterpret_cast(effect_info.GetParameter())}; const auto state{effect_info.GetStateBuffer()}; if (IsChannelCountValid(parameter.channel_count)) { const auto state_buffer{ memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))}; if (state_buffer) { for (s16 channel = 0; channel < parameter.channel_count; channel++) { cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; } if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) { UseOldChannelMapping(cmd.inputs, cmd.outputs); } cmd.parameter = parameter; cmd.effect_enabled = effect_info.IsEnabled(); cmd.state = state_buffer; cmd.workbuffer = effect_info.GetWorkbuffer(-1); } } GenerateEnd(cmd); } void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state, const PerformanceEntryAddresses& entry_addresses) { auto& cmd{GenerateStart(node_id)}; cmd.state = state; cmd.entry_address = entry_addresses; GenerateEnd(cmd); } void CommandBuffer::GenerateClearMixCommand(const s32 node_id) { auto& cmd{GenerateStart(node_id)}; GenerateEnd(cmd); } void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info, const s16 buffer_offset, const s8 channel) { auto& cmd{GenerateStart(node_id)}; const auto& parameter{ *reinterpret_cast(effect_info.GetParameter())}; cmd.input_index = buffer_offset + parameter.inputs[channel]; cmd.output_index = buffer_offset + parameter.outputs[channel]; GenerateEnd(cmd); } void CommandBuffer::GenerateLightLimiterCommand( const s32 node_id, const s16 buffer_offset, const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state, const bool enabled, const CpuAddr workbuffer) { auto& cmd{GenerateStart(node_id)}; if (IsChannelCountValid(parameter.channel_count)) { const auto state_buffer{ memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; if (state_buffer) { for (s8 channel = 0; channel < parameter.channel_count; channel++) { cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; } std::memcpy(&cmd.parameter, ¶meter, sizeof(LightLimiterInfo::ParameterVersion1)); cmd.effect_enabled = enabled; cmd.state = state_buffer; cmd.workbuffer = workbuffer; } } GenerateEnd(cmd); } void CommandBuffer::GenerateLightLimiterCommand( const s32 node_id, const s16 buffer_offset, const LightLimiterInfo::ParameterVersion2& parameter, const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state, const bool enabled, const CpuAddr workbuffer) { auto& cmd{GenerateStart(node_id)}; if (IsChannelCountValid(parameter.channel_count)) { const auto state_buffer{ memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; if (state_buffer) { for (s8 channel = 0; channel < parameter.channel_count; channel++) { cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; } cmd.parameter = parameter; cmd.effect_enabled = enabled; cmd.state = state_buffer; if (cmd.parameter.statistics_enabled) { cmd.result_state = memory_pool->Translate( CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal)); } else { cmd.result_state = 0; } cmd.workbuffer = workbuffer; } } GenerateEnd(cmd); } void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, const VoiceState& voice_state, const s16 buffer_count, const s8 channel) { auto& cmd{GenerateStart(node_id)}; cmd.input = buffer_count + channel; cmd.output = buffer_count + channel; cmd.biquads = voice_info.biquads; cmd.states[0] = memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); cmd.states[1] = memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); cmd.needs_init[0] = !voice_info.biquad_initialized[0]; cmd.needs_init[1] = !voice_info.biquad_initialized[1]; cmd.filter_tap_count = MaxBiquadFilters; GenerateEnd(cmd); } void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info, const s16 input_index, const s16 output_index, const s16 buffer_offset, const u32 update_count, const u32 count_max, const u32 write_offset) { auto& cmd{GenerateStart(node_id)}; if (effect_info.GetSendBuffer()) { cmd.input = buffer_offset + input_index; cmd.output = buffer_offset + output_index; cmd.send_buffer_info = effect_info.GetSendBufferInfo(); cmd.send_buffer = effect_info.GetSendBuffer(); cmd.count_max = count_max; cmd.write_offset = write_offset; cmd.update_count = update_count; cmd.effect_enabled = effect_info.IsEnabled(); } GenerateEnd(cmd); } void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id) { auto& cmd{GenerateStart(node_id)}; auto& parameter{ *reinterpret_cast(effect_info.GetParameter())}; auto state{reinterpret_cast(effect_info.GetStateBuffer())}; if (IsChannelCountValid(parameter.channel_count)) { auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))}; if (state_buffer) { for (u16 channel = 0; channel < parameter.channel_count; channel++) { cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; } cmd.parameter = parameter; cmd.workbuffer = state_buffer; cmd.enabled = effect_info.IsEnabled(); } } GenerateEnd(cmd); } } // namespace AudioCore::AudioRenderer