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: a2c0035013
Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron
2025-09-27 10:13:32 +10:00
parent 0001539f4a
commit 2fa3501ff4
13 changed files with 263 additions and 8 deletions

View File

@@ -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 =

View File

@@ -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<u32>(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<u32>(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

View File

@@ -3,6 +3,7 @@
#pragma once
#include <map>
#include <string_view>
#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<u64, bool> output_device_notifications{};
mutable std::map<u64, bool> input_device_notifications{};
/// Auto tune enabled state
mutable bool auto_tune_enabled{false};
private:
/// Backend output sink for the device
Sink::Sink& output_sink;

View File

@@ -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

View File

@@ -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

View File

@@ -45,7 +45,8 @@ static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2&
static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
CompressorInfo::State& state, bool enabled,
std::span<std::span<const s32>> input_buffers,
std::span<std::span<s32>> output_buffers, u32 sample_count) {
std::span<std::span<s32>> 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<s32>(
static_cast<f32>(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<f32>(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<CompressorInfo::State*>(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<CompressorInfo::StatisticsInternal*>(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) {

View File

@@ -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;
};

View File

@@ -31,6 +31,18 @@ void CompressorInfo::UpdateForCommandGeneration() {
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
params->state = ParameterState::Updated;
params->statistics_reset_required = false;
}
void CompressorInfo::InitializeResultState(EffectResultState& result_state) {
auto statistics{reinterpret_cast<StatisticsInternal*>(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) {

View File

@@ -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<f32, MaxChannels> 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.
*

View File

@@ -32,12 +32,14 @@ SplitterDestinationData& SplitterContext::GetData(const u32 index) {
void SplitterContext::Setup(std::span<SplitterInfo> 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);
}

View File

@@ -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<SplitterInfo> 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<SplitterInfo> 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

View File

@@ -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;
}

View File

@@ -22,6 +22,7 @@ public:
/* 0x08 */ std::array<f32, MaxMixBuffers> 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!");