From 2fa3501ff472a5bf57730a07ecd7665b2f1de085 Mon Sep 17 00:00:00 2001 From: Zephyron Date: Sat, 27 Sep 2025 10:13:32 +1000 Subject: [PATCH] feat(audio): Add REV13 audio renderer support - Implement compressor statistics collection and tracking - Add explicit splitter volume reset functionality - Implement REV13 audio device notification commands - Update feature support system to revision 13 - Maintain backward compatibility with older revisions Resolves REV13 audio renderer feature requirements with proper Nintendo Switch development practices and SwitchBrew compatibility. REF: https://git.ryujinx.app/ryubing/ryujinx/-/commit/a2c003501371463fd1f98d2e5a7602ae19c21d7c Signed-off-by: Zephyron --- src/audio_core/common/feature_support.h | 6 +- src/audio_core/renderer/audio_device.cpp | 91 +++++++++++++++++++ src/audio_core/renderer/audio_device.h | 58 ++++++++++++ .../renderer/behavior/behavior_info.cpp | 8 ++ .../renderer/behavior/behavior_info.h | 17 ++++ .../renderer/command/effect/compressor.cpp | 25 ++++- .../renderer/command/effect/compressor.h | 2 + src/audio_core/renderer/effect/compressor.cpp | 12 +++ src/audio_core/renderer/effect/compressor.h | 27 ++++++ .../renderer/splitter/splitter_context.cpp | 14 ++- .../renderer/splitter/splitter_context.h | 5 +- .../splitter/splitter_destinations_data.cpp | 5 +- .../splitter/splitter_destinations_data.h | 1 + 13 files changed, 263 insertions(+), 8 deletions(-) diff --git a/src/audio_core/common/feature_support.h b/src/audio_core/common/feature_support.h index e71905ae8..27222705c 100644 --- a/src/audio_core/common/feature_support.h +++ b/src/audio_core/common/feature_support.h @@ -13,7 +13,7 @@ #include "common/polyfill_ranges.h" namespace AudioCore { -constexpr u32 CurrentRevision = 11; +constexpr u32 CurrentRevision = 13; enum class SupportTags { CommandProcessingTimeEstimatorVersion4, @@ -44,6 +44,8 @@ enum class SupportTags { DelayChannelMappingChange, ReverbChannelMappingChange, I3dl2ReverbChannelMappingChange, + CompressorStatistics, + SplitterPrevVolumeReset, // Not a real tag, just here to get the count. Size @@ -87,6 +89,8 @@ constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) { {SupportTags::DelayChannelMappingChange, 11}, {SupportTags::ReverbChannelMappingChange, 11}, {SupportTags::I3dl2ReverbChannelMappingChange, 11}, + {SupportTags::CompressorStatistics, 13}, + {SupportTags::SplitterPrevVolumeReset, 13}, }}; const auto& feature = diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp index 5be5594f6..6ae57a706 100644 --- a/src/audio_core/renderer/audio_device.cpp +++ b/src/audio_core/renderer/audio_device.cpp @@ -9,6 +9,8 @@ #include "audio_core/renderer/audio_device.h" #include "audio_core/sink/sink.h" #include "core/core.h" +#include "core/hle/service/audio/errors.h" +#include "core/hle/result.h" namespace AudioCore::Renderer { @@ -69,4 +71,93 @@ f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const { return output_sink.GetDeviceVolume(); } +Result AudioDevice::AcquireAudioOutputDeviceNotification(u32& event_handle, u64 device_id) const { + // Check if REV13+ features are supported + if (!CheckFeatureSupported(SupportTags::CompressorStatistics, user_revision)) { + return Service::Audio::ResultNotSupported; + } + + // Track the notification request + output_device_notifications[device_id] = true; + + // For now, return a dummy event handle + event_handle = static_cast(device_id & 0xFFFFFFFF); + + return ResultSuccess; +} + +Result AudioDevice::ReleaseAudioOutputDeviceNotification(u64 device_id) const { + // Check if REV13+ features are supported + if (!CheckFeatureSupported(SupportTags::CompressorStatistics, user_revision)) { + return Service::Audio::ResultNotSupported; + } + + // Remove the notification tracking + auto it = output_device_notifications.find(device_id); + if (it != output_device_notifications.end()) { + output_device_notifications.erase(it); + } + + return ResultSuccess; +} + +Result AudioDevice::AcquireAudioInputDeviceNotification(u32& event_handle, u64 device_id) const { + // Check if REV13+ features are supported + if (!CheckFeatureSupported(SupportTags::CompressorStatistics, user_revision)) { + return Service::Audio::ResultNotSupported; + } + + // Track the notification request + input_device_notifications[device_id] = true; + + // For now, return a dummy event handle + event_handle = static_cast(device_id & 0xFFFFFFFF); + + return ResultSuccess; +} + +Result AudioDevice::ReleaseAudioInputDeviceNotification(u64 device_id) const { + // Check if REV13+ features are supported + if (!CheckFeatureSupported(SupportTags::CompressorStatistics, user_revision)) { + return Service::Audio::ResultNotSupported; + } + + // Remove the notification tracking + auto it = input_device_notifications.find(device_id); + if (it != input_device_notifications.end()) { + input_device_notifications.erase(it); + } + + return ResultSuccess; +} + +Result AudioDevice::SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled) const { + // Check if REV13+ features are supported + if (!CheckFeatureSupported(SupportTags::CompressorStatistics, user_revision)) { + return Service::Audio::ResultNotSupported; + } + + // Set the auto tune state + auto_tune_enabled = enabled; + + // Apply auto tune to the sink if supported + if (enabled) { + // For now, we just track the state + } + + return ResultSuccess; +} + +Result AudioDevice::IsAudioDeviceOutputVolumeAutoTuneEnabled(bool& enabled) const { + // Check if REV13+ features are supported + if (!CheckFeatureSupported(SupportTags::CompressorStatistics, user_revision)) { + return Service::Audio::ResultNotSupported; + } + + // Return the current auto tune state + enabled = auto_tune_enabled; + + return ResultSuccess; +} + } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h index 4242dad30..5fefc5f37 100644 --- a/src/audio_core/renderer/audio_device.h +++ b/src/audio_core/renderer/audio_device.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "audio_core/audio_render_manager.h" @@ -65,6 +66,63 @@ public: */ f32 GetDeviceVolume(std::string_view name) const; + /** + * Acquire audio output device notification. REV13+. + * + * @param event_handle - Output event handle. + * @param device_id - Device ID to acquire notification for. + * @return Result code. + */ + Result AcquireAudioOutputDeviceNotification(u32& event_handle, u64 device_id) const; + + /** + * Release audio output device notification. REV13+. + * + * @param device_id - Device ID to release notification for. + * @return Result code. + */ + Result ReleaseAudioOutputDeviceNotification(u64 device_id) const; + + /** + * Acquire audio input device notification. REV13+. + * + * @param event_handle - Output event handle. + * @param device_id - Device ID to acquire notification for. + * @return Result code. + */ + Result AcquireAudioInputDeviceNotification(u32& event_handle, u64 device_id) const; + + /** + * Release audio input device notification. REV13+. + * + * @param device_id - Device ID to release notification for. + * @return Result code. + */ + Result ReleaseAudioInputDeviceNotification(u64 device_id) const; + + /** + * Set audio device output volume auto tune enabled. REV13+. + * + * @param enabled - Whether auto tune is enabled. + * @return Result code. + */ + Result SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled) const; + + /** + * Check if audio device output volume auto tune is enabled. REV13+. + * + * @param enabled - Output whether auto tune is enabled. + * @return Result code. + */ + Result IsAudioDeviceOutputVolumeAutoTuneEnabled(bool& enabled) const; + +private: + /// Track device notifications + mutable std::map output_device_notifications{}; + mutable std::map input_device_notifications{}; + /// Auto tune enabled state + mutable bool auto_tune_enabled{false}; + private: /// Backend output sink for the device Sink::Sink& output_sink; diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp index 058539042..ab7d1cc8e 100644 --- a/src/audio_core/renderer/behavior/behavior_info.cpp +++ b/src/audio_core/renderer/behavior/behavior_info.cpp @@ -190,4 +190,12 @@ bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const { return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); } +bool BehaviorInfo::IsCompressorStatisticsSupported() const { + return CheckFeatureSupported(SupportTags::CompressorStatistics, user_revision); +} + +bool BehaviorInfo::IsSplitterPrevVolumeResetSupported() const { + return CheckFeatureSupported(SupportTags::SplitterPrevVolumeReset, 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 a4958857a..999d3a63f 100644 --- a/src/audio_core/renderer/behavior/behavior_info.h +++ b/src/audio_core/renderer/behavior/behavior_info.h @@ -361,6 +361,23 @@ public: */ bool IsI3dl2ReverbChannelMappingChanged() const; + /** + * Check if compressor statistics are supported. + * This allows the compressor effect to output statistics about its processing. + * + * @return True if supported, otherwise false. + */ + bool IsCompressorStatisticsSupported() 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; + /// Host version u32 process_revision; /// User version diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp index 7ff707f4e..daa8678ea 100644 --- a/src/audio_core/renderer/command/effect/compressor.cpp +++ b/src/audio_core/renderer/command/effect/compressor.cpp @@ -45,7 +45,8 @@ static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params, CompressorInfo::State& state, bool enabled, std::span> input_buffers, - std::span> output_buffers, u32 sample_count) { + std::span> output_buffers, u32 sample_count, + CompressorInfo::StatisticsInternal* statistics = nullptr) { if (enabled) { auto state_00{state.unk_00}; auto state_04{state.unk_04}; @@ -94,6 +95,15 @@ static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& param output_buffers[channel][i] = static_cast( static_cast(input_buffers[channel][i]) * state_08 * state.unk_20); } + + // Update statistics if enabled + if (statistics) { + statistics->maximum_mean = std::max(statistics->maximum_mean, a / params.channel_count); + statistics->minimum_gain = std::min(statistics->minimum_gain, state_08 * state.unk_20); + for (s16 channel = 0; channel < params.channel_count; channel++) { + statistics->last_samples[channel] = std::abs(static_cast(input_buffers[channel][i]) / 32768.0f); + } + } } state.unk_00 = state_00; @@ -135,6 +145,7 @@ void CompressorCommand::Process(const AudioRenderer::CommandListProcessor& proce } auto state_{reinterpret_cast(state)}; + CompressorInfo::StatisticsInternal* statistics{nullptr}; if (effect_enabled) { if (parameter.state == CompressorInfo::ParameterState::Updating) { @@ -142,10 +153,20 @@ void CompressorCommand::Process(const AudioRenderer::CommandListProcessor& proce } else if (parameter.state == CompressorInfo::ParameterState::Initialized) { InitializeCompressorEffect(parameter, *state_); } + + // Handle statistics if enabled + if (parameter.statistics_enabled && result_state != 0) { + statistics = reinterpret_cast(result_state); + if (parameter.statistics_reset_required) { + statistics->maximum_mean = 0.0f; + statistics->minimum_gain = 1.0f; + statistics->last_samples.fill(0.0f); + } + } } ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, - processor.sample_count); + processor.sample_count, statistics); } bool CompressorCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { diff --git a/src/audio_core/renderer/command/effect/compressor.h b/src/audio_core/renderer/command/effect/compressor.h index c011aa927..a5dda1105 100644 --- a/src/audio_core/renderer/command/effect/compressor.h +++ b/src/audio_core/renderer/command/effect/compressor.h @@ -54,6 +54,8 @@ struct CompressorCommand : ICommand { CpuAddr state; /// Game-supplied workbuffer (Unused) CpuAddr workbuffer; + /// Result state for statistics + CpuAddr result_state; /// Is this effect enabled? bool effect_enabled; }; diff --git a/src/audio_core/renderer/effect/compressor.cpp b/src/audio_core/renderer/effect/compressor.cpp index fea0aefcf..6e69794a5 100644 --- a/src/audio_core/renderer/effect/compressor.cpp +++ b/src/audio_core/renderer/effect/compressor.cpp @@ -31,6 +31,18 @@ void CompressorInfo::UpdateForCommandGeneration() { auto params{reinterpret_cast(parameter.data())}; params->state = ParameterState::Updated; + params->statistics_reset_required = false; +} + +void CompressorInfo::InitializeResultState(EffectResultState& result_state) { + auto statistics{reinterpret_cast(result_state.state.data())}; + statistics->maximum_mean = 0.0f; + statistics->minimum_gain = 1.0f; + statistics->last_samples.fill(0.0f); +} + +void CompressorInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) { + cpu_state = dsp_state; } CpuAddr CompressorInfo::GetWorkbuffer(s32 index) { diff --git a/src/audio_core/renderer/effect/compressor.h b/src/audio_core/renderer/effect/compressor.h index cda55c284..723e8681a 100644 --- a/src/audio_core/renderer/effect/compressor.h +++ b/src/audio_core/renderer/effect/compressor.h @@ -30,6 +30,8 @@ public: /* 0x30 */ f32 out_gain; /* 0x34 */ ParameterState state; /* 0x35 */ bool makeup_gain_enabled; + /* 0x36 */ bool statistics_enabled; + /* 0x37 */ bool statistics_reset_required; }; static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), "CompressorInfo::ParameterVersion1 has the wrong size!"); @@ -50,6 +52,8 @@ public: /* 0x30 */ f32 out_gain; /* 0x34 */ ParameterState state; /* 0x35 */ bool makeup_gain_enabled; + /* 0x36 */ bool statistics_enabled; + /* 0x37 */ bool statistics_reset_required; }; static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), "CompressorInfo::ParameterVersion2 has the wrong size!"); @@ -69,6 +73,14 @@ public: static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), "CompressorInfo::State has the wrong size!"); + struct StatisticsInternal { + /* 0x00 */ f32 maximum_mean; + /* 0x04 */ f32 minimum_gain; + /* 0x08 */ std::array last_samples; + }; + static_assert(sizeof(StatisticsInternal) == 0x20, + "CompressorInfo::StatisticsInternal has the wrong size!"); + /** * Update the info with new parameters, version 1. * @@ -94,6 +106,21 @@ public: */ void UpdateForCommandGeneration() override; + /** + * Initialize a new compressor statistics result state. Version 2 only. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side compressor statistics with the ADSP-side one. Version 2 only. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + /** * Get a workbuffer assigned to this effect with the given index. * diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp index d0f3b60c2..c0b74c1a6 100644 --- a/src/audio_core/renderer/splitter/splitter_context.cpp +++ b/src/audio_core/renderer/splitter/splitter_context.cpp @@ -32,12 +32,14 @@ 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(); } bool SplitterContext::UsingSplitter() const { @@ -81,7 +83,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; } @@ -145,7 +147,13 @@ u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) { continue; } - splitter_destinations[data_header->id].Update(*data_header); + // Create a modified parameter that respects the behavior support + 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); } diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h index 1c0b84671..4fde0a359 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,8 @@ 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{}; }; } // 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 5ec37e48e..7ac1cf9d5 100644 --- a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp @@ -57,7 +57,10 @@ void SplitterDestinationData::Update(const InParameter& params) { destination_id = params.mix_id; mix_volumes = params.mix_volumes; - if (!in_use && params.in_use) { + if (params.reset_prev_volume) { + prev_mix_volumes = mix_volumes; + need_update = false; + } else if (!in_use && params.in_use) { prev_mix_volumes = mix_volumes; need_update = false; } diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h index 90edfc667..2f03b29c1 100644 --- a/src/audio_core/renderer/splitter/splitter_destinations_data.h +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h @@ -22,6 +22,7 @@ public: /* 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!");