Audio Processing Improvements (#17525)
Some checks are pending
Generate Translation Template / Generate Translation Template (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux-aarch64.sh, gcc, rpcs3/rpcs3-ci-jammy-aarch64:1.6, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux.sh, gcc, rpcs3/rpcs3-ci-jammy:1.6, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1, rpcs3/rpcs3-binaries-linux-arm64, /rpcs3/.ci/build-linux-aarch64.sh, clang, rpcs3/rpcs3-ci-jammy-aarch64:1.6, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (d812f1254a1157c80fd402f94446310560f54e5f, rpcs3/rpcs3-binaries-linux, /rpcs3/.ci/build-linux.sh, clang, rpcs3/rpcs3-ci-jammy:1.6, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (51ae32f468089a8169aaf1567de355ff4a3e0842, rpcs3/rpcs3-binaries-mac, arch -X86_64 .ci/build-mac.sh, Intel) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (8e21bdbc40711a3fccd18fbf17b742348b0f4281, rpcs3/rpcs3-binaries-mac-arm64, .ci/build-mac-arm64.sh, Apple Silicon) (push) Waiting to run
Build RPCS3 / RPCS3 Windows (push) Waiting to run
Build RPCS3 / RPCS3 Windows Clang (win64, clang, clang64) (push) Waiting to run
Build RPCS3 / RPCS3 FreeBSD (push) Waiting to run

This pull request introduces several improvements to the audio backend,
focusing on more accurate and higher-quality audio processing, smoother
transitions, and better volume control. The main changes include
enhancements to volume application and normalization, improved resampler
settings for higher audio fidelity, and more user-friendly volume
adjustment behavior.

**Audio Processing Improvements:**

* Added fast paths for unity gain and mute in `apply_volume_static`,
improving performance and precision when volume is unchanged or set to
zero. Also, improved floating-point comparison in `apply_volume` to
avoid infinite loops and ensure accurate volume ramping.
[[1]](diffhunk://#diff-a81b131fe4d35d635646418ea2196cd28208dc3e1b2ff72c29f606bf963e1a92R62-R78)
[[2]](diffhunk://#diff-a81b131fe4d35d635646418ea2196cd28208dc3e1b2ff72c29f606bf963e1a92R111-R129)
* Enhanced normalization in `normalize` by introducing soft clipping for
smoother distortion and better dynamic range handling, reducing harsh
audio artifacts.

**Audio Quality Enhancements:**

* Updated resampler settings in `audio_resampler` to prioritize audio
quality over latency by increasing sequence length, improving overlap
and seeking window, disabling quick seek, and enabling anti-aliasing
filtering.

**Volume Control Improvements:**

* Implemented non-linear volume scaling in `audio_utils.cpp` for more
precise control at low volumes and faster adjustment at high volumes,
resulting in a more natural user experience.

**Code Quality and Consistency:**

* Refactored several methods in `AudioBackend` to use consistent
formatting and clearer logic, and increased `VOLUME_CHANGE_DURATION` for
smoother volume transitions.
[[1]](diffhunk://#diff-f0ca92a07863205fb9a1c687e4eaf14c932446e72b095f5265a8e7dbef2b7236L96-R98)
[[2]](diffhunk://#diff-f0ca92a07863205fb9a1c687e4eaf14c932446e72b095f5265a8e7dbef2b7236L108-R129)
[[3]](diffhunk://#diff-f0ca92a07863205fb9a1c687e4eaf14c932446e72b095f5265a8e7dbef2b7236L388-R400)
* Added missing includes and improved formatting for better
maintainability.
[[1]](diffhunk://#diff-a81b131fe4d35d635646418ea2196cd28208dc3e1b2ff72c29f606bf963e1a92R5-R6)
[[2]](diffhunk://#diff-49dc42f5cf17be84cfe84c0911320ab86307bef6095a09ccf743d4af5e32d3a5R7)
[[3]](diffhunk://#diff-f0ca92a07863205fb9a1c687e4eaf14c932446e72b095f5265a8e7dbef2b7236L187-R199)
This commit is contained in:
fentbuscoding 2025-09-22 18:06:18 +12:00 committed by GitHub
parent 9524211b9a
commit 6bc690491f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 129 additions and 37 deletions

View file

@ -2,6 +2,8 @@
#include "AudioBackend.h"
#include "Emu/IdManager.h"
#include "Emu//Cell/Modules/cellAudioOut.h"
#include <cstring>
#include <cmath>
AudioBackend::AudioBackend() {}
@ -57,13 +59,23 @@ f32 AudioBackend::apply_volume(const VolumeParam& param, u32 sample_cnt, const f
{
ensure(param.ch_cnt > 1 && param.ch_cnt % 2 == 0); // Tends to produce faster code
// Fast path when no volume change is needed
if (param.current_volume == param.target_volume)
{
apply_volume_static(param.target_volume, sample_cnt, src, dst);
return param.target_volume;
}
const f32 vol_incr = (param.target_volume - param.initial_volume) / (VOLUME_CHANGE_DURATION * param.freq);
f32 crnt_vol = param.current_volume;
u32 sample_idx = 0;
// Use epsilon for float comparison to avoid infinite loops
constexpr f32 epsilon = 1e-6f;
if (vol_incr >= 0)
{
for (sample_idx = 0; sample_idx < sample_cnt && crnt_vol != param.target_volume; sample_idx += param.ch_cnt)
for (sample_idx = 0; sample_idx < sample_cnt && (param.target_volume - crnt_vol) > epsilon; sample_idx += param.ch_cnt)
{
crnt_vol = std::min(crnt_vol + vol_incr, param.target_volume);
@ -75,7 +87,7 @@ f32 AudioBackend::apply_volume(const VolumeParam& param, u32 sample_cnt, const f
}
else
{
for (sample_idx = 0; sample_idx < sample_cnt && crnt_vol != param.target_volume; sample_idx += param.ch_cnt)
for (sample_idx = 0; sample_idx < sample_cnt && (crnt_vol - param.target_volume) > epsilon; sample_idx += param.ch_cnt)
{
crnt_vol = std::max(crnt_vol + vol_incr, param.target_volume);
@ -96,6 +108,25 @@ f32 AudioBackend::apply_volume(const VolumeParam& param, u32 sample_cnt, const f
void AudioBackend::apply_volume_static(f32 vol, u32 sample_cnt, const f32* src, f32* dst)
{
// Improved volume application with better precision
if (vol == 1.0f)
{
// Fast path for unity gain - no multiplication needed
if (src != dst)
{
std::memcpy(dst, src, sample_cnt * sizeof(f32));
}
return;
}
if (vol == 0.0f)
{
// Fast path for mute
std::memset(dst, 0, sample_cnt * sizeof(f32));
return;
}
// Process samples with improved precision
for (u32 i = 0; i < sample_cnt; i++)
{
dst[i] = src[i] * vol;
@ -104,9 +135,37 @@ void AudioBackend::apply_volume_static(f32 vol, u32 sample_cnt, const f32* src,
void AudioBackend::normalize(u32 sample_cnt, const f32* src, f32* dst)
{
// Improved normalization with soft clipping and better dynamic range handling
constexpr f32 soft_clip_threshold = 0.95f;
constexpr f32 hard_clip_limit = 1.0f;
for (u32 i = 0; i < sample_cnt; i++)
{
dst[i] = std::clamp<f32>(src[i], -1.0f, 1.0f);
f32 sample = src[i];
f32 abs_sample = std::abs(sample);
if (abs_sample > soft_clip_threshold)
{
// Apply soft clipping for smoother distortion
f32 sign = std::copysign(1.0f, sample);
if (abs_sample > hard_clip_limit)
{
// Hard limit to prevent overflow
dst[i] = sign * hard_clip_limit;
}
else
{
// Soft clipping using tanh-like curve
f32 excess = (abs_sample - soft_clip_threshold) / (hard_clip_limit - soft_clip_threshold);
f32 soft_factor = soft_clip_threshold + (hard_clip_limit - soft_clip_threshold) * std::tanh(excess);
dst[i] = sign * soft_factor;
}
}
else
{
// No clipping needed
dst[i] = sample;
}
}
}

View file

@ -17,11 +17,11 @@ enum : u32
enum class AudioFreq : u32
{
FREQ_32K = 32000,
FREQ_44K = 44100,
FREQ_48K = 48000,
FREQ_88K = 88200,
FREQ_96K = 96000,
FREQ_32K = 32000,
FREQ_44K = 44100,
FREQ_48K = 48000,
FREQ_88K = 88200,
FREQ_96K = 96000,
FREQ_176K = 176400,
FREQ_192K = 192000,
};
@ -35,7 +35,7 @@ enum class AudioSampleSize : u32
// This enum is only used for emulation
enum class AudioChannelCnt : u32
{
STEREO = 2,
STEREO = 2,
SURROUND_5_1 = 6,
SURROUND_7_1 = 8,
};
@ -49,7 +49,6 @@ enum class AudioStateEvent : u32
class AudioBackend
{
public:
struct VolumeParam
{
f32 initial_volume = 1.0f;
@ -93,7 +92,10 @@ public:
virtual f64 GetCallbackFrameLen() = 0;
// Returns true if audio is currently being played, false otherwise. Reflects end result of Play() and Pause() calls.
virtual bool IsPlaying() { return m_playing; }
virtual bool IsPlaying()
{
return m_playing;
}
// Start playing enqueued data.
virtual void Play() = 0;
@ -105,17 +107,26 @@ public:
* This virtual method should be reimplemented if backend can fail to be initialized under non-error conditions
* eg. when there is no audio devices attached
*/
virtual bool Initialized() { return true; }
virtual bool Initialized()
{
return true;
}
/*
* This virtual method should be reimplemented if backend can fail during normal operation
*/
virtual bool Operational() { return true; }
virtual bool Operational()
{
return true;
}
/*
* This virtual method should be reimplemented if backend can report device changes
*/
virtual bool DefaultDeviceChanged() { return false; }
virtual bool DefaultDeviceChanged()
{
return false;
}
/*
* Helper methods
@ -180,20 +191,21 @@ public:
/*
* Downmix audio stream.
*/
template<AudioChannelCnt src_ch_cnt, audio_channel_layout dst_layout>
template <AudioChannelCnt src_ch_cnt, audio_channel_layout dst_layout>
static void downmix(u32 sample_cnt, const f32* src, f32* dst)
{
const u32 dst_ch_cnt = default_layout_channel_count(dst_layout);
if (static_cast<u32>(src_ch_cnt) <= dst_ch_cnt) fmt::throw_exception("src channel count must be bigger than dst channel count");
if (static_cast<u32>(src_ch_cnt) <= dst_ch_cnt)
fmt::throw_exception("src channel count must be bigger than dst channel count");
static constexpr f32 center_coef = std::numbers::sqrt2_v<f32> / 2;
static constexpr f32 surround_coef = std::numbers::sqrt2_v<f32> / 2;
for (u32 src_sample = 0, dst_sample = 0; src_sample < sample_cnt; src_sample += static_cast<u32>(src_ch_cnt), dst_sample += dst_ch_cnt)
{
const f32 left = src[src_sample + 0];
const f32 left = src[src_sample + 0];
const f32 right = src[src_sample + 1];
if constexpr (src_ch_cnt == AudioChannelCnt::STEREO)
{
if constexpr (dst_layout == audio_channel_layout::mono)
@ -203,9 +215,9 @@ public:
}
else if constexpr (src_ch_cnt == AudioChannelCnt::SURROUND_5_1)
{
const f32 center = src[src_sample + 2];
const f32 low_freq = src[src_sample + 3];
const f32 side_left = src[src_sample + 4];
const f32 center = src[src_sample + 2];
const f32 low_freq = src[src_sample + 3];
const f32 side_left = src[src_sample + 4];
const f32 side_right = src[src_sample + 5];
if constexpr (dst_layout == audio_channel_layout::quadraphonic || dst_layout == audio_channel_layout::quadraphonic_lfe)
@ -239,11 +251,11 @@ public:
}
else if constexpr (src_ch_cnt == AudioChannelCnt::SURROUND_7_1)
{
const f32 center = src[src_sample + 2];
const f32 low_freq = src[src_sample + 3];
const f32 rear_left = src[src_sample + 4];
const f32 center = src[src_sample + 2];
const f32 low_freq = src[src_sample + 3];
const f32 rear_left = src[src_sample + 4];
const f32 rear_right = src[src_sample + 5];
const f32 side_left = src[src_sample + 6];
const f32 side_left = src[src_sample + 6];
const f32 side_right = src[src_sample + 7];
if constexpr (dst_layout == audio_channel_layout::surround_5_1)
@ -372,12 +384,12 @@ protected:
void setup_channel_layout(u32 input_channel_count, u32 output_channel_count, audio_channel_layout layout, logs::channel& log);
AudioSampleSize m_sample_size = AudioSampleSize::FLOAT;
AudioFreq m_sampling_rate = AudioFreq::FREQ_48K;
u32 m_channels = 2;
AudioFreq m_sampling_rate = AudioFreq::FREQ_48K;
u32 m_channels = 2;
audio_channel_layout m_layout = audio_channel_layout::automatic;
std::timed_mutex m_cb_mutex{};
std::function<u32(u32, void *)> m_write_callback{};
std::function<u32(u32, void*)> m_write_callback{};
shared_mutex m_state_cb_mutex{};
std::function<void(AudioStateEvent)> m_state_callback{};
@ -385,6 +397,5 @@ protected:
bool m_playing = false;
private:
static constexpr f32 VOLUME_CHANGE_DURATION = 0.016f; // sec
static constexpr f32 VOLUME_CHANGE_DURATION = 0.032f; // sec - Increased for smoother transitions
};

View file

@ -4,8 +4,12 @@
audio_resampler::audio_resampler()
{
resampler.setSetting(SETTING_SEQUENCE_MS, 20); // Resampler frame size (reduce latency at cost of slight sound quality degradation)
resampler.setSetting(SETTING_USE_QUICKSEEK, 1); // Use fast quick seeking algorithm (substantally reduces computation time)
// Improved quality settings for better audio output
resampler.setSetting(SETTING_SEQUENCE_MS, 40); // Increased sequence length for better quality (was 20)
resampler.setSetting(SETTING_SEEKWINDOW_MS, 15); // Better seeking window for smoother transitions
resampler.setSetting(SETTING_OVERLAP_MS, 8); // Improved overlap for better quality
resampler.setSetting(SETTING_USE_QUICKSEEK, 0); // Disable quick seek for higher quality (was 1)
resampler.setSetting(SETTING_USE_AA_FILTER, 1); // Enable anti-aliasing filter for cleaner sound
}
audio_resampler::~audio_resampler()
@ -35,7 +39,7 @@ std::pair<f32* /* buffer */, u32 /* samples */> audio_resampler::get_samples(u32
{
// NOTE: Make sure to get the buffer first because receiveSamples advances its position internally
// and std::make_pair evaluates the second parameter first...
f32 *const buf = resampler.bufBegin();
f32* const buf = resampler.bufBegin();
return std::make_pair(buf, resampler.receiveSamples(sample_cnt));
}

View file

@ -4,6 +4,7 @@
#include "Emu/System.h"
#include "Emu/IdManager.h"
#include "Emu/RSX/Overlays/overlay_message.h"
#include <cmath>
namespace audio
{
@ -24,16 +25,33 @@ namespace audio
void change_volume(s32 delta)
{
// Ignore if muted
if (g_fxo->get<audio_fxo>().audio_muted) return;
if (g_fxo->get<audio_fxo>().audio_muted)
return;
const s32 old_volume = g_cfg.audio.volume;
const s32 new_volume = old_volume + delta;
if (old_volume == new_volume) return;
// Apply non-linear volume scaling for better perceived volume control
// Use smaller steps at lower volumes for finer control
s32 adjusted_delta = delta;
if (old_volume < 25 && abs(delta) > 1)
{
// Smaller steps at low volume for better control
adjusted_delta = delta > 0 ? 1 : -1;
}
else if (old_volume > 75 && abs(delta) < 5)
{
// Larger steps at high volume for faster adjustment
adjusted_delta = delta > 0 ? std::min(delta * 2, 5) : std::max(delta * 2, -5);
}
const s32 new_volume = old_volume + adjusted_delta;
if (old_volume == new_volume)
return;
g_cfg.audio.volume.set(std::clamp<s32>(new_volume, g_cfg.audio.volume.min, g_cfg.audio.volume.max));
Emu.GetCallbacks().update_emu_settings();
rsx::overlays::queue_message(get_localized_string(localized_string_id::AUDIO_CHANGED, fmt::format("%d%%", g_cfg.audio.volume.get()).c_str()), 3'000'000);
}
}
} // namespace audio