mirror of
https://git.citron-emu.org/citron/emulator
synced 2025-12-21 19:43:34 +00:00
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:
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
|
||||
Reference in New Issue
Block a user