From 6a4945090ca0a36aac6b2bf7c567d61839c1aed0 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Mon, 10 Apr 2017 14:57:24 +0100 Subject: [PATCH 01/10] OpenALStream: Remove audio stretching --- Source/Core/AudioCommon/OpenALStream.cpp | 84 +++++------------------- Source/Core/AudioCommon/OpenALStream.h | 14 +--- 2 files changed, 19 insertions(+), 79 deletions(-) diff --git a/Source/Core/AudioCommon/OpenALStream.cpp b/Source/Core/AudioCommon/OpenALStream.cpp index aee276e691..3d4bc90e1d 100644 --- a/Source/Core/AudioCommon/OpenALStream.cpp +++ b/Source/Core/AudioCommon/OpenALStream.cpp @@ -20,8 +20,6 @@ #pragma comment(lib, "openal32.lib") #endif -static soundtouch::SoundTouch soundTouch; - // // AyuanX: Spec says OpenAL1.1 is thread safe already // @@ -71,7 +69,6 @@ bool OpenALStream::Start() // Initialize DPL2 parameters DPL2Reset(); - soundTouch.clear(); return bReturn; } @@ -81,8 +78,6 @@ void OpenALStream::Stop() // kick the thread if it's waiting soundSyncEvent.Set(); - soundTouch.clear(); - thread.join(); alSourceStop(uiSource); @@ -120,7 +115,6 @@ void OpenALStream::Clear(bool mute) if (m_muted) { - soundTouch.clear(); alSourceStop(uiSource); } else @@ -213,15 +207,6 @@ void OpenALStream::SoundLoop() unsigned int numBuffersQueued = 0; ALint iState = 0; - soundTouch.setChannels(2); - soundTouch.setSampleRate(ulFrequency); - soundTouch.setTempo(1.0); - soundTouch.setSetting(SETTING_USE_QUICKSEEK, 0); - soundTouch.setSetting(SETTING_USE_AA_FILTER, 0); - soundTouch.setSetting(SETTING_SEQUENCE_MS, 1); - soundTouch.setSetting(SETTING_SEEKWINDOW_MS, 28); - soundTouch.setSetting(SETTING_OVERLAP_MS, 12); - while (m_run_thread.IsSet()) { // Block until we have a free buffer @@ -243,62 +228,29 @@ void OpenALStream::SoundLoop() numBuffersQueued -= numBuffersProcessed; } - // num_samples_to_render in this update - depends on SystemTimers::AUDIO_DMA_PERIOD. - const u32 stereo_16_bit_size = 4; - const u32 dma_length = 32; - const u64 ais_samples_per_second = 48000 * stereo_16_bit_size; - u64 audio_dma_period = SystemTimers::GetTicksPerSecond() / - (AudioInterface::GetAIDSampleRate() * stereo_16_bit_size / dma_length); - u64 num_samples_to_render = - (audio_dma_period * ais_samples_per_second) / SystemTimers::GetTicksPerSecond(); + // DPL2 accepts 240 samples minimum (FWRDURATION) + unsigned int minSamples = surround_capable ? 240 : 0; - unsigned int numSamples = (unsigned int)num_samples_to_render; - unsigned int minSamples = - surround_capable ? 240 : 0; // DPL2 accepts 240 samples minimum (FWRDURATION) - - numSamples = (numSamples > OAL_MAX_SAMPLES) ? OAL_MAX_SAMPLES : numSamples; - numSamples = m_mixer->Mix(realtimeBuffer, numSamples, false); + unsigned int numSamples = OAL_MAX_SAMPLES; + numSamples = m_mixer->Mix(realtimeBuffer, numSamples); // Convert the samples from short to float - float dest[OAL_MAX_SAMPLES * STEREO_CHANNELS]; for (u32 i = 0; i < numSamples * STEREO_CHANNELS; ++i) - dest[i] = (float)realtimeBuffer[i] / (1 << 15); + sampleBuffer[i] = static_cast(realtimeBuffer[i]) / (1 << 15); - soundTouch.putSamples(dest, numSamples); - - double rate = (double)m_mixer->GetCurrentSpeed(); - if (rate <= 0) - { - Core::RequestRefreshInfo(); - rate = (double)m_mixer->GetCurrentSpeed(); - } - - // Place a lower limit of 10% speed. When a game boots up, there will be - // many silence samples. These do not need to be timestretched. - if (rate > 0.10) - { - soundTouch.setTempo(rate); - if (rate > 10) - { - soundTouch.clear(); - } - } - - unsigned int nSamples = soundTouch.receiveSamples(sampleBuffer, OAL_MAX_SAMPLES * numBuffers); - - if (nSamples <= minSamples) + if (numSamples <= minSamples) continue; if (surround_capable) { float dpl2[OAL_MAX_SAMPLES * OAL_MAX_BUFFERS * SURROUND_CHANNELS]; - DPL2Decode(sampleBuffer, nSamples, dpl2); + DPL2Decode(sampleBuffer, numSamples, dpl2); // zero-out the subwoofer channel - DPL2Decode generates a pretty // good 5.0 but not a good 5.1 output. Sadly there is not a 5.0 // AL_FORMAT_50CHN32 to make this super-explicit. // DPL2Decode output: LEFTFRONT, RIGHTFRONT, CENTREFRONT, (sub), LEFTREAR, RIGHTREAR - for (u32 i = 0; i < nSamples; ++i) + for (u32 i = 0; i < numSamples; ++i) { dpl2[i * SURROUND_CHANNELS + 3 /*sub/lfe*/] = 0.0f; } @@ -306,13 +258,13 @@ void OpenALStream::SoundLoop() if (float32_capable) { alBufferData(uiBuffers[nextBuffer], AL_FORMAT_51CHN32, dpl2, - nSamples * FRAME_SURROUND_FLOAT, ulFrequency); + numSamples * FRAME_SURROUND_FLOAT, ulFrequency); } else if (fixed32_capable) { int surround_int32[OAL_MAX_SAMPLES * SURROUND_CHANNELS * OAL_MAX_BUFFERS]; - for (u32 i = 0; i < nSamples * SURROUND_CHANNELS; ++i) + for (u32 i = 0; i < numSamples * SURROUND_CHANNELS; ++i) { // For some reason the ffdshow's DPL2 decoder outputs samples bigger than 1. // Most are close to 2.5 and some go up to 8. Hard clamping here, we need to @@ -327,13 +279,13 @@ void OpenALStream::SoundLoop() } alBufferData(uiBuffers[nextBuffer], AL_FORMAT_51CHN32, surround_int32, - nSamples * FRAME_SURROUND_INT32, ulFrequency); + numSamples * FRAME_SURROUND_INT32, ulFrequency); } else { short surround_short[OAL_MAX_SAMPLES * SURROUND_CHANNELS * OAL_MAX_BUFFERS]; - for (u32 i = 0; i < nSamples * SURROUND_CHANNELS; ++i) + for (u32 i = 0; i < numSamples * SURROUND_CHANNELS; ++i) { dpl2[i] = dpl2[i] * (1 << 15); if (dpl2[i] > SHRT_MAX) @@ -345,7 +297,7 @@ void OpenALStream::SoundLoop() } alBufferData(uiBuffers[nextBuffer], AL_FORMAT_51CHN16, surround_short, - nSamples * FRAME_SURROUND_SHORT, ulFrequency); + numSamples * FRAME_SURROUND_SHORT, ulFrequency); } err = CheckALError("buffering data"); @@ -362,7 +314,7 @@ void OpenALStream::SoundLoop() if (float32_capable) { alBufferData(uiBuffers[nextBuffer], AL_FORMAT_STEREO_FLOAT32, sampleBuffer, - nSamples * FRAME_STEREO_FLOAT, ulFrequency); + numSamples * FRAME_STEREO_FLOAT, ulFrequency); err = CheckALError("buffering float32 data"); if (err == AL_INVALID_ENUM) @@ -374,21 +326,21 @@ void OpenALStream::SoundLoop() { // Clamping is not necessary here, samples are always between (-1,1) int stereo_int32[OAL_MAX_SAMPLES * STEREO_CHANNELS * OAL_MAX_BUFFERS]; - for (u32 i = 0; i < nSamples * STEREO_CHANNELS; ++i) + for (u32 i = 0; i < numSamples * STEREO_CHANNELS; ++i) stereo_int32[i] = (int)((float)sampleBuffer[i] * (INT64_C(1) << 31)); alBufferData(uiBuffers[nextBuffer], AL_FORMAT_STEREO32, stereo_int32, - nSamples * FRAME_STEREO_INT32, ulFrequency); + numSamples * FRAME_STEREO_INT32, ulFrequency); } else { // Convert the samples from float to short short stereo[OAL_MAX_SAMPLES * STEREO_CHANNELS * OAL_MAX_BUFFERS]; - for (u32 i = 0; i < nSamples * STEREO_CHANNELS; ++i) + for (u32 i = 0; i < numSamples * STEREO_CHANNELS; ++i) stereo[i] = (short)((float)sampleBuffer[i] * (1 << 15)); alBufferData(uiBuffers[nextBuffer], AL_FORMAT_STEREO16, stereo, - nSamples * FRAME_STEREO_SHORT, ulFrequency); + numSamples * FRAME_STEREO_SHORT, ulFrequency); } } diff --git a/Source/Core/AudioCommon/OpenALStream.h b/Source/Core/AudioCommon/OpenALStream.h index ddbade3bb5..6d32a37adb 100644 --- a/Source/Core/AudioCommon/OpenALStream.h +++ b/Source/Core/AudioCommon/OpenALStream.h @@ -26,18 +26,6 @@ #include #endif -#ifdef __APPLE__ -// Avoid conflict with objc.h (on Windows, ST uses the system BOOL type, so this doesn't work) -#define BOOL SoundTouch_BOOL -#endif - -#include -#include - -#ifdef __APPLE__ -#undef BOOL -#endif - #define SFX_MAX_SOURCE 1 #define OAL_MAX_BUFFERS 32 #define OAL_MAX_SAMPLES 256 @@ -89,7 +77,7 @@ private: Common::Event soundSyncEvent; short realtimeBuffer[OAL_MAX_SAMPLES * STEREO_CHANNELS]; - soundtouch::SAMPLETYPE sampleBuffer[OAL_MAX_SAMPLES * SURROUND_CHANNELS * OAL_MAX_BUFFERS]; + float sampleBuffer[OAL_MAX_SAMPLES * SURROUND_CHANNELS * OAL_MAX_BUFFERS]; ALuint uiBuffers[OAL_MAX_BUFFERS]; ALuint uiSource; ALfloat fVolume; From 8ff26a6edab1a57d7d1e10564f7ceaafcae3f7d0 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Mon, 10 Apr 2017 14:59:57 +0100 Subject: [PATCH 02/10] soundtouch: Use shorts instead of floats for samples --- Externals/soundtouch/STTypes.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Externals/soundtouch/STTypes.h b/Externals/soundtouch/STTypes.h index f920afe316..a07bc3dcea 100644 --- a/Externals/soundtouch/STTypes.h +++ b/Externals/soundtouch/STTypes.h @@ -98,8 +98,8 @@ namespace soundtouch /// However, if you still prefer to select the sample format here /// also in GNU environment, then please #undef the INTEGER_SAMPLE /// and FLOAT_SAMPLE defines first as in comments above. - //#define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples - #define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples + #define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples + //#define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples #endif @@ -110,7 +110,7 @@ namespace soundtouch /// routines compiled for whatever reason, you may disable these optimizations /// to make the library compile. - #define SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS 1 + //#define SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS 1 /// In GNU environment, allow the user to override this setting by /// giving the following switch to the configure script: From 5b81f2a31d044da2cac5ca04a7d9c5dce186ad07 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Mon, 10 Apr 2017 16:58:55 +0100 Subject: [PATCH 03/10] Mixer: Return actual number of samples mixed into buffer from MixerFifo::Mix No code in the codebase currently depends on the return value of this function. --- Source/Core/AudioCommon/Mixer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index bdaaad812c..bdfbbfe5a0 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -88,6 +88,9 @@ unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples, m_frac &= 0xffff; } + // Actual number of samples written to the buffer without padding. + unsigned int actual_sample_count = currentSample / 2; + // Padding short s[2]; s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]); @@ -106,7 +109,7 @@ unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples, // Flush cached variable m_indexR.store(indexR); - return numSamples; + return actual_sample_count; } unsigned int CMixer::Mix(short* samples, unsigned int num_samples, bool consider_framelimit) From b8c867dd7a14ed9e96dfd8f8bc01170dacea9018 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Mon, 10 Apr 2017 17:44:17 +0100 Subject: [PATCH 04/10] Mixer: Implement audio stretching --- CMakeLists.txt | 17 ++---- Source/Core/AudioCommon/CMakeLists.txt | 4 +- Source/Core/AudioCommon/Mixer.cpp | 77 +++++++++++++++++++++++++- Source/Core/AudioCommon/Mixer.h | 11 ++++ 4 files changed, 92 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aba2ac8768..cc3fe60dbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -648,18 +648,11 @@ else() endif() find_package(OpenAL) -if(OPENAL_FOUND) - if(NOT APPLE) - check_lib(SOUNDTOUCH soundtouch SoundTouch soundtouch/SoundTouch.h QUIET) - endif() - if (SOUNDTOUCH_FOUND) - message(STATUS "Using shared soundtouch") - else() - message(STATUS "Using static soundtouch from Externals") - add_subdirectory(Externals/soundtouch) - include_directories(Externals) - endif() -endif() + +# Using static soundtouch from Externals +# Unable to use system soundtouch library: We require shorts, not floats. +add_subdirectory(Externals/soundtouch) +include_directories(Externals) if(NOT ANDROID) add_definitions(-D__LIBUSB__) diff --git a/Source/Core/AudioCommon/CMakeLists.txt b/Source/Core/AudioCommon/CMakeLists.txt index f09b7ef823..8f0a283db8 100644 --- a/Source/Core/AudioCommon/CMakeLists.txt +++ b/Source/Core/AudioCommon/CMakeLists.txt @@ -37,7 +37,7 @@ if(ENABLE_OPENAL) if(OPENAL_FOUND) message(STATUS "OpenAL found, enabling OpenAL sound backend") target_sources(audiocommon PRIVATE OpenALStream.cpp aldlist.cpp) - target_link_libraries(audiocommon PRIVATE OpenAL::OpenAL SoundTouch) + target_link_libraries(audiocommon PRIVATE OpenAL::OpenAL) target_compile_definitions(audiocommon PRIVATE HAVE_OPENAL=1) else() message(STATUS "OpenAL NOT found, disabling OpenAL sound backend") @@ -76,4 +76,4 @@ elseif(APPLE) target_sources(audiocommon PRIVATE CoreAudioSoundStream.cpp) endif() - +target_link_libraries(audiocommon PRIVATE SoundTouch) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index bdfbbfe5a0..bebdd2abbe 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -4,6 +4,7 @@ #include "AudioCommon/Mixer.h" +#include #include #include "Common/CommonTypes.h" @@ -15,6 +16,15 @@ CMixer::CMixer(unsigned int BackendSampleRate) : m_sampleRate(BackendSampleRate) { INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized"); + + m_sound_touch.setChannels(2); + m_sound_touch.setSampleRate(BackendSampleRate); + m_sound_touch.setPitch(1.0); + m_sound_touch.setTempo(1.0); + m_sound_touch.setSetting(SETTING_USE_QUICKSEEK, 0); + m_sound_touch.setSetting(SETTING_SEQUENCE_MS, 62); + m_sound_touch.setSetting(SETTING_SEEKWINDOW_MS, 28); + m_sound_touch.setSetting(SETTING_OVERLAP_MS, 8); } CMixer::~CMixer() @@ -119,12 +129,73 @@ unsigned int CMixer::Mix(short* samples, unsigned int num_samples, bool consider memset(samples, 0, num_samples * 2 * sizeof(short)); - m_dma_mixer.Mix(samples, num_samples, consider_framelimit); - m_streaming_mixer.Mix(samples, num_samples, consider_framelimit); - m_wiimote_speaker_mixer.Mix(samples, num_samples, consider_framelimit); + unsigned int actual_samples = m_dma_mixer.Mix(samples, num_samples, false); + m_streaming_mixer.Mix(samples, num_samples, false); + m_wiimote_speaker_mixer.Mix(samples, num_samples, false); + + StretchAudio(samples, actual_samples, num_samples); + return num_samples; } +void CMixer::StretchAudio(short* samples, unsigned int actual_samples, unsigned int num_samples) +{ + const double time_delta = static_cast(num_samples) / m_sampleRate; // seconds + + // We were given actual_samples number of samples, and num_samples were requested from us. + double current_ratio = static_cast(actual_samples) / static_cast(num_samples); + + constexpr double MAXIMUM_LATENCY = 0.080; // seconds + const double max_backlog = m_sampleRate * MAXIMUM_LATENCY; + double backlog_fullness = m_sound_touch.numSamples() / max_backlog; + if (backlog_fullness > 1.0) + { + // Exceeded latency budget: Do not add more samples into FIFO. + actual_samples = 0; + } + + // We ideally want the backlog to be about 50% full. + // This gives some headroom both ways to prevent underflow and overflow. + // We tweak current_ratio to encourage this. + constexpr double tweak_time_scale = 0.1; // seconds + current_ratio *= 1.0 + 2.0 * (backlog_fullness - 0.5) * (time_delta / tweak_time_scale); + + // This low-pass filter smoothes out variance in the calculated stretch ratio. + // The time-scale determines how responsive this filter is. + constexpr double lpf_time_scale = 0.3; // seconds + const double m_lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale); + m_stretch_ratio += m_lpf_gain * (current_ratio - m_stretch_ratio); + + // Place a lower limit of 10% speed. When a game boots up, there will be + // many silence samples. These do not need to be timestretched. + m_sound_touch.setTempo(std::max(m_stretch_ratio, 0.1)); + + if (actual_samples != num_samples) + { + DEBUG_LOG(AUDIO, "Audio stretching: samples:%u/%u ratio:%f backlog:%f gain: %f", actual_samples, + num_samples, m_stretch_ratio, backlog_fullness, m_lpf_gain); + } + + m_sound_touch.putSamples(samples, actual_samples); + + memset(samples, 0, num_samples * 2 * sizeof(short)); + + const size_t samples_received = m_sound_touch.receiveSamples(samples, num_samples); + + if (samples_received != 0) + { + m_last_stretched_sample[0] = samples[samples_received * 2 - 2]; + m_last_stretched_sample[1] = samples[samples_received * 2 - 1]; + } + + // Preform padding if we've run out of samples. + for (size_t i = samples_received; i < num_samples; i++) + { + samples[i * 2 + 0] = m_last_stretched_sample[0]; + samples[i * 2 + 1] = m_last_stretched_sample[1]; + } +} + void CMixer::MixerFifo::PushSamples(const short* samples, unsigned int num_samples) { // Cache access in non-volatile variable diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index dd879462ab..762a872d50 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -10,6 +10,9 @@ #include "AudioCommon/WaveFile.h" #include "Common/CommonTypes.h" +#include +#include + class CMixer final { public: @@ -70,11 +73,19 @@ private: float m_numLeftI = 0.0f; u32 m_frac = 0; }; + + void StretchAudio(short* samples, unsigned int actual_samples, unsigned int num_samples); + MixerFifo m_dma_mixer{this, 32000}; MixerFifo m_streaming_mixer{this, 48000}; MixerFifo m_wiimote_speaker_mixer{this, 3000}; unsigned int m_sampleRate; + bool m_is_stretching = false; + soundtouch::SoundTouch m_sound_touch; + double m_stretch_ratio = 1.0; + std::array m_last_stretched_sample = {}; + WaveFileWriter m_wave_writer_dtk; WaveFileWriter m_wave_writer_dsp; From 26514358f40fd110d5d55d033fad16064a4db27b Mon Sep 17 00:00:00 2001 From: MerryMage Date: Mon, 10 Apr 2017 17:56:24 +0100 Subject: [PATCH 05/10] Add audio stretching as a configuration option --- Source/Core/AudioCommon/Mixer.cpp | 26 +++++++++++++++++--------- Source/Core/AudioCommon/Mixer.h | 2 +- Source/Core/Core/ConfigManager.cpp | 6 ++++++ Source/Core/Core/ConfigManager.h | 2 ++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index bebdd2abbe..5030e5d492 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -122,18 +122,26 @@ unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples, return actual_sample_count; } -unsigned int CMixer::Mix(short* samples, unsigned int num_samples, bool consider_framelimit) +unsigned int CMixer::Mix(short* samples, unsigned int num_samples) { if (!samples) return 0; memset(samples, 0, num_samples * 2 * sizeof(short)); - unsigned int actual_samples = m_dma_mixer.Mix(samples, num_samples, false); - m_streaming_mixer.Mix(samples, num_samples, false); - m_wiimote_speaker_mixer.Mix(samples, num_samples, false); + const bool stretch = SConfig::GetInstance().m_audio_stretch; - StretchAudio(samples, actual_samples, num_samples); + unsigned int actual_samples = m_dma_mixer.Mix(samples, num_samples, !stretch); + m_streaming_mixer.Mix(samples, num_samples, !stretch); + m_wiimote_speaker_mixer.Mix(samples, num_samples, !stretch); + + if (stretch) + { + if (m_is_stretching != stretch) + m_sound_touch.clear(); + StretchAudio(samples, actual_samples, num_samples); + } + m_is_stretching = stretch; return num_samples; } @@ -144,10 +152,10 @@ void CMixer::StretchAudio(short* samples, unsigned int actual_samples, unsigned // We were given actual_samples number of samples, and num_samples were requested from us. double current_ratio = static_cast(actual_samples) / static_cast(num_samples); - - constexpr double MAXIMUM_LATENCY = 0.080; // seconds - const double max_backlog = m_sampleRate * MAXIMUM_LATENCY; - double backlog_fullness = m_sound_touch.numSamples() / max_backlog; + + const double max_latency = SConfig::GetInstance().m_audio_stretch_max_latency; + const double max_backlog = m_sampleRate * max_latency / 1000.0; + const double backlog_fullness = m_sound_touch.numSamples() / max_backlog; if (backlog_fullness > 1.0) { // Exceeded latency budget: Do not add more samples into FIFO. diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 762a872d50..72082b19ce 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -20,7 +20,7 @@ public: ~CMixer(); // Called from audio threads - unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit = true); + unsigned int Mix(short* samples, unsigned int numSamples); // Called from main thread void PushSamples(const short* samples, unsigned int num_samples); diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index ba10d0aeb8..ffb309c7f7 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -249,6 +249,8 @@ void SConfig::SaveCoreSettings(IniFile& ini) core->Set("OverrideGCLang", bOverrideGCLanguage); core->Set("DPL2Decoder", bDPL2Decoder); core->Set("Latency", iLatency); + core->Set("AudioStretch", m_audio_stretch); + core->Set("AudioStretchMaxLatency", m_audio_stretch_max_latency); core->Set("MemcardAPath", m_strMemoryCardA); core->Set("MemcardBPath", m_strMemoryCardB); core->Set("AgpCartAPath", m_strGbaCartA); @@ -567,6 +569,8 @@ void SConfig::LoadCoreSettings(IniFile& ini) core->Get("OverrideGCLang", &bOverrideGCLanguage, false); core->Get("DPL2Decoder", &bDPL2Decoder, false); core->Get("Latency", &iLatency, 2); + core->Get("AudioStretch", &m_audio_stretch, false); + core->Get("AudioStretchMaxLatency", &m_audio_stretch_max_latency, 80); core->Get("MemcardAPath", &m_strMemoryCardA); core->Get("MemcardBPath", &m_strMemoryCardB); core->Get("AgpCartAPath", &m_strGbaCartA); @@ -827,6 +831,8 @@ void SConfig::LoadDefaults() bWii = false; bDPL2Decoder = false; iLatency = 14; + m_audio_stretch = false; + m_audio_stretch_max_latency = 80; iPosX = INT_MIN; iPosY = INT_MIN; diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 3754956c47..a66fd70e76 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -110,6 +110,8 @@ struct SConfig : NonCopyable bool bDPL2Decoder = false; int iLatency = 14; + bool m_audio_stretch = false; + int m_audio_stretch_max_latency = 80; bool bRunCompareServer = false; bool bRunCompareClient = false; From 71e748b68f799eb9e219ace7fc567b6aaeea29e9 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Mon, 10 Apr 2017 18:46:21 +0100 Subject: [PATCH 06/10] AudioConfigPane: Allow user-configuration of audio stretching --- .../Core/DolphinWX/Config/AudioConfigPane.cpp | 53 +++++++++++++++++++ .../Core/DolphinWX/Config/AudioConfigPane.h | 6 +++ 2 files changed, 59 insertions(+) diff --git a/Source/Core/DolphinWX/Config/AudioConfigPane.cpp b/Source/Core/DolphinWX/Config/AudioConfigPane.cpp index 6a49499fe0..ec83542d15 100644 --- a/Source/Core/DolphinWX/Config/AudioConfigPane.cpp +++ b/Source/Core/DolphinWX/Config/AudioConfigPane.cpp @@ -48,12 +48,21 @@ void AudioConfigPane::InitializeGUI() new wxSpinCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 30); m_audio_latency_label = new wxStaticText(this, wxID_ANY, _("Latency:")); + m_stretch_checkbox = new wxCheckBox(this, wxID_ANY, _("Enable Audio Stretching")); + m_stretch_label = new wxStaticText(this, wxID_ANY, _("Buffer Size:")); + m_stretch_slider = + new DolphinSlider(this, wxID_ANY, 80, 5, 300, wxDefaultPosition, wxDefaultSize); + m_stretch_text = new wxStaticText(this, wxID_ANY, ""); + m_audio_backend_choice->SetToolTip( _("Changing this will have no effect while the emulator is running.")); m_audio_latency_spinctrl->SetToolTip(_("Sets the latency (in ms). Higher values may reduce audio " "crackling. Certain backends only.")); m_dpl2_decoder_checkbox->SetToolTip( _("Enables Dolby Pro Logic II emulation using 5.1 surround. Certain backends only.")); + m_stretch_checkbox->SetToolTip(_("Enables stretching of the audio to match emulation speed.")); + m_stretch_slider->SetToolTip(_("Size of stretch buffer in milliseconds. " + "Values too low may cause audio crackling.")); const int space5 = FromDIP(5); @@ -93,12 +102,32 @@ void AudioConfigPane::InitializeGUI() dsp_audio_sizer->Add(volume_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, space5); dsp_audio_sizer->AddSpacer(space5); + wxGridBagSizer* const latency_sizer = new wxGridBagSizer(); + latency_sizer->Add(m_stretch_slider, wxGBPosition(0, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + latency_sizer->Add(m_stretch_text, wxGBPosition(0, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + + wxGridBagSizer* const stretching_grid_sizer = new wxGridBagSizer(space5, space5); + stretching_grid_sizer->Add(m_stretch_checkbox, wxGBPosition(0, 0), wxGBSpan(1, 2), + wxALIGN_CENTER_VERTICAL); + stretching_grid_sizer->Add(m_stretch_label, wxGBPosition(1, 0), wxDefaultSpan, + wxALIGN_CENTER_VERTICAL); + stretching_grid_sizer->Add(latency_sizer, wxGBPosition(1, 1), wxDefaultSpan, + wxALIGN_CENTER_VERTICAL); + + wxStaticBoxSizer* const stretching_box_sizer = + new wxStaticBoxSizer(wxVERTICAL, this, _("Audio Stretching Settings")); + stretching_box_sizer->AddSpacer(space5); + stretching_box_sizer->Add(stretching_grid_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5); + stretching_box_sizer->AddSpacer(space5); + wxBoxSizer* const main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->AddSpacer(space5); main_sizer->Add(dsp_audio_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5); main_sizer->AddSpacer(space5); main_sizer->Add(backend_static_box_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5); main_sizer->AddSpacer(space5); + main_sizer->Add(stretching_box_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5); + main_sizer->AddSpacer(space5); SetSizerAndFit(main_sizer); } @@ -119,6 +148,12 @@ void AudioConfigPane::LoadGUIValues() m_volume_text->SetLabel(wxString::Format("%d %%", SConfig::GetInstance().m_Volume)); m_dpl2_decoder_checkbox->SetValue(startup_params.bDPL2Decoder); m_audio_latency_spinctrl->SetValue(startup_params.iLatency); + m_stretch_checkbox->SetValue(startup_params.m_audio_stretch); + m_stretch_slider->Enable(startup_params.m_audio_stretch); + m_stretch_slider->SetValue(startup_params.m_audio_stretch_max_latency); + m_stretch_text->Enable(startup_params.m_audio_stretch); + m_stretch_text->SetLabel(wxString::Format("%d ms", startup_params.m_audio_stretch_max_latency)); + m_stretch_label->Enable(startup_params.m_audio_stretch); } void AudioConfigPane::ToggleBackendSpecificControls(const std::string& backend) @@ -150,6 +185,9 @@ void AudioConfigPane::BindEvents() m_audio_latency_spinctrl->Bind(wxEVT_SPINCTRL, &AudioConfigPane::OnLatencySpinCtrlChanged, this); m_audio_latency_spinctrl->Bind(wxEVT_UPDATE_UI, &WxEventUtils::OnEnableIfCoreNotRunning); + + m_stretch_checkbox->Bind(wxEVT_CHECKBOX, &AudioConfigPane::OnStretchCheckBoxChanged, this); + m_stretch_slider->Bind(wxEVT_SLIDER, &AudioConfigPane::OnStretchSliderChanged, this); } void AudioConfigPane::OnDSPEngineRadioBoxChanged(wxCommandEvent& event) @@ -186,6 +224,21 @@ void AudioConfigPane::OnLatencySpinCtrlChanged(wxCommandEvent& event) SConfig::GetInstance().iLatency = m_audio_latency_spinctrl->GetValue(); } +void AudioConfigPane::OnStretchCheckBoxChanged(wxCommandEvent& event) +{ + const bool stretch_enabled = m_stretch_checkbox->GetValue(); + SConfig::GetInstance().m_audio_stretch = stretch_enabled; + m_stretch_slider->Enable(stretch_enabled); + m_stretch_text->Enable(stretch_enabled); + m_stretch_label->Enable(stretch_enabled); +} + +void AudioConfigPane::OnStretchSliderChanged(wxCommandEvent& event) +{ + SConfig::GetInstance().m_audio_stretch_max_latency = m_stretch_slider->GetValue(); + m_stretch_text->SetLabel(wxString::Format("%d ms", m_stretch_slider->GetValue())); +} + void AudioConfigPane::PopulateBackendChoiceBox() { for (const std::string& backend : AudioCommon::GetSoundBackends()) diff --git a/Source/Core/DolphinWX/Config/AudioConfigPane.h b/Source/Core/DolphinWX/Config/AudioConfigPane.h index 0c5db837e6..06618d1b57 100644 --- a/Source/Core/DolphinWX/Config/AudioConfigPane.h +++ b/Source/Core/DolphinWX/Config/AudioConfigPane.h @@ -33,6 +33,8 @@ private: void OnVolumeSliderChanged(wxCommandEvent&); void OnAudioBackendChanged(wxCommandEvent&); void OnLatencySpinCtrlChanged(wxCommandEvent&); + void OnStretchCheckBoxChanged(wxCommandEvent&); + void OnStretchSliderChanged(wxCommandEvent&); wxArrayString m_dsp_engine_strings; wxArrayString m_audio_backend_strings; @@ -44,4 +46,8 @@ private: wxChoice* m_audio_backend_choice; wxSpinCtrl* m_audio_latency_spinctrl; wxStaticText* m_audio_latency_label; + wxCheckBox* m_stretch_checkbox; + wxStaticText* m_stretch_label; + DolphinSlider* m_stretch_slider; + wxStaticText* m_stretch_text; }; From ac0df5b2dbd40ac58137f13c09719abe24a95caf Mon Sep 17 00:00:00 2001 From: MerryMage Date: Tue, 11 Apr 2017 10:07:21 +0100 Subject: [PATCH 07/10] Mixer: Disable frequency shifting when stretching is enabled --- Source/Core/AudioCommon/Mixer.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 5030e5d492..3e64d7f01f 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -47,26 +47,27 @@ unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples, u32 indexR = m_indexR.load(); u32 indexW = m_indexW.load(); - u32 low_waterwark = m_input_sample_rate * SConfig::GetInstance().iTimingVariance / 1000; - low_waterwark = std::min(low_waterwark, MAX_SAMPLES / 2); - - float numLeft = (float)(((indexW - indexR) & INDEX_MASK) / 2); - m_numLeftI = (numLeft + m_numLeftI * (CONTROL_AVG - 1)) / CONTROL_AVG; - float offset = (m_numLeftI - low_waterwark) * CONTROL_FACTOR; - if (offset > MAX_FREQ_SHIFT) - offset = MAX_FREQ_SHIFT; - if (offset < -MAX_FREQ_SHIFT) - offset = -MAX_FREQ_SHIFT; - // render numleft sample pairs to samples[] // advance indexR with sample position // remember fractional offset float emulationspeed = SConfig::GetInstance().m_EmulationSpeed; - float aid_sample_rate = m_input_sample_rate + offset; + float aid_sample_rate = static_cast(m_input_sample_rate); if (consider_framelimit && emulationspeed > 0.0f) { - aid_sample_rate = aid_sample_rate * emulationspeed; + float numLeft = static_cast(((indexW - indexR) & INDEX_MASK) / 2); + + u32 low_waterwark = m_input_sample_rate * SConfig::GetInstance().iTimingVariance / 1000; + low_waterwark = std::min(low_waterwark, MAX_SAMPLES / 2); + + m_numLeftI = (numLeft + m_numLeftI * (CONTROL_AVG - 1)) / CONTROL_AVG; + float offset = (m_numLeftI - low_waterwark) * CONTROL_FACTOR; + if (offset > MAX_FREQ_SHIFT) + offset = MAX_FREQ_SHIFT; + if (offset < -MAX_FREQ_SHIFT) + offset = -MAX_FREQ_SHIFT; + + aid_sample_rate = (aid_sample_rate + offset) * emulationspeed; } const u32 ratio = (u32)(65536.0f * aid_sample_rate / (float)m_mixer->m_sampleRate); From f5018010d39d4aff030c19b13f18f12c04c4a99b Mon Sep 17 00:00:00 2001 From: MerryMage Date: Tue, 11 Apr 2017 12:41:35 +0100 Subject: [PATCH 08/10] Mixer: Calculate actual_samples based on availability in all FIFOs --- Source/Core/AudioCommon/Mixer.cpp | 37 +++++++++++++++++++++++-------- Source/Core/AudioCommon/Mixer.h | 1 + 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 3e64d7f01f..0863dd1d7c 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -130,19 +130,30 @@ unsigned int CMixer::Mix(short* samples, unsigned int num_samples) memset(samples, 0, num_samples * 2 * sizeof(short)); - const bool stretch = SConfig::GetInstance().m_audio_stretch; - - unsigned int actual_samples = m_dma_mixer.Mix(samples, num_samples, !stretch); - m_streaming_mixer.Mix(samples, num_samples, !stretch); - m_wiimote_speaker_mixer.Mix(samples, num_samples, !stretch); - - if (stretch) + if (SConfig::GetInstance().m_audio_stretch) { - if (m_is_stretching != stretch) + unsigned int actual_samples = std::min({ + m_dma_mixer.AvailableSamples(), m_streaming_mixer.AvailableSamples(), num_samples, + }); + + m_dma_mixer.Mix(samples, actual_samples, false); + m_streaming_mixer.Mix(samples, actual_samples, false); + m_wiimote_speaker_mixer.Mix(samples, actual_samples, false); + + if (!m_is_stretching) + { m_sound_touch.clear(); + m_is_stretching = true; + } StretchAudio(samples, actual_samples, num_samples); } - m_is_stretching = stretch; + else + { + m_dma_mixer.Mix(samples, num_samples, true); + m_streaming_mixer.Mix(samples, num_samples, true); + m_wiimote_speaker_mixer.Mix(samples, num_samples, true); + m_is_stretching = false; + } return num_samples; } @@ -378,3 +389,11 @@ void CMixer::MixerFifo::SetVolume(unsigned int lvolume, unsigned int rvolume) m_LVolume.store(lvolume + (lvolume >> 7)); m_RVolume.store(rvolume + (rvolume >> 7)); } + +unsigned int CMixer::MixerFifo::AvailableSamples() const +{ + unsigned int samples_in_fifo = ((m_indexW.load() - m_indexR.load()) & INDEX_MASK) / 2; + if (samples_in_fifo <= 1) + return 0; // CMixer::MixerFifo::Mix always keeps one sample in the buffer. + return (samples_in_fifo - 1) * m_mixer->m_sampleRate / m_input_sample_rate; +} diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 72082b19ce..45b542bcf5 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -60,6 +60,7 @@ private: void SetInputSampleRate(unsigned int rate); unsigned int GetInputSampleRate() const; void SetVolume(unsigned int lvolume, unsigned int rvolume); + unsigned int AvailableSamples() const; private: CMixer* m_mixer; From 9397fdfe95ad5f3e74a1fce2e049abddcbbbfba5 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Wed, 12 Apr 2017 12:56:07 +0100 Subject: [PATCH 09/10] Mixer: Use a temporary buffer when stretching audio --- Source/Core/AudioCommon/Mixer.cpp | 48 ++++++++++++++----------------- Source/Core/AudioCommon/Mixer.h | 3 +- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 0863dd1d7c..ffb9489246 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -132,20 +132,21 @@ unsigned int CMixer::Mix(short* samples, unsigned int num_samples) if (SConfig::GetInstance().m_audio_stretch) { - unsigned int actual_samples = std::min({ - m_dma_mixer.AvailableSamples(), m_streaming_mixer.AvailableSamples(), num_samples, - }); + unsigned int available_samples = + std::min(m_dma_mixer.AvailableSamples(), m_streaming_mixer.AvailableSamples()); - m_dma_mixer.Mix(samples, actual_samples, false); - m_streaming_mixer.Mix(samples, actual_samples, false); - m_wiimote_speaker_mixer.Mix(samples, actual_samples, false); + m_stretch_buffer.fill(0); + + m_dma_mixer.Mix(m_stretch_buffer.data(), available_samples, false); + m_streaming_mixer.Mix(m_stretch_buffer.data(), available_samples, false); + m_wiimote_speaker_mixer.Mix(m_stretch_buffer.data(), available_samples, false); if (!m_is_stretching) { m_sound_touch.clear(); m_is_stretching = true; } - StretchAudio(samples, actual_samples, num_samples); + StretchAudio(m_stretch_buffer.data(), available_samples, samples, num_samples); } else { @@ -158,12 +159,12 @@ unsigned int CMixer::Mix(short* samples, unsigned int num_samples) return num_samples; } -void CMixer::StretchAudio(short* samples, unsigned int actual_samples, unsigned int num_samples) +void CMixer::StretchAudio(const short* in, unsigned int num_in, short* out, unsigned int num_out) { - const double time_delta = static_cast(num_samples) / m_sampleRate; // seconds + const double time_delta = static_cast(num_out) / m_sampleRate; // seconds // We were given actual_samples number of samples, and num_samples were requested from us. - double current_ratio = static_cast(actual_samples) / static_cast(num_samples); + double current_ratio = static_cast(num_in) / static_cast(num_out); const double max_latency = SConfig::GetInstance().m_audio_stretch_max_latency; const double max_backlog = m_sampleRate * max_latency / 1000.0; @@ -171,7 +172,7 @@ void CMixer::StretchAudio(short* samples, unsigned int actual_samples, unsigned if (backlog_fullness > 1.0) { // Exceeded latency budget: Do not add more samples into FIFO. - actual_samples = 0; + num_in = 0; } // We ideally want the backlog to be about 50% full. @@ -182,7 +183,7 @@ void CMixer::StretchAudio(short* samples, unsigned int actual_samples, unsigned // This low-pass filter smoothes out variance in the calculated stretch ratio. // The time-scale determines how responsive this filter is. - constexpr double lpf_time_scale = 0.3; // seconds + constexpr double lpf_time_scale = 1.0; // seconds const double m_lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale); m_stretch_ratio += m_lpf_gain * (current_ratio - m_stretch_ratio); @@ -190,29 +191,24 @@ void CMixer::StretchAudio(short* samples, unsigned int actual_samples, unsigned // many silence samples. These do not need to be timestretched. m_sound_touch.setTempo(std::max(m_stretch_ratio, 0.1)); - if (actual_samples != num_samples) - { - DEBUG_LOG(AUDIO, "Audio stretching: samples:%u/%u ratio:%f backlog:%f gain: %f", actual_samples, - num_samples, m_stretch_ratio, backlog_fullness, m_lpf_gain); - } + DEBUG_LOG(AUDIO, "Audio stretching: samples:%u/%u ratio:%f backlog:%f gain: %f", num_in, num_out, + m_stretch_ratio, backlog_fullness, m_lpf_gain); - m_sound_touch.putSamples(samples, actual_samples); + m_sound_touch.putSamples(in, num_in); - memset(samples, 0, num_samples * 2 * sizeof(short)); - - const size_t samples_received = m_sound_touch.receiveSamples(samples, num_samples); + const size_t samples_received = m_sound_touch.receiveSamples(out, num_out); if (samples_received != 0) { - m_last_stretched_sample[0] = samples[samples_received * 2 - 2]; - m_last_stretched_sample[1] = samples[samples_received * 2 - 1]; + m_last_stretched_sample[0] = out[samples_received * 2 - 2]; + m_last_stretched_sample[1] = out[samples_received * 2 - 1]; } // Preform padding if we've run out of samples. - for (size_t i = samples_received; i < num_samples; i++) + for (size_t i = samples_received; i < num_out; i++) { - samples[i * 2 + 0] = m_last_stretched_sample[0]; - samples[i * 2 + 1] = m_last_stretched_sample[1]; + out[i * 2 + 0] = m_last_stretched_sample[0]; + out[i * 2 + 1] = m_last_stretched_sample[1]; } } diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 45b542bcf5..f2127cceba 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -75,7 +75,7 @@ private: u32 m_frac = 0; }; - void StretchAudio(short* samples, unsigned int actual_samples, unsigned int num_samples); + void StretchAudio(const short* in, unsigned int num_in, short* out, unsigned int num_out); MixerFifo m_dma_mixer{this, 32000}; MixerFifo m_streaming_mixer{this, 48000}; @@ -86,6 +86,7 @@ private: soundtouch::SoundTouch m_sound_touch; double m_stretch_ratio = 1.0; std::array m_last_stretched_sample = {}; + std::array m_stretch_buffer; WaveFileWriter m_wave_writer_dtk; WaveFileWriter m_wave_writer_dsp; From cbaa00457a961b3aa0213da0f5282e13a69c610d Mon Sep 17 00:00:00 2001 From: MerryMage Date: Wed, 12 Apr 2017 16:30:10 +0100 Subject: [PATCH 10/10] Mixer: Tweak audio stretch parameters --- Source/Core/AudioCommon/Mixer.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index ffb9489246..8149da088c 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -167,18 +167,18 @@ void CMixer::StretchAudio(const short* in, unsigned int num_in, short* out, unsi double current_ratio = static_cast(num_in) / static_cast(num_out); const double max_latency = SConfig::GetInstance().m_audio_stretch_max_latency; - const double max_backlog = m_sampleRate * max_latency / 1000.0; + const double max_backlog = m_sampleRate * max_latency / 1000.0 / m_stretch_ratio; const double backlog_fullness = m_sound_touch.numSamples() / max_backlog; - if (backlog_fullness > 1.0) + if (backlog_fullness > 5.0) { - // Exceeded latency budget: Do not add more samples into FIFO. + // Too many samples in backlog: Don't push anymore on num_in = 0; } // We ideally want the backlog to be about 50% full. // This gives some headroom both ways to prevent underflow and overflow. // We tweak current_ratio to encourage this. - constexpr double tweak_time_scale = 0.1; // seconds + constexpr double tweak_time_scale = 0.5; // seconds current_ratio *= 1.0 + 2.0 * (backlog_fullness - 0.5) * (time_delta / tweak_time_scale); // This low-pass filter smoothes out variance in the calculated stretch ratio. @@ -189,7 +189,8 @@ void CMixer::StretchAudio(const short* in, unsigned int num_in, short* out, unsi // Place a lower limit of 10% speed. When a game boots up, there will be // many silence samples. These do not need to be timestretched. - m_sound_touch.setTempo(std::max(m_stretch_ratio, 0.1)); + m_stretch_ratio = std::max(m_stretch_ratio, 0.1); + m_sound_touch.setTempo(m_stretch_ratio); DEBUG_LOG(AUDIO, "Audio stretching: samples:%u/%u ratio:%f backlog:%f gain: %f", num_in, num_out, m_stretch_ratio, backlog_fullness, m_lpf_gain);