mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-12-06 07:12:28 +01:00
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
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:
parent
9524211b9a
commit
6bc690491f
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,11 +191,12 @@ 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;
|
||||
|
|
@ -377,7 +389,7 @@ protected:
|
|||
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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue