fix(audio): biquad filter effect parameter version 2 handling

Fix ParameterVersion2 structure layout to match actual format:
- Add padding and use f32 for coefficients (was incorrectly using s16)
- Update state field offset from 0x17 to 0x25

Update command generation to:
- Read from correct structure based on version
- Convert float coefficients to fixed-point Q2.14 when using version 2

Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron
2025-12-06 15:31:24 +10:00
parent d397f379fe
commit b5a82bcfdc
4 changed files with 96 additions and 22 deletions

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/behavior/behavior_info.h" #include "audio_core/renderer/behavior/behavior_info.h"
@@ -257,11 +258,36 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBas
const bool use_float_processing) { const bool use_float_processing) {
auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)}; auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
const auto& parameter{
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
const auto state{reinterpret_cast<VoiceState::BiquadFilterState*>( const auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(
effect_info.GetStateBuffer() + channel * sizeof(VoiceState::BiquadFilterState))}; effect_info.GetStateBuffer() + channel * sizeof(VoiceState::BiquadFilterState))};
// Check which parameter version is being used
if (behavior->IsEffectInfoVersion2Supported()) {
const auto& parameter{
*reinterpret_cast<BiquadFilterInfo::ParameterVersion2*>(effect_info.GetParameter())};
cmd.input = buffer_offset + parameter.inputs[channel];
cmd.output = buffer_offset + parameter.outputs[channel];
// Convert float coefficients to fixed-point Q2.14 format (multiply by 16384)
constexpr f32 fixed_point_scale = 16384.0f;
cmd.biquad.b[0] = static_cast<s16>(
std::clamp(parameter.b[0] * fixed_point_scale, -32768.0f, 32767.0f));
cmd.biquad.b[1] = static_cast<s16>(
std::clamp(parameter.b[1] * fixed_point_scale, -32768.0f, 32767.0f));
cmd.biquad.b[2] = static_cast<s16>(
std::clamp(parameter.b[2] * fixed_point_scale, -32768.0f, 32767.0f));
cmd.biquad.a[0] = static_cast<s16>(
std::clamp(parameter.a[0] * fixed_point_scale, -32768.0f, 32767.0f));
cmd.biquad.a[1] = static_cast<s16>(
std::clamp(parameter.a[1] * fixed_point_scale, -32768.0f, 32767.0f));
// Effects use legacy fixed-point format
cmd.use_float_coefficients = false;
} else {
const auto& parameter{
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
cmd.input = buffer_offset + parameter.inputs[channel]; cmd.input = buffer_offset + parameter.inputs[channel];
cmd.output = buffer_offset + parameter.outputs[channel]; cmd.output = buffer_offset + parameter.outputs[channel];
@@ -270,6 +296,7 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBas
// Effects use legacy fixed-point format // Effects use legacy fixed-point format
cmd.use_float_coefficients = false; cmd.use_float_coefficients = false;
}
cmd.state = memory_pool->Translate(CpuAddr(state), cmd.state = memory_pool->Translate(CpuAddr(state),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/common/audio_renderer_parameter.h" #include "audio_core/common/audio_renderer_parameter.h"
@@ -361,12 +362,33 @@ void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBas
void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset, void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset,
EffectInfoBase& effect_info, EffectInfoBase& effect_info,
const s32 node_id) { const s32 node_id) {
const auto& parameter{ EffectInfoBase::ParameterState state{};
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; s8 channel_count{0};
if (render_context.behavior->IsEffectInfoVersion2Supported()) {
const auto* parameter = reinterpret_cast<const BiquadFilterInfo::ParameterVersion2*>(
effect_info.GetParameter());
if (!parameter) {
LOG_ERROR(Service_Audio, "Biquad filter parameter is null");
return;
}
state = parameter->state;
channel_count = parameter->channel_count;
} else {
const auto* parameter = reinterpret_cast<const BiquadFilterInfo::ParameterVersion1*>(
effect_info.GetParameter());
if (!parameter) {
LOG_ERROR(Service_Audio, "Biquad filter parameter is null");
return;
}
state = parameter->state;
channel_count = parameter->channel_count;
}
if (effect_info.IsEnabled()) { if (effect_info.IsEnabled()) {
bool needs_init{false}; bool needs_init{false};
switch (parameter.state) { switch (state) {
case EffectInfoBase::ParameterState::Initialized: case EffectInfoBase::ParameterState::Initialized:
needs_init = true; needs_init = true;
break; break;
@@ -375,22 +397,23 @@ void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset
if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) { if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) {
needs_init = false; needs_init = false;
} else { } else {
needs_init = parameter.state == EffectInfoBase::ParameterState::Updating; needs_init = state == EffectInfoBase::ParameterState::Updating;
} }
break; break;
default: default:
LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}", LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}, treating as uninitialized",
static_cast<u32>(parameter.state)); static_cast<u32>(state));
needs_init = true;
break; break;
} }
for (s8 channel = 0; channel < parameter.channel_count; channel++) { for (s8 channel = 0; channel < channel_count; channel++) {
command_buffer.GenerateBiquadFilterCommand( command_buffer.GenerateBiquadFilterCommand(
node_id, effect_info, buffer_offset, channel, needs_init, node_id, effect_info, buffer_offset, channel, needs_init,
render_context.behavior->UseBiquadFilterFloatProcessing()); render_context.behavior->UseBiquadFilterFloatProcessing());
} }
} else { } else {
for (s8 channel = 0; channel < parameter.channel_count; channel++) { for (s8 channel = 0; channel < channel_count; channel++) {
command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset, command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset,
channel); channel);
} }

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/effect/biquad_filter.h" #include "audio_core/renderer/effect/biquad_filter.h"
@@ -40,8 +41,28 @@ void BiquadFilterInfo::UpdateForCommandGeneration() {
usage_state = UsageState::Disabled; usage_state = UsageState::Disabled;
} }
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())}; // Determine which version structure is being used
params->state = ParameterState::Updated; // Version 1: state at offset 0x17, structure size ~24 bytes
// Version 2: state at offset 0x25, structure size ~40 bytes
auto params_v1{reinterpret_cast<ParameterVersion1*>(parameter.data())};
auto params_v2{reinterpret_cast<ParameterVersion2*>(parameter.data())};
// Check which state location contains a valid ParameterState value (0-2)
// Valid states: Initialized (0), Updating (1), Updated (2)
const auto state_v1_raw = *reinterpret_cast<const u8*>(&params_v1->state);
const auto state_v2_raw = *reinterpret_cast<const u8*>(&params_v2->state);
if (state_v1_raw <= 2) {
// Version 1 location has valid state, update there
params_v1->state = ParameterState::Updated;
} else if (state_v2_raw <= 2) {
// Version 2 location has valid state, update there
params_v2->state = ParameterState::Updated;
} else {
// Neither looks valid, update both (one will be wrong but command generator handles it)
params_v1->state = ParameterState::Updated;
params_v2->state = ParameterState::Updated;
}
} }
void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {} void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {}

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
@@ -27,10 +28,12 @@ public:
struct ParameterVersion2 { struct ParameterVersion2 {
/* 0x00 */ std::array<s8, MaxChannels> inputs; /* 0x00 */ std::array<s8, MaxChannels> inputs;
/* 0x06 */ std::array<s8, MaxChannels> outputs; /* 0x06 */ std::array<s8, MaxChannels> outputs;
/* 0x0C */ std::array<s16, 3> b; /* 0x0C */ u32 padding;
/* 0x12 */ std::array<s16, 2> a; /* 0x10 */ std::array<f32, 3> b;
/* 0x16 */ s8 channel_count; /* 0x1C */ std::array<f32, 2> a;
/* 0x17 */ ParameterState state; /* 0x24 */ s8 channel_count;
/* 0x25 */ ParameterState state;
/* 0x26 */ u16 reserved;
}; };
static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
"BiquadFilterInfo::ParameterVersion2 has the wrong size!"); "BiquadFilterInfo::ParameterVersion2 has the wrong size!");