diff --git a/externals/vcpkg b/externals/vcpkg index 2ded45cc7..561cf50a7 160000 --- a/externals/vcpkg +++ b/externals/vcpkg @@ -1 +1 @@ -Subproject commit 2ded45cc7a35667ad8d96e5e50b4a24afacafb3a +Subproject commit 561cf50a731761f9890fb720f830cb3501b8472d diff --git a/src/audio_core/renderer/audio_renderer.h b/src/audio_core/renderer/audio_renderer.h index f16adeda7..5b0875c81 100644 --- a/src/audio_core/renderer/audio_renderer.h +++ b/src/audio_core/renderer/audio_renderer.h @@ -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 input, std::span performance, std::span output); + bool IsCompressorStatisticsSupported() const { + return revision >= static_cast(AudioRendererRevision::Rev13); + } + + bool IsSplitterPrevVolumeResetSupported() const { + return revision >= static_cast(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 diff --git a/src/audio_core/renderer/compressor.h b/src/audio_core/renderer/compressor.h new file mode 100644 index 000000000..97f43dfae --- /dev/null +++ b/src/audio_core/renderer/compressor.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/common_types.h" + +namespace AudioCore::Renderer { + +struct CompressorStatistics { + float maximum_mean{}; + float minimum_gain{1.0f}; + std::array 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 diff --git a/src/audio_core/renderer/effect/compressor.cpp b/src/audio_core/renderer/effect/compressor.cpp index fea0aefcf..3b32670f7 100644 --- a/src/audio_core/renderer/effect/compressor.cpp +++ b/src/audio_core/renderer/effect/compressor.cpp @@ -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 #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 output_buffer, std::span 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(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(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 diff --git a/src/audio_core/renderer/effect/compressor.h b/src/audio_core/renderer/effect/compressor.h index cda55c284..7074c8484 100644 --- a/src/audio_core/renderer/effect/compressor.h +++ b/src/audio_core/renderer/effect/compressor.h @@ -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 +#include #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 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 output_buffer, std::span input_buffer); + + bool IsEnabled() const { + return effect_enabled; + } + +private: + CompressorParameter parameter; + CompressorStatistics statistics; + std::size_t sample_count; + bool effect_enabled{false}; + std::span result_state; +}; + class CompressorInfo : public EffectInfoBase { public: struct ParameterVersion1 { diff --git a/src/audio_core/renderer/splitter.h b/src/audio_core/renderer/splitter.h new file mode 100644 index 000000000..373656d44 --- /dev/null +++ b/src/audio_core/renderer/splitter.h @@ -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 mix_volumes{}; + std::array prev_mix_volumes{}; +}; + +} // namespace AudioCore::Renderer