diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp index e338c1ae5..6dcce64df 100644 --- a/src/audio_core/renderer/command/command_buffer.cpp +++ b/src/audio_core/renderer/command/command_buffer.cpp @@ -255,15 +255,89 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBas const s16 buffer_offset, const s8 channel, const bool needs_init, const bool use_float_processing) { - auto& cmd{GenerateStart(node_id)}; + if (behavior->IsEffectInfoVersion2Supported()) { + const auto& parameter_v2{ + *reinterpret_cast(effect_info.GetParameter())}; + + // Validate channel and channel count + if (!IsChannelCountValid(static_cast(parameter_v2.channel_count)) || channel < 0 || + channel >= parameter_v2.channel_count) { + return; + } + + // ParameterVersion2 has per-parameter enable field - if disabled, copy input to output + if (!parameter_v2.enable) { + GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset, channel); + return; + } + + // Validate buffer indices before generating command + const s16 input_index = buffer_offset + parameter_v2.inputs[channel]; + const s16 output_index = buffer_offset + parameter_v2.outputs[channel]; + + if (input_index < 0) { + LOG_WARNING(Service_Audio, + "BiquadFilterCommand: Skipping v2 command generation - invalid input index ({})", + input_index); + return; + } + + s16 effective_output = output_index; + if (output_index < 0) { + LOG_WARNING(Service_Audio, + "BiquadFilterCommand: Invalid v2 output index ({}), using input ({}) for in-place processing", + output_index, input_index); + effective_output = input_index; + } + + auto& cmd{GenerateStart(node_id)}; + + // ParameterVersion2 uses native float coefficients + const auto state{reinterpret_cast( + effect_info.GetStateBuffer() + channel * sizeof(VoiceState::BiquadFilterState))}; + + cmd.input = input_index; + cmd.output = effective_output; + cmd.biquad_float.numerator = parameter_v2.b; + cmd.biquad_float.denominator = parameter_v2.a; + cmd.use_float_coefficients = true; + cmd.state = memory_pool->Translate(CpuAddr(state), sizeof(VoiceState::BiquadFilterState)); + cmd.needs_init = needs_init; + cmd.use_float_processing = use_float_processing; + + GenerateEnd(cmd); + return; + } 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]; + const s16 input_index = buffer_offset + parameter.inputs[channel]; + const s16 output_index = buffer_offset + parameter.outputs[channel]; + + // Validate buffer indices before generating command + // If output is invalid, use input as output (in-place processing) + // This matches the fix in Process() and Verify() + if (input_index < 0) { + LOG_WARNING(Service_Audio, + "BiquadFilterCommand: Skipping command generation - invalid input index ({})", + input_index); + return; + } + + s16 effective_output = output_index; + if (output_index < 0) { + LOG_WARNING(Service_Audio, + "BiquadFilterCommand: Invalid output index ({}), using input ({}) for in-place " + "processing", + output_index, input_index); + effective_output = input_index; + } + + auto& cmd{GenerateStart(node_id)}; + + cmd.input = input_index; + cmd.output = effective_output; cmd.biquad.b = parameter.b; cmd.biquad.a = parameter.a; @@ -271,6 +345,8 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBas // Effects use legacy fixed-point format cmd.use_float_coefficients = false; + const auto state{reinterpret_cast( + effect_info.GetStateBuffer() + channel * sizeof(VoiceState::BiquadFilterState))}; cmd.state = memory_pool->Translate(CpuAddr(state), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); @@ -593,6 +669,15 @@ void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBa const s16 buffer_offset, const s8 channel) { auto& cmd{GenerateStart(node_id)}; + if (behavior->IsEffectInfoVersion2Supported()) { + const auto& parameter_v2{ + *reinterpret_cast(effect_info.GetParameter())}; + cmd.input_index = buffer_offset + parameter_v2.inputs[channel]; + cmd.output_index = buffer_offset + parameter_v2.outputs[channel]; + GenerateEnd(cmd); + return; + } + const auto& parameter{ *reinterpret_cast(effect_info.GetParameter())}; cmd.input_index = buffer_offset + parameter.inputs[channel]; diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp index f97db5899..f97d58d21 100644 --- a/src/audio_core/renderer/command/command_generator.cpp +++ b/src/audio_core/renderer/command/command_generator.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include "audio_core/common/audio_renderer_parameter.h" #include "audio_core/renderer/behavior/behavior_info.h" #include "audio_core/renderer/command/command_buffer.h" @@ -361,9 +363,61 @@ void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBas void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset, EffectInfoBase& effect_info, const s32 node_id) { - const auto& parameter{ + if (render_context.behavior->IsEffectInfoVersion2Supported()) { + const auto& parameter_v2{ + *reinterpret_cast(effect_info.GetParameter())}; + + // ParameterVersion2 doesn't use state transitions, always needs_init = false + const bool needs_init = false; + const bool use_float_processing = render_context.behavior->UseBiquadFilterFloatProcessing(); + + // Validate channel count - use IsChannelCountValid for proper validation + if (!IsChannelCountValid(static_cast(parameter_v2.channel_count))) { + LOG_WARNING(Service_Audio, "BiquadFilterEffectCommand: Invalid v2 channel_count {}, skipping", + parameter_v2.channel_count); + return; + } + + const s8 channels = parameter_v2.channel_count; + + if (effect_info.IsEnabled()) { + for (s8 channel = 0; channel < channels; channel++) { + command_buffer.GenerateBiquadFilterCommand( + node_id, effect_info, buffer_offset, channel, needs_init, use_float_processing); + } + } else { + // Effect disabled - generate copy commands for all channels + for (s8 channel = 0; channel < channels; channel++) { + command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset, + channel); + } + } + return; + } + + auto& parameter{ *reinterpret_cast(effect_info.GetParameter())}; - if (effect_info.IsEnabled()) { + + // If effect is disabled (e.g., due to corrupted parameters), skip command generation + if (!effect_info.IsEnabled()) { + return; + } + + // Validate parameters - if corrupted, skip command generation to prevent audio issues + if (parameter.channel_count < 0 || parameter.channel_count > MaxChannels) { + LOG_WARNING(Service_Audio, "BiquadFilterEffectCommand: Invalid channel_count {}, skipping command generation", + parameter.channel_count); + return; + } + + if (static_cast(parameter.state) > static_cast(EffectInfoBase::ParameterState::Updated)) { + LOG_WARNING(Service_Audio, "BiquadFilterEffectCommand: Invalid parameter state {}, skipping command generation", + static_cast(parameter.state)); + return; + } + + // Effect is enabled and parameters are valid - generate commands + { bool needs_init{false}; switch (parameter.state) { @@ -379,8 +433,10 @@ void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset } break; default: - LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}", - static_cast(parameter.state)); + // Should not reach here after validation, but handle gracefully + LOG_WARNING(Service_Audio, "BiquadFilterEffectCommand: Unexpected state {}, treating as Updated", + static_cast(parameter.state)); + needs_init = false; break; } @@ -389,11 +445,6 @@ void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset 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); - } } }