diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp index 792149f3d2..804aba438d 100644 --- a/src/audio_core/renderer/command/command_buffer.cpp +++ b/src/audio_core/renderer/command/command_buffer.cpp @@ -21,6 +21,15 @@ namespace AudioCore::Renderer { +namespace { +constexpr f32 BiquadParameterFixedScaleQ14 = 16384.0f; // 1 << 14 + +[[nodiscard]] inline s16 ToQ14Clamped(f32 v) { + const f32 scaled = std::clamp(v * BiquadParameterFixedScaleQ14, -32768.0f, 32767.0f); + return static_cast(scaled); +} +} // namespace + template T& CommandBuffer::GenerateStart(const s32 node_id) { if (size + sizeof(T) >= command_list.size_bytes()) { @@ -257,49 +266,44 @@ 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)}; + + const auto state{reinterpret_cast( + effect_info.GetStateBuffer() + channel * sizeof(VoiceState::BiquadFilterState))}; + if (behavior->IsEffectInfoVersion2Supported()) { - auto& cmd{GenerateStart(node_id)}; - const auto& parameter_v2{ + const auto& p{ *reinterpret_cast(effect_info.GetParameter())}; - if (!IsChannelCountValid(parameter_v2.channel_count) || channel < 0 || - channel >= parameter_v2.channel_count) { - return; - } - if (!parameter_v2.enable) { - // Effect disabled at parameter level: copy input -> output for this channel - GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset, channel); + + if (!IsChannelCountValid(p.channel_count) || channel < 0 || channel >= p.channel_count) { return; } - const auto state{reinterpret_cast( - effect_info.GetStateBuffer() + channel * sizeof(VoiceState::BiquadFilterState))}; - cmd.input = buffer_offset + parameter_v2.inputs[channel]; - cmd.output = buffer_offset + parameter_v2.outputs[channel]; - 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; + cmd.input = buffer_offset + p.inputs[channel]; + cmd.output = buffer_offset + p.outputs[channel]; - GenerateEnd(cmd); - return; - } - - auto& cmd{GenerateStart(node_id)}; + // Convert float coefficients to Q2.14 fixed-point as expected by the legacy DSP path. + cmd.biquad.b[0] = ToQ14Clamped(p.b[0]); + cmd.biquad.b[1] = ToQ14Clamped(p.b[1]); + cmd.biquad.b[2] = ToQ14Clamped(p.b[2]); + cmd.biquad.a[0] = ToQ14Clamped(p.a[0]); + cmd.biquad.a[1] = ToQ14Clamped(p.a[1]); + } else { + const auto& p{ + *reinterpret_cast(effect_info.GetParameter())}; - const auto& parameter{ - *reinterpret_cast(effect_info.GetParameter())}; - const auto state{reinterpret_cast( - effect_info.GetStateBuffer() + channel * sizeof(VoiceState::BiquadFilterState))}; + if (!IsChannelCountValid(p.channel_count) || channel < 0 || channel >= p.channel_count) { + return; + } - cmd.input = buffer_offset + parameter.inputs[channel]; - cmd.output = buffer_offset + parameter.outputs[channel]; + cmd.input = buffer_offset + p.inputs[channel]; + cmd.output = buffer_offset + p.outputs[channel]; - cmd.biquad.b = parameter.b; - cmd.biquad.a = parameter.a; + cmd.biquad.b = p.b; + cmd.biquad.a = p.a; + } - // Effects use legacy fixed-point format + // Effects always use the fixed-point coefficient path on the DSP. cmd.use_float_coefficients = false; cmd.state = memory_pool->Translate(CpuAddr(state), diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp index 11725c6255..bec6abbaf9 100644 --- a/src/audio_core/renderer/command/command_generator.cpp +++ b/src/audio_core/renderer/command/command_generator.cpp @@ -364,32 +364,37 @@ void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBas void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset, EffectInfoBase& effect_info, const s32 node_id) { + EffectInfoBase::ParameterState param_state{}; + s8 channel_count = 0; + if (render_context.behavior->IsEffectInfoVersion2Supported()) { - const auto& parameter_v2{ - *reinterpret_cast(effect_info.GetParameter())}; - const bool needs_init = false; - const bool use_float_processing = render_context.behavior->UseBiquadFilterFloatProcessing(); - const s8 channels = parameter_v2.channel_count > 0 ? parameter_v2.channel_count : 2; - 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 { - for (s8 channel = 0; channel < channels; channel++) { - command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset, - channel); - } + const auto* parameter = + reinterpret_cast(effect_info.GetParameter()); + if (!parameter) { + LOG_ERROR(Service_Audio, "Biquad filter parameter is null"); + return; + } + param_state = parameter->state; + channel_count = parameter->channel_count; + } else { + const auto* parameter = + reinterpret_cast(effect_info.GetParameter()); + if (!parameter) { + LOG_ERROR(Service_Audio, "Biquad filter parameter is null"); + return; } + param_state = parameter->state; + channel_count = parameter->channel_count; + } + + if (channel_count <= 0) { return; } - const auto& parameter{ - *reinterpret_cast(effect_info.GetParameter())}; if (effect_info.IsEnabled()) { bool needs_init{false}; - switch (parameter.state) { + switch (param_state) { case EffectInfoBase::ParameterState::Initialized: needs_init = true; break; @@ -398,22 +403,26 @@ void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) { needs_init = false; } else { - needs_init = parameter.state == EffectInfoBase::ParameterState::Updating; + needs_init = param_state == EffectInfoBase::ParameterState::Updating; } break; default: - LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}", - static_cast(parameter.state)); + LOG_ERROR(Service_Audio, + "Invalid biquad parameter state {}, treating as uninitialized", + static_cast(param_state)); + needs_init = true; 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()); + const bool use_float_processing = + render_context.behavior->UseBiquadFilterFloatProcessing(); + + for (s8 channel = 0; channel < channel_count; channel++) { + command_buffer.GenerateBiquadFilterCommand(node_id, effect_info, buffer_offset, channel, + needs_init, use_float_processing); } } 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, channel); } diff --git a/src/audio_core/renderer/effect/biquad_filter.cpp b/src/audio_core/renderer/effect/biquad_filter.cpp index 08161d8400..3906a473dd 100644 --- a/src/audio_core/renderer/effect/biquad_filter.cpp +++ b/src/audio_core/renderer/effect/biquad_filter.cpp @@ -34,14 +34,22 @@ void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info, } void BiquadFilterInfo::UpdateForCommandGeneration() { - if (enabled) { - usage_state = UsageState::Enabled; + usage_state = enabled ? UsageState::Enabled : UsageState::Disabled; + + auto* params_v1 = reinterpret_cast(parameter.data()); + auto* params_v2 = reinterpret_cast(parameter.data()); + + const auto raw_state_v1 = static_cast(params_v1->state); + const auto raw_state_v2 = static_cast(params_v2->state); + + if (raw_state_v1 <= static_cast(ParameterState::Updated)) { + params_v1->state = ParameterState::Updated; + } else if (raw_state_v2 <= static_cast(ParameterState::Updated)) { + params_v2->state = ParameterState::Updated; } else { - usage_state = UsageState::Disabled; + params_v1->state = ParameterState::Updated; + params_v2->state = ParameterState::Updated; } - - auto params{reinterpret_cast(parameter.data())}; - params->state = ParameterState::Updated; } void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {} diff --git a/src/audio_core/renderer/effect/biquad_filter.h b/src/audio_core/renderer/effect/biquad_filter.h index 07a967a4fd..e268cfa11a 100644 --- a/src/audio_core/renderer/effect/biquad_filter.h +++ b/src/audio_core/renderer/effect/biquad_filter.h @@ -28,12 +28,14 @@ public: "BiquadFilterInfo::ParameterVersion1 has the wrong size!"); struct ParameterVersion2 { - bool enable; - s8 channel_count; - s8 inputs[MaxChannels]; - s8 outputs[MaxChannels]; - std::array b; - std::array a; + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ u32 padding; + /* 0x10 */ std::array b; + /* 0x1C */ std::array a; + /* 0x24 */ s8 channel_count; + /* 0x25 */ ParameterState state; + /* 0x26 */ u16 reserved; }; static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), "BiquadFilterInfo::ParameterVersion2 has the wrong size!");