diff --git a/rpcs3/Emu/Audio/AudioBackend.cpp b/rpcs3/Emu/Audio/AudioBackend.cpp index def9ebfbab..80af260414 100644 --- a/rpcs3/Emu/Audio/AudioBackend.cpp +++ b/rpcs3/Emu/Audio/AudioBackend.cpp @@ -2,6 +2,8 @@ #include "AudioBackend.h" #include "Emu/IdManager.h" #include "Emu//Cell/Modules/cellAudioOut.h" +#include +#include 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(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; + } } } diff --git a/rpcs3/Emu/Audio/AudioBackend.h b/rpcs3/Emu/Audio/AudioBackend.h index cbd8e045c9..bc07a1e46f 100644 --- a/rpcs3/Emu/Audio/AudioBackend.h +++ b/rpcs3/Emu/Audio/AudioBackend.h @@ -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 + template 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(src_ch_cnt) <= dst_ch_cnt) fmt::throw_exception("src channel count must be bigger than dst channel count"); + if (static_cast(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 / 2; static constexpr f32 surround_coef = std::numbers::sqrt2_v / 2; for (u32 src_sample = 0, dst_sample = 0; src_sample < sample_cnt; src_sample += static_cast(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 m_write_callback{}; + std::function m_write_callback{}; shared_mutex m_state_cb_mutex{}; std::function 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 }; diff --git a/rpcs3/Emu/Audio/audio_resampler.cpp b/rpcs3/Emu/Audio/audio_resampler.cpp index 32c7109b4c..108c63e66d 100644 --- a/rpcs3/Emu/Audio/audio_resampler.cpp +++ b/rpcs3/Emu/Audio/audio_resampler.cpp @@ -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 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)); } diff --git a/rpcs3/Emu/Audio/audio_utils.cpp b/rpcs3/Emu/Audio/audio_utils.cpp index bcff7bf947..2db2e1a0ed 100644 --- a/rpcs3/Emu/Audio/audio_utils.cpp +++ b/rpcs3/Emu/Audio/audio_utils.cpp @@ -4,6 +4,7 @@ #include "Emu/System.h" #include "Emu/IdManager.h" #include "Emu/RSX/Overlays/overlay_message.h" +#include namespace audio { @@ -24,16 +25,33 @@ namespace audio void change_volume(s32 delta) { // Ignore if muted - if (g_fxo->get().audio_muted) return; + if (g_fxo->get().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(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