audio_core/renderer: Add compressor and splitter support for Rev13

Implement new audio features available in AudioRenderer Revision 13:

- Add AudioRendererRevision enum to track version-specific features
- Implement CompressorEffect with statistics tracking support
- Add SplitterDestination with previous volume reset functionality
- Add version checks for feature compatibility

The compressor provides dynamic range compression with configurable
parameters and optional statistics tracking. The splitter improvements
allow for more flexible volume management between audio transitions.

These changes maintain compatibility with older revisions while enabling
new features in Rev13.
This commit is contained in:
Zephyron 2025-02-04 16:32:59 +10:00
parent 89ecb641f1
commit 4cc01f6c71
No known key found for this signature in database
GPG key ID: 2177ADED8AC966AF
6 changed files with 176 additions and 1 deletions

2
externals/vcpkg vendored

@ -1 +1 @@
Subproject commit 2ded45cc7a35667ad8d96e5e50b4a24afacafb3a
Subproject commit 561cf50a731761f9890fb720f830cb3501b8472d

View file

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -23,6 +24,11 @@ struct AudioRendererParameterInternal;
namespace Renderer {
class Manager;
enum class AudioRendererRevision {
Rev12 = 12 << 24,
Rev13 = 13 << 24,
};
/**
* Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls.
*/
@ -82,6 +88,14 @@ public:
Result RequestUpdate(std::span<const u8> input, std::span<u8> performance,
std::span<u8> output);
bool IsCompressorStatisticsSupported() const {
return revision >= static_cast<u32>(AudioRendererRevision::Rev13);
}
bool IsSplitterPrevVolumeResetSupported() const {
return revision >= static_cast<u32>(AudioRendererRevision::Rev13);
}
private:
/// System core
Core::System& core;
@ -93,6 +107,7 @@ private:
bool system_registered{};
/// Audio render system, main driver of audio rendering
System system;
u32 revision;
};
} // namespace Renderer

View file

@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "common/common_types.h"
namespace AudioCore::Renderer {
struct CompressorStatistics {
float maximum_mean{};
float minimum_gain{1.0f};
std::array<float, 6> last_samples{}; // 6 channels max
void Reset(u16 channel_count) {
maximum_mean = 0.0f;
minimum_gain = 1.0f;
std::fill_n(last_samples.begin(), channel_count, 0.0f);
}
};
struct CompressorParameter {
u32 channel_count{};
float input_gain{};
float release_coefficient{};
float attack_coefficient{};
float ratio{};
float threshold{};
bool makeup_gain_enabled{};
bool statistics_enabled{};
bool statistics_reset{};
bool IsChannelCountValid() const {
return channel_count > 0 && channel_count <= 6;
}
};
} // namespace AudioCore::Renderer

View file

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cmath>
#include "audio_core/renderer/effect/compressor.h"
namespace AudioCore::Renderer {
@ -37,4 +39,48 @@ CpuAddr CompressorInfo::GetWorkbuffer(s32 index) {
return GetSingleBuffer(index);
}
CompressorEffect::CompressorEffect(std::size_t sample_count_) : sample_count{sample_count_} {}
void CompressorEffect::Process(std::span<f32> output_buffer, std::span<const f32> input_buffer) {
if (!IsEnabled() || !parameter.IsChannelCountValid()) {
std::copy(input_buffer.begin(), input_buffer.end(), output_buffer.begin());
return;
}
if (!result_state.empty() && parameter.statistics_reset) {
statistics.Reset(static_cast<u16>(parameter.channel_count));
}
// Process audio with compressor effect
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
float mean = 0.0f;
for (u32 channel = 0; channel < parameter.channel_count; channel++) {
const auto sample = input_buffer[sample_index * parameter.channel_count + channel];
mean += sample * sample;
}
mean /= static_cast<float>(parameter.channel_count);
// Calculate compression gain
float compression_gain = 1.0f;
if (mean > parameter.threshold) {
compression_gain = parameter.threshold / mean;
compression_gain = std::pow(compression_gain, 1.0f - (1.0f / parameter.ratio));
}
// Apply compression
for (u32 channel = 0; channel < parameter.channel_count; channel++) {
const auto in_sample = input_buffer[sample_index * parameter.channel_count + channel];
const auto out_sample = in_sample * compression_gain * parameter.input_gain;
output_buffer[sample_index * parameter.channel_count + channel] = out_sample;
// Update statistics if enabled
if (parameter.statistics_enabled) {
statistics.maximum_mean = std::max(statistics.maximum_mean, mean);
statistics.minimum_gain = std::min(statistics.minimum_gain, compression_gain);
statistics.last_samples[channel] = std::abs(in_sample) * (1.0f / 32768.0f);
}
}
}
}
} // namespace AudioCore::Renderer

View file

@ -1,9 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
#include "audio_core/common/common.h"
#include "audio_core/renderer/effect/effect_info_base.h"
@ -12,6 +14,53 @@
namespace AudioCore::Renderer {
struct CompressorStatistics {
float maximum_mean{};
float minimum_gain{1.0f};
std::array<float, 6> last_samples{}; // 6 channels max
void Reset(u16 channel_count) {
maximum_mean = 0.0f;
minimum_gain = 1.0f;
std::fill_n(last_samples.begin(), channel_count, 0.0f);
}
};
struct CompressorParameter {
u32 channel_count{};
float input_gain{};
float release_coefficient{};
float attack_coefficient{};
float ratio{};
float threshold{};
bool makeup_gain_enabled{};
bool statistics_enabled{};
bool statistics_reset{};
bool IsChannelCountValid() const {
return channel_count > 0 && channel_count <= 6;
}
};
class CompressorEffect : public EffectInfoBase {
public:
explicit CompressorEffect(std::size_t sample_count_);
~CompressorEffect() override = default;
void Process(std::span<f32> output_buffer, std::span<const f32> input_buffer);
bool IsEnabled() const {
return effect_enabled;
}
private:
CompressorParameter parameter;
CompressorStatistics statistics;
std::size_t sample_count;
bool effect_enabled{false};
std::span<u8> result_state;
};
class CompressorInfo : public EffectInfoBase {
public:
struct ParameterVersion1 {

View file

@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
namespace AudioCore::Renderer {
class SplitterDestination {
public:
void Update(const SplitterDestinationParameter& parameter, bool is_prev_volume_reset_supported) {
if (is_prev_volume_reset_supported ? parameter.reset_prev_volume
: (!is_used && parameter.is_used)) {
// Reset previous mix volumes
prev_mix_volumes = parameter.mix_volumes;
mix_volumes = parameter.mix_volumes;
}
is_used = parameter.is_used;
}
private:
bool is_used{};
std::array<float, MaxMixBuffers> mix_volumes{};
std::array<float, MaxMixBuffers> prev_mix_volumes{};
};
} // namespace AudioCore::Renderer