diff --git a/src/audio_core/common/feature_support.h b/src/audio_core/common/feature_support.h index 91d6991416..59c1ff00ef 100644 --- a/src/audio_core/common/feature_support.h +++ b/src/audio_core/common/feature_support.h @@ -16,7 +16,7 @@ #include namespace AudioCore { -constexpr u32 CurrentRevision = 16; +constexpr u32 CurrentRevision = 15; enum class SupportTags { CommandProcessingTimeEstimatorVersion4, @@ -47,6 +47,9 @@ enum class SupportTags { DelayChannelMappingChange, ReverbChannelMappingChange, I3dl2ReverbChannelMappingChange, + SplitterPrevVolumeReset, + SplitterDestinationV2b, + VoiceInParameterV2, // Not a real tag, just here to get the count. Size @@ -91,6 +94,9 @@ constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) { {SupportTags::DelayChannelMappingChange, 11}, {SupportTags::ReverbChannelMappingChange, 11}, {SupportTags::I3dl2ReverbChannelMappingChange, 11}, + {SupportTags::SplitterPrevVolumeReset, 13}, + {SupportTags::SplitterDestinationV2b, 15}, + {SupportTags::VoiceInParameterV2, 15}, }}; const auto& feature = diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp index d0df1f29de..4bd3912e0f 100644 --- a/src/audio_core/renderer/behavior/behavior_info.cpp +++ b/src/audio_core/renderer/behavior/behavior_info.cpp @@ -193,4 +193,16 @@ bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const { return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); } +bool BehaviorInfo::IsSplitterPrevVolumeResetSupported() const { + return CheckFeatureSupported(SupportTags::SplitterPrevVolumeReset, user_revision); +} + +bool BehaviorInfo::IsSplitterDestinationV2bSupported() const { + return CheckFeatureSupported(SupportTags::SplitterDestinationV2b, user_revision); +} + +bool BehaviorInfo::IsVoiceInParameterV2Supported() const { + return CheckFeatureSupported(SupportTags::VoiceInParameterV2, user_revision); +} + } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h index a4958857a4..57c7a4023a 100644 --- a/src/audio_core/renderer/behavior/behavior_info.h +++ b/src/audio_core/renderer/behavior/behavior_info.h @@ -361,6 +361,31 @@ public: */ bool IsI3dl2ReverbChannelMappingChanged() const; + /** + * Check if explicit previous mix volume reset is supported for splitters. + * This allows splitters to explicitly reset their previous mix volumes instead of + * doing so implicitly on first use. + * + * @return True if supported, otherwise false. + */ + bool IsSplitterPrevVolumeResetSupported() const; + + /** + * Check if splitter destination v2b parameter format is supported (revision 15+). + * This uses the extended parameter format with biquad filter fields. + * + * @return True if supported, otherwise false. + */ + bool IsSplitterDestinationV2bSupported() const; + + /** + * Check if voice input parameter v2 format is supported (revision 15+). + * This uses the extended parameter format with float biquad filters. + * + * @return True if supported, otherwise false. + */ + bool IsVoiceInParameterV2Supported() const; + /// Host version u32 process_revision; /// User version diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp index 20f6cda3a2..39bbc91ded 100644 --- a/src/audio_core/renderer/behavior/info_updater.cpp +++ b/src/audio_core/renderer/behavior/info_updater.cpp @@ -64,8 +64,6 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, behaviour.IsMemoryForceMappingEnabled()); const auto voice_count{voice_context.GetCount()}; - std::span in_params{ - reinterpret_cast(input), voice_count}; std::span out_params{reinterpret_cast(output), voice_count}; @@ -76,8 +74,104 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, u32 new_voice_count{0}; + // Two input formats exist: legacy (0x170) and v2 with float biquad (0x188). + const bool use_v2 = behaviour.IsVoiceInParameterV2Supported(); + const u32 in_stride = use_v2 ? 0x188u : static_cast(sizeof(VoiceInfo::InParameter)); + for (u32 i = 0; i < voice_count; i++) { - const auto& in_param{in_params[i]}; + VoiceInfo::InParameter local_in{}; + std::array float_biquads{}; + + if (!use_v2) { + const auto* in_param_ptr = reinterpret_cast( + input + i * sizeof(VoiceInfo::InParameter)); + local_in = *in_param_ptr; + } else { + struct VoiceInParameterV2 { + u32 id; + u32 node_id; + bool is_new; + bool in_use; + PlayState play_state; + SampleFormat sample_format; + u32 sample_rate; + u32 priority; + u32 sort_order; + u32 channel_count; + f32 pitch; + f32 volume; + // Two BiquadFilterParameter2 (0x18 each) -> ignored/converted + struct BiquadV2 { + bool enable; + u8 r1; + u8 r2; + u8 r3; + std::array b; + std::array a; + } biquads[2]; + u32 wave_buffer_count; + u32 wave_buffer_index; + u32 reserved1; + u64 src_data_address; + u64 src_data_size; + s32 mix_id; + u32 splitter_id; + std::array wavebuffers; + std::array channel_resource_ids; + bool clear_voice_drop; + u8 flush_wave_buffer_count; + u16 reserved2; + VoiceInfo::Flags flags; + SrcQuality src_quality; + u32 external_ctx; + u32 external_ctx_size; + u32 reserved3[2]; + }; + const auto* vin = reinterpret_cast(input + i * in_stride); + local_in.id = vin->id; + local_in.node_id = vin->node_id; + local_in.is_new = vin->is_new; + local_in.in_use = vin->in_use; + local_in.play_state = vin->play_state; + local_in.sample_format = vin->sample_format; + local_in.sample_rate = vin->sample_rate; + local_in.priority = static_cast(vin->priority); + local_in.sort_order = static_cast(vin->sort_order); + local_in.channel_count = vin->channel_count; + local_in.pitch = vin->pitch; + local_in.volume = vin->volume; + + // For REV15+, we keep float coefficients separate and only convert for compatibility + for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) { + const auto& src = vin->biquads[filter_idx]; + auto& dst = local_in.biquads[filter_idx]; + dst.enabled = src.enable; + // Convert float coefficients to fixed-point Q2.14 for legacy path + dst.b[0] = static_cast(std::clamp(src.b[0] * 16384.0f, -32768.0f, 32767.0f)); + dst.b[1] = static_cast(std::clamp(src.b[1] * 16384.0f, -32768.0f, 32767.0f)); + dst.b[2] = static_cast(std::clamp(src.b[2] * 16384.0f, -32768.0f, 32767.0f)); + dst.a[0] = static_cast(std::clamp(src.a[0] * 16384.0f, -32768.0f, 32767.0f)); + dst.a[1] = static_cast(std::clamp(src.a[1] * 16384.0f, -32768.0f, 32767.0f)); + + // Also store the native float version + float_biquads[filter_idx].enabled = src.enable; + float_biquads[filter_idx].numerator = src.b; + float_biquads[filter_idx].denominator = src.a; + } + local_in.wave_buffer_count = vin->wave_buffer_count; + local_in.wave_buffer_index = static_cast(vin->wave_buffer_index); + local_in.src_data_address = static_cast(vin->src_data_address); + local_in.src_data_size = vin->src_data_size; + local_in.mix_id = static_cast(vin->mix_id); + local_in.splitter_id = vin->splitter_id; + local_in.wave_buffer_internal = vin->wavebuffers; + local_in.channel_resource_ids = vin->channel_resource_ids; + local_in.clear_voice_drop = vin->clear_voice_drop; + local_in.flush_buffer_count = vin->flush_wave_buffer_count; + local_in.flags = vin->flags; + local_in.src_quality = vin->src_quality; + } + const auto& in_param = local_in; std::array voice_states{}; if (!in_param.in_use) { @@ -101,6 +195,14 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, BehaviorInfo::ErrorInfo update_error{}; voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour); + // For REV15+, store the native float biquad coefficients + if (use_v2) { + voice_info.use_float_biquads = true; + voice_info.biquads_float = float_biquads; + } else { + voice_info.use_float_biquads = false; + } + if (!update_error.error_code.IsSuccess()) { behaviour.AppendError(update_error); } @@ -121,7 +223,7 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, new_voice_count += in_param.channel_count; } - auto consumed_input_size{voice_count * static_cast(sizeof(VoiceInfo::InParameter))}; + auto consumed_input_size{voice_count * in_stride}; auto consumed_output_size{voice_count * static_cast(sizeof(VoiceInfo::OutStatus))}; if (consumed_input_size != in_header->voices_size) { LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}", @@ -257,18 +359,31 @@ Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_co EffectContext& effect_context, SplitterContext& splitter_context) { s32 mix_count{0}; u32 consumed_input_size{0}; + u32 input_mix_size{0}; if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { auto in_dirty_params{reinterpret_cast(input)}; mix_count = in_dirty_params->count; + + // Validate against expected header size to ensure structure is correct + if (mix_count < 0 || mix_count > 0x100) { + LOG_ERROR( + Service_Audio, + "Invalid mix count from dirty parameter: count={}, magic=0x{:X}, expected_size={}", + mix_count, in_dirty_params->magic, in_header->mix_size); + return Service::Audio::ResultInvalidUpdateInfo; + } + + consumed_input_size += static_cast(sizeof(MixInfo::InDirtyParameter)); input += sizeof(MixInfo::InDirtyParameter); - consumed_input_size = static_cast(sizeof(MixInfo::InDirtyParameter) + - mix_count * sizeof(MixInfo::InParameter)); } else { mix_count = mix_context.GetCount(); - consumed_input_size = static_cast(mix_count * sizeof(MixInfo::InParameter)); } + input_mix_size = static_cast(mix_count * sizeof(MixInfo::InParameter)); + consumed_input_size += input_mix_size; + + if (mix_buffer_count == 0) { return Service::Audio::ResultInvalidUpdateInfo; } diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp index d680e0c74b..a0a574fc64 100644 --- a/src/audio_core/renderer/command/command_buffer.cpp +++ b/src/audio_core/renderer/command/command_buffer.cpp @@ -237,6 +237,13 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& vo cmd.biquad = voice_info.biquads[biquad_index]; + if (voice_info.use_float_biquads) { + cmd.biquad_float = voice_info.biquads_float[biquad_index]; + cmd.use_float_coefficients = true; + } else { + cmd.use_float_coefficients = false; + } + cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); @@ -263,6 +270,9 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBas cmd.biquad.b = parameter.b; cmd.biquad.a = parameter.a; + // Effects use legacy fixed-point format + cmd.use_float_coefficients = false; + cmd.state = memory_pool->Translate(CpuAddr(state), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); @@ -658,6 +668,13 @@ void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, Voice cmd.output = buffer_count + channel; cmd.biquads = voice_info.biquads; + if (voice_info.use_float_biquads) { + cmd.biquads_float = voice_info.biquads_float; + cmd.use_float_coefficients = true; + } else { + cmd.use_float_coefficients = false; + } + cmd.states[0] = memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp index 539e0c6370..4ad3184079 100644 --- a/src/audio_core/renderer/command/effect/biquad_filter.cpp +++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp @@ -51,6 +51,40 @@ void ApplyBiquadFilterFloat(std::span output, std::span input, state.s3 = Common::BitCast(s[3]); } +/** + * Biquad filter float implementation with native float coefficients. + */ +void ApplyBiquadFilterFloat2(std::span output, std::span input, + std::array& b, std::array& a, + VoiceState::BiquadFilterState& state, const u32 sample_count) { + constexpr f64 min{std::numeric_limits::min()}; + constexpr f64 max{std::numeric_limits::max()}; + + std::array b_double{static_cast(b[0]), static_cast(b[1]), + static_cast(b[2])}; + std::array a_double{static_cast(a[0]), static_cast(a[1])}; + std::array s{Common::BitCast(state.s0), Common::BitCast(state.s1), + Common::BitCast(state.s2), Common::BitCast(state.s3)}; + + for (u32 i = 0; i < sample_count; i++) { + f64 in_sample{static_cast(input[i])}; + auto sample{in_sample * b_double[0] + s[0] * b_double[1] + s[1] * b_double[2] + + s[2] * a_double[0] + s[3] * a_double[1]}; + + output[i] = static_cast(std::clamp(sample, min, max)); + + s[1] = s[0]; + s[0] = in_sample; + s[3] = s[2]; + s[2] = sample; + } + + state.s0 = Common::BitCast(s[0]); + state.s1 = Common::BitCast(s[1]); + state.s2 = Common::BitCast(s[2]); + state.s3 = Common::BitCast(s[3]); +} + /** * Biquad filter s32 implementation. * @@ -98,8 +132,14 @@ void BiquadFilterCommand::Process(const AudioRenderer::CommandListProcessor& pro processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; if (use_float_processing) { - ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, - processor.sample_count); + // REV15+: Use native float coefficients if available + if (use_float_coefficients) { + ApplyBiquadFilterFloat2(output_buffer, input_buffer, biquad_float.numerator, + biquad_float.denominator, *state_, processor.sample_count); + } else { + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, + processor.sample_count); + } } else { ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_, processor.sample_count); diff --git a/src/audio_core/renderer/command/effect/biquad_filter.h b/src/audio_core/renderer/command/effect/biquad_filter.h index 0e903930a6..5f4626c326 100644 --- a/src/audio_core/renderer/command/effect/biquad_filter.h +++ b/src/audio_core/renderer/command/effect/biquad_filter.h @@ -50,12 +50,16 @@ struct BiquadFilterCommand : ICommand { s16 output; /// Input parameters for biquad VoiceInfo::BiquadFilterParameter biquad; + /// Input parameters for biquad (REV15+ native float) + VoiceInfo::BiquadFilterParameter2 biquad_float; /// Biquad state, updated each call CpuAddr state; /// If true, reset the state bool needs_init; /// If true, use float processing rather than int bool use_float_processing; + /// If true, use native float coefficients (REV15+) + bool use_float_coefficients; }; /** @@ -72,4 +76,18 @@ void ApplyBiquadFilterFloat(std::span output, std::span input, std::array& b, std::array& a, VoiceState::BiquadFilterState& state, const u32 sample_count); +/** + * Biquad filter float implementation with native float coefficients (SDK REV15+). + * + * @param output - Output container for filtered samples. + * @param input - Input container for samples to be filtered. + * @param b - Feedforward coefficients (float). + * @param a - Feedback coefficients (float). + * @param state - State to track previous samples. + * @param sample_count - Number of samples to process. + */ +void ApplyBiquadFilterFloat2(std::span output, std::span input, + std::array& b, std::array& a, + VoiceState::BiquadFilterState& state, const u32 sample_count); + } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp index 208bbeaf29..78cd4e06d8 100644 --- a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp @@ -33,8 +33,14 @@ void MultiTapBiquadFilterCommand::Process(const AudioRenderer::CommandListProces *state = {}; } - ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, - processor.sample_count); + // REV15+: Use native float coefficients if available + if (use_float_coefficients) { + ApplyBiquadFilterFloat2(output_buffer, input_buffer, biquads_float[i].numerator, + biquads_float[i].denominator, *state, processor.sample_count); + } else { + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, + processor.sample_count); + } } } diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h index 50fce80b0c..f8c359bdc1 100644 --- a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h @@ -49,12 +49,16 @@ struct MultiTapBiquadFilterCommand : ICommand { s16 output; /// Biquad parameters std::array biquads; + /// Biquad parameters (REV15+ native float) + std::array biquads_float; /// Biquad states, updated each call std::array states; /// If each biquad needs initialisation std::array needs_init; /// Number of active biquads u8 filter_tap_count; + /// If true, use native float coefficients (REV15+) + bool use_float_coefficients; }; } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp index fb118e981e..9d6ac646e9 100644 --- a/src/audio_core/renderer/splitter/splitter_context.cpp +++ b/src/audio_core/renderer/splitter/splitter_context.cpp @@ -35,12 +35,15 @@ SplitterDestinationData& SplitterContext::GetData(const u32 index) { void SplitterContext::Setup(std::span splitter_infos_, const u32 splitter_info_count_, SplitterDestinationData* splitter_destinations_, - const u32 destination_count_, const bool splitter_bug_fixed_) { + const u32 destination_count_, const bool splitter_bug_fixed_, + const BehaviorInfo& behavior) { splitter_infos = splitter_infos_; info_count = splitter_info_count_; splitter_destinations = splitter_destinations_; destinations_count = destination_count_; splitter_bug_fixed = splitter_bug_fixed_; + splitter_prev_volume_reset_supported = behavior.IsSplitterPrevVolumeResetSupported(); + splitter_float_coeff_supported = behavior.IsSplitterDestinationV2bSupported(); } bool SplitterContext::UsingSplitter() const { @@ -84,7 +87,7 @@ bool SplitterContext::Initialize(const BehaviorInfo& behavior, } Setup(splitter_infos, params.splitter_infos, splitter_destinations, - params.splitter_destinations, behavior.IsSplitterBugFixed()); + params.splitter_destinations, behavior.IsSplitterBugFixed(), behavior); } return true; } @@ -137,19 +140,59 @@ u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_ u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) { for (u32 i = 0; i < count; i++) { - auto data_header{ - reinterpret_cast(input + offset)}; - - if (data_header->magic != GetSplitterSendDataMagic()) { - continue; - } - - if (data_header->id < 0 || data_header->id > destinations_count) { - continue; + // Version selection based on float coeff/biquad v2b support. + if (!splitter_float_coeff_supported) { + const auto* data_header = + reinterpret_cast(input + offset); + + if (data_header->magic != GetSplitterSendDataMagic()) { + continue; + } + if (data_header->id < 0 || data_header->id > destinations_count) { + continue; + } + + auto modified_params = *data_header; + if (!splitter_prev_volume_reset_supported) { + modified_params.reset_prev_volume = false; + } + splitter_destinations[data_header->id].Update(modified_params); + offset += sizeof(SplitterDestinationData::InParameter); + } else { + // Version 2b: struct contains extra biquad filter fields + const auto* data_header_v2b = + reinterpret_cast(input + + offset); + + if (data_header_v2b->magic != GetSplitterSendDataMagic()) { + continue; + } + if (data_header_v2b->id < 0 || data_header_v2b->id > destinations_count) { + continue; + } + + // Map common fields to the old format + SplitterDestinationData::InParameter mapped{}; + mapped.magic = data_header_v2b->magic; + mapped.id = data_header_v2b->id; + mapped.mix_volumes = data_header_v2b->mix_volumes; + mapped.mix_id = data_header_v2b->mix_id; + mapped.in_use = data_header_v2b->in_use; + mapped.reset_prev_volume = + splitter_prev_volume_reset_supported ? data_header_v2b->reset_prev_volume : false; + + // Store biquad filters from V2b (REV15+) + auto& destination = splitter_destinations[data_header_v2b->id]; + destination.Update(mapped); + + // Copy biquad filter parameters + auto biquad_filters = destination.GetBiquadFilters(); + for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) { + biquad_filters[filter_idx] = data_header_v2b->biquad_filters[filter_idx]; + } + + offset += static_cast(sizeof(SplitterDestinationData::InParameterVersion2b)); } - - splitter_destinations[data_header->id].Update(*data_header); - offset += sizeof(SplitterDestinationData::InParameter); } return offset; diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h index 1c0b846719..6f5ec65746 100644 --- a/src/audio_core/renderer/splitter/splitter_context.h +++ b/src/audio_core/renderer/splitter/splitter_context.h @@ -168,10 +168,11 @@ private: * @param splitter_destinations - Workbuffer for splitter destinations. * @param destination_count - Number of destinations in the workbuffer. * @param splitter_bug_fixed - Is the splitter bug fixed? + * @param behavior - Behavior info for feature support. */ void Setup(std::span splitter_infos, u32 splitter_info_count, SplitterDestinationData* splitter_destinations, u32 destination_count, - bool splitter_bug_fixed); + bool splitter_bug_fixed, const BehaviorInfo& behavior); /// Workbuffer for splitters std::span splitter_infos{}; @@ -183,6 +184,10 @@ private: s32 destinations_count{}; /// Is the splitter bug fixed? bool splitter_bug_fixed{}; + /// Is explicit previous mix volume reset supported? + bool splitter_prev_volume_reset_supported{}; + /// Is float coefficient/biquad filter v2b parameter supported? + bool splitter_float_coeff_supported{}; }; } // namespace Renderer diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp index 5ec37e48e1..58e1d7d562 100644 --- a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp @@ -84,4 +84,14 @@ void SplitterDestinationData::SetNext(SplitterDestinationData* next_) { next = next_; } +std::span +SplitterDestinationData::GetBiquadFilters() { + return biquad_filters; +} + +std::span +SplitterDestinationData::GetBiquadFilters() const { + return biquad_filters; +} + } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h index 90edfc667c..3b76b6bd4e 100644 --- a/src/audio_core/renderer/splitter/splitter_destinations_data.h +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h @@ -16,16 +16,46 @@ namespace AudioCore::Renderer { */ class SplitterDestinationData { public: + /** + * Biquad filter parameter with float coefficients (SDK REV15+). + * Defined here to avoid circular dependency with VoiceInfo. + */ + struct BiquadFilterParameter2 { + /* 0x00 */ bool enabled; + /* 0x01 */ u8 reserved1; + /* 0x02 */ u8 reserved2; + /* 0x03 */ u8 reserved3; + /* 0x04 */ std::array numerator; // b0, b1, b2 + /* 0x10 */ std::array denominator; // a1, a2 (a0 = 1) + }; + static_assert(sizeof(BiquadFilterParameter2) == 0x18, + "BiquadFilterParameter2 has the wrong size!"); + struct InParameter { /* 0x00 */ u32 magic; // 'SNDD' /* 0x04 */ s32 id; /* 0x08 */ std::array mix_volumes; /* 0x68 */ u32 mix_id; /* 0x6C */ bool in_use; + /* 0x6D */ bool reset_prev_volume; }; static_assert(sizeof(InParameter) == 0x70, "SplitterDestinationData::InParameter has the wrong size!"); + struct InParameterVersion2b { + /* 0x00 */ u32 magic; // 'SNDD' + /* 0x04 */ s32 id; + /* 0x08 */ std::array mix_volumes; + /* 0x68 */ u32 mix_id; + /* 0x6C */ std::array + biquad_filters; + /* 0x9C */ bool in_use; + /* 0x9D */ bool reset_prev_volume; + /* 0x9E */ u8 reserved[10]; + }; + static_assert(sizeof(InParameterVersion2b) == 0xA8, + "SplitterDestinationData::InParameterVersion2b has the wrong size!"); + SplitterDestinationData(s32 id); /** @@ -115,6 +145,20 @@ public: */ void SetNext(SplitterDestinationData* next); + /** + * Get biquad filter parameters for this destination (REV15+). + * + * @return Span of biquad filter parameters. + */ + std::span GetBiquadFilters(); + + /** + * Get const biquad filter parameters for this destination (REV15+). + * + * @return Const span of biquad filter parameters. + */ + std::span GetBiquadFilters() const; + private: /// Id of this destination const s32 id; @@ -124,6 +168,8 @@ private: std::array mix_volumes{0.0f}; /// Previous mix volumes std::array prev_mix_volumes{0.0f}; + /// Biquad filter parameters (REV15+) + std::array biquad_filters{}; /// Next destination in the mix chain SplitterDestinationData* next{}; /// Is this destination in use? diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h index 14a687dcb7..bc9e550efe 100644 --- a/src/audio_core/renderer/voice/voice_info.h +++ b/src/audio_core/renderer/voice/voice_info.h @@ -135,6 +135,17 @@ public: static_assert(sizeof(BiquadFilterParameter) == 0xC, "VoiceInfo::BiquadFilterParameter has the wrong size!"); + struct BiquadFilterParameter2 { + /* 0x00 */ bool enabled; + /* 0x01 */ u8 reserved1; + /* 0x02 */ u8 reserved2; + /* 0x03 */ u8 reserved3; + /* 0x04 */ std::array numerator; // b0, b1, b2 + /* 0x10 */ std::array denominator; // a1, a2 (a0 = 1) + }; + static_assert(sizeof(BiquadFilterParameter2) == 0x18, + "VoiceInfo::BiquadFilterParameter2 has the wrong size!"); + struct InParameter { /* 0x000 */ u32 id; /* 0x004 */ u32 node_id; @@ -168,6 +179,43 @@ public: }; static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!"); + struct InParameter2 { + /* 0x000 */ u32 id; + /* 0x004 */ u32 node_id; + /* 0x008 */ bool is_new; + /* 0x009 */ bool in_use; + /* 0x00A */ PlayState play_state; + /* 0x00B */ SampleFormat sample_format; + /* 0x00C */ u32 sample_rate; + /* 0x010 */ s32 priority; + /* 0x014 */ s32 sort_order; + /* 0x018 */ u32 channel_count; + /* 0x01C */ f32 pitch; + /* 0x020 */ f32 volume; + /* 0x024 */ std::array biquads; + /* 0x054 */ u32 wave_buffer_count; + /* 0x058 */ u32 wave_buffer_index; + /* 0x05C */ u32 reserved1; + /* 0x060 */ CpuAddr src_data_address; + /* 0x068 */ u64 src_data_size; + /* 0x070 */ u32 mix_id; + /* 0x074 */ u32 splitter_id; + /* 0x078 */ std::array wave_buffer_internal; + /* 0x158 */ std::array channel_resource_ids; + /* 0x170 */ bool clear_voice_drop; + /* 0x171 */ u8 flush_buffer_count; + /* 0x172 */ u16 reserved2; + /* 0x174 */ Flags flags; + /* 0x175 */ u8 reserved3; + /* 0x176 */ SrcQuality src_quality; + /* 0x177 */ u8 reserved4; + /* 0x178 */ u32 external_context; + /* 0x17C */ u32 external_context_size; + /* 0x180 */ u32 reserved5; + /* 0x184 */ u32 reserved6; + }; + static_assert(sizeof(InParameter2) == 0x188, "VoiceInfo::InParameter2 has the wrong size!"); + struct OutStatus { /* 0x00 */ u64 played_sample_count; /* 0x08 */ u32 wave_buffers_consumed; @@ -349,6 +397,10 @@ public: f32 prev_volume{}; /// Biquad filters for generating filter commands on this voice std::array biquads{}; + /// Float biquad filters for REV15+ (native float coefficients) + std::array biquads_float{}; + /// Use float biquad coefficients (REV15+) + bool use_float_biquads{}; /// Number of active wavebuffers u32 wave_buffer_count{}; /// Current playing wavebuffer index diff --git a/src/hid_core/resources/ring_lifo.h b/src/hid_core/resources/ring_lifo.h index 0816784e08..5dafd7d819 100644 --- a/src/hid_core/resources/ring_lifo.h +++ b/src/hid_core/resources/ring_lifo.h @@ -44,8 +44,7 @@ struct Lifo { buffer_count++; } buffer_tail = GetNextEntryIndex(); - const auto& previous_entry = ReadPreviousEntry(); - entries[buffer_tail].sampling_number = previous_entry.sampling_number + 1; + entries[buffer_tail].sampling_number = new_state.sampling_number << 1; entries[buffer_tail].state = new_state; } };