mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-09 15:49:25 +01:00
Merge pull request #5241 from MerryMage/stretch
Pitch-Preserving Audio Stretching
This commit is contained in:
commit
2151858fb1
@ -650,18 +650,11 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(OpenAL)
|
find_package(OpenAL)
|
||||||
if(OPENAL_FOUND)
|
|
||||||
if(NOT APPLE)
|
# Using static soundtouch from Externals
|
||||||
check_lib(SOUNDTOUCH soundtouch SoundTouch soundtouch/SoundTouch.h QUIET)
|
# Unable to use system soundtouch library: We require shorts, not floats.
|
||||||
endif()
|
add_subdirectory(Externals/soundtouch)
|
||||||
if (SOUNDTOUCH_FOUND)
|
include_directories(Externals)
|
||||||
message(STATUS "Using shared soundtouch")
|
|
||||||
else()
|
|
||||||
message(STATUS "Using static soundtouch from Externals")
|
|
||||||
add_subdirectory(Externals/soundtouch)
|
|
||||||
include_directories(Externals)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT ANDROID)
|
if(NOT ANDROID)
|
||||||
add_definitions(-D__LIBUSB__)
|
add_definitions(-D__LIBUSB__)
|
||||||
|
6
Externals/soundtouch/STTypes.h
vendored
6
Externals/soundtouch/STTypes.h
vendored
@ -98,8 +98,8 @@ namespace soundtouch
|
|||||||
/// However, if you still prefer to select the sample format here
|
/// However, if you still prefer to select the sample format here
|
||||||
/// also in GNU environment, then please #undef the INTEGER_SAMPLE
|
/// also in GNU environment, then please #undef the INTEGER_SAMPLE
|
||||||
/// and FLOAT_SAMPLE defines first as in comments above.
|
/// and FLOAT_SAMPLE defines first as in comments above.
|
||||||
//#define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples
|
#define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples
|
||||||
#define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples
|
//#define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ namespace soundtouch
|
|||||||
/// routines compiled for whatever reason, you may disable these optimizations
|
/// routines compiled for whatever reason, you may disable these optimizations
|
||||||
/// to make the library compile.
|
/// 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
|
/// In GNU environment, allow the user to override this setting by
|
||||||
/// giving the following switch to the configure script:
|
/// giving the following switch to the configure script:
|
||||||
|
@ -37,7 +37,7 @@ if(ENABLE_OPENAL)
|
|||||||
if(OPENAL_FOUND)
|
if(OPENAL_FOUND)
|
||||||
message(STATUS "OpenAL found, enabling OpenAL sound backend")
|
message(STATUS "OpenAL found, enabling OpenAL sound backend")
|
||||||
target_sources(audiocommon PRIVATE OpenALStream.cpp aldlist.cpp)
|
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)
|
target_compile_definitions(audiocommon PRIVATE HAVE_OPENAL=1)
|
||||||
else()
|
else()
|
||||||
message(STATUS "OpenAL NOT found, disabling OpenAL sound backend")
|
message(STATUS "OpenAL NOT found, disabling OpenAL sound backend")
|
||||||
@ -76,4 +76,4 @@ elseif(APPLE)
|
|||||||
target_sources(audiocommon PRIVATE CoreAudioSoundStream.cpp)
|
target_sources(audiocommon PRIVATE CoreAudioSoundStream.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(audiocommon PRIVATE SoundTouch)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "AudioCommon/Mixer.h"
|
#include "AudioCommon/Mixer.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
@ -15,6 +16,15 @@
|
|||||||
CMixer::CMixer(unsigned int BackendSampleRate) : m_sampleRate(BackendSampleRate)
|
CMixer::CMixer(unsigned int BackendSampleRate) : m_sampleRate(BackendSampleRate)
|
||||||
{
|
{
|
||||||
INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized");
|
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()
|
CMixer::~CMixer()
|
||||||
@ -37,26 +47,27 @@ unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples,
|
|||||||
u32 indexR = m_indexR.load();
|
u32 indexR = m_indexR.load();
|
||||||
u32 indexW = m_indexW.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[]
|
// render numleft sample pairs to samples[]
|
||||||
// advance indexR with sample position
|
// advance indexR with sample position
|
||||||
// remember fractional offset
|
// remember fractional offset
|
||||||
|
|
||||||
float emulationspeed = SConfig::GetInstance().m_EmulationSpeed;
|
float emulationspeed = SConfig::GetInstance().m_EmulationSpeed;
|
||||||
float aid_sample_rate = m_input_sample_rate + offset;
|
float aid_sample_rate = static_cast<float>(m_input_sample_rate);
|
||||||
if (consider_framelimit && emulationspeed > 0.0f)
|
if (consider_framelimit && emulationspeed > 0.0f)
|
||||||
{
|
{
|
||||||
aid_sample_rate = aid_sample_rate * emulationspeed;
|
float numLeft = static_cast<float>(((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);
|
const u32 ratio = (u32)(65536.0f * aid_sample_rate / (float)m_mixer->m_sampleRate);
|
||||||
@ -88,6 +99,9 @@ unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples,
|
|||||||
m_frac &= 0xffff;
|
m_frac &= 0xffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actual number of samples written to the buffer without padding.
|
||||||
|
unsigned int actual_sample_count = currentSample / 2;
|
||||||
|
|
||||||
// Padding
|
// Padding
|
||||||
short s[2];
|
short s[2];
|
||||||
s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]);
|
s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]);
|
||||||
@ -106,22 +120,99 @@ unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples,
|
|||||||
// Flush cached variable
|
// Flush cached variable
|
||||||
m_indexR.store(indexR);
|
m_indexR.store(indexR);
|
||||||
|
|
||||||
return 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)
|
if (!samples)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
memset(samples, 0, num_samples * 2 * sizeof(short));
|
memset(samples, 0, num_samples * 2 * sizeof(short));
|
||||||
|
|
||||||
m_dma_mixer.Mix(samples, num_samples, consider_framelimit);
|
if (SConfig::GetInstance().m_audio_stretch)
|
||||||
m_streaming_mixer.Mix(samples, num_samples, consider_framelimit);
|
{
|
||||||
m_wiimote_speaker_mixer.Mix(samples, num_samples, consider_framelimit);
|
unsigned int available_samples =
|
||||||
|
std::min(m_dma_mixer.AvailableSamples(), m_streaming_mixer.AvailableSamples());
|
||||||
|
|
||||||
|
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(m_stretch_buffer.data(), available_samples, samples, num_samples);
|
||||||
|
}
|
||||||
|
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;
|
return num_samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CMixer::StretchAudio(const short* in, unsigned int num_in, short* out, unsigned int num_out)
|
||||||
|
{
|
||||||
|
const double time_delta = static_cast<double>(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<double>(num_in) / static_cast<double>(num_out);
|
||||||
|
|
||||||
|
const double max_latency = SConfig::GetInstance().m_audio_stretch_max_latency;
|
||||||
|
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 > 5.0)
|
||||||
|
{
|
||||||
|
// 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.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.
|
||||||
|
// The time-scale determines how responsive this filter is.
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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_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);
|
||||||
|
|
||||||
|
m_sound_touch.putSamples(in, num_in);
|
||||||
|
|
||||||
|
const size_t samples_received = m_sound_touch.receiveSamples(out, num_out);
|
||||||
|
|
||||||
|
if (samples_received != 0)
|
||||||
|
{
|
||||||
|
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_out; i++)
|
||||||
|
{
|
||||||
|
out[i * 2 + 0] = m_last_stretched_sample[0];
|
||||||
|
out[i * 2 + 1] = m_last_stretched_sample[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CMixer::MixerFifo::PushSamples(const short* samples, unsigned int num_samples)
|
void CMixer::MixerFifo::PushSamples(const short* samples, unsigned int num_samples)
|
||||||
{
|
{
|
||||||
// Cache access in non-volatile variable
|
// Cache access in non-volatile variable
|
||||||
@ -295,3 +386,11 @@ void CMixer::MixerFifo::SetVolume(unsigned int lvolume, unsigned int rvolume)
|
|||||||
m_LVolume.store(lvolume + (lvolume >> 7));
|
m_LVolume.store(lvolume + (lvolume >> 7));
|
||||||
m_RVolume.store(rvolume + (rvolume >> 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;
|
||||||
|
}
|
||||||
|
@ -10,6 +10,9 @@
|
|||||||
#include "AudioCommon/WaveFile.h"
|
#include "AudioCommon/WaveFile.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
#include <soundtouch/STTypes.h>
|
||||||
|
#include <soundtouch/SoundTouch.h>
|
||||||
|
|
||||||
class CMixer final
|
class CMixer final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -17,7 +20,7 @@ public:
|
|||||||
~CMixer();
|
~CMixer();
|
||||||
|
|
||||||
// Called from audio threads
|
// 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
|
// Called from main thread
|
||||||
void PushSamples(const short* samples, unsigned int num_samples);
|
void PushSamples(const short* samples, unsigned int num_samples);
|
||||||
@ -57,6 +60,7 @@ private:
|
|||||||
void SetInputSampleRate(unsigned int rate);
|
void SetInputSampleRate(unsigned int rate);
|
||||||
unsigned int GetInputSampleRate() const;
|
unsigned int GetInputSampleRate() const;
|
||||||
void SetVolume(unsigned int lvolume, unsigned int rvolume);
|
void SetVolume(unsigned int lvolume, unsigned int rvolume);
|
||||||
|
unsigned int AvailableSamples() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CMixer* m_mixer;
|
CMixer* m_mixer;
|
||||||
@ -70,11 +74,20 @@ private:
|
|||||||
float m_numLeftI = 0.0f;
|
float m_numLeftI = 0.0f;
|
||||||
u32 m_frac = 0;
|
u32 m_frac = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void StretchAudio(const short* in, unsigned int num_in, short* out, unsigned int num_out);
|
||||||
|
|
||||||
MixerFifo m_dma_mixer{this, 32000};
|
MixerFifo m_dma_mixer{this, 32000};
|
||||||
MixerFifo m_streaming_mixer{this, 48000};
|
MixerFifo m_streaming_mixer{this, 48000};
|
||||||
MixerFifo m_wiimote_speaker_mixer{this, 3000};
|
MixerFifo m_wiimote_speaker_mixer{this, 3000};
|
||||||
unsigned int m_sampleRate;
|
unsigned int m_sampleRate;
|
||||||
|
|
||||||
|
bool m_is_stretching = false;
|
||||||
|
soundtouch::SoundTouch m_sound_touch;
|
||||||
|
double m_stretch_ratio = 1.0;
|
||||||
|
std::array<short, 2> m_last_stretched_sample = {};
|
||||||
|
std::array<short, MAX_SAMPLES * 2> m_stretch_buffer;
|
||||||
|
|
||||||
WaveFileWriter m_wave_writer_dtk;
|
WaveFileWriter m_wave_writer_dtk;
|
||||||
WaveFileWriter m_wave_writer_dsp;
|
WaveFileWriter m_wave_writer_dsp;
|
||||||
|
|
||||||
|
@ -20,8 +20,6 @@
|
|||||||
#pragma comment(lib, "openal32.lib")
|
#pragma comment(lib, "openal32.lib")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static soundtouch::SoundTouch soundTouch;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// AyuanX: Spec says OpenAL1.1 is thread safe already
|
// AyuanX: Spec says OpenAL1.1 is thread safe already
|
||||||
//
|
//
|
||||||
@ -71,7 +69,6 @@ bool OpenALStream::Start()
|
|||||||
// Initialize DPL2 parameters
|
// Initialize DPL2 parameters
|
||||||
DPL2Reset();
|
DPL2Reset();
|
||||||
|
|
||||||
soundTouch.clear();
|
|
||||||
return bReturn;
|
return bReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,8 +78,6 @@ void OpenALStream::Stop()
|
|||||||
// kick the thread if it's waiting
|
// kick the thread if it's waiting
|
||||||
soundSyncEvent.Set();
|
soundSyncEvent.Set();
|
||||||
|
|
||||||
soundTouch.clear();
|
|
||||||
|
|
||||||
thread.join();
|
thread.join();
|
||||||
|
|
||||||
alSourceStop(uiSource);
|
alSourceStop(uiSource);
|
||||||
@ -120,7 +115,6 @@ void OpenALStream::Clear(bool mute)
|
|||||||
|
|
||||||
if (m_muted)
|
if (m_muted)
|
||||||
{
|
{
|
||||||
soundTouch.clear();
|
|
||||||
alSourceStop(uiSource);
|
alSourceStop(uiSource);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -213,15 +207,6 @@ void OpenALStream::SoundLoop()
|
|||||||
unsigned int numBuffersQueued = 0;
|
unsigned int numBuffersQueued = 0;
|
||||||
ALint iState = 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())
|
while (m_run_thread.IsSet())
|
||||||
{
|
{
|
||||||
// Block until we have a free buffer
|
// Block until we have a free buffer
|
||||||
@ -243,62 +228,29 @@ void OpenALStream::SoundLoop()
|
|||||||
numBuffersQueued -= numBuffersProcessed;
|
numBuffersQueued -= numBuffersProcessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// num_samples_to_render in this update - depends on SystemTimers::AUDIO_DMA_PERIOD.
|
// DPL2 accepts 240 samples minimum (FWRDURATION)
|
||||||
const u32 stereo_16_bit_size = 4;
|
unsigned int minSamples = surround_capable ? 240 : 0;
|
||||||
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();
|
|
||||||
|
|
||||||
unsigned int numSamples = (unsigned int)num_samples_to_render;
|
unsigned int numSamples = OAL_MAX_SAMPLES;
|
||||||
unsigned int minSamples =
|
numSamples = m_mixer->Mix(realtimeBuffer, numSamples);
|
||||||
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);
|
|
||||||
|
|
||||||
// Convert the samples from short to float
|
// Convert the samples from short to float
|
||||||
float dest[OAL_MAX_SAMPLES * STEREO_CHANNELS];
|
|
||||||
for (u32 i = 0; i < numSamples * STEREO_CHANNELS; ++i)
|
for (u32 i = 0; i < numSamples * STEREO_CHANNELS; ++i)
|
||||||
dest[i] = (float)realtimeBuffer[i] / (1 << 15);
|
sampleBuffer[i] = static_cast<float>(realtimeBuffer[i]) / (1 << 15);
|
||||||
|
|
||||||
soundTouch.putSamples(dest, numSamples);
|
if (numSamples <= minSamples)
|
||||||
|
|
||||||
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)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (surround_capable)
|
if (surround_capable)
|
||||||
{
|
{
|
||||||
float dpl2[OAL_MAX_SAMPLES * OAL_MAX_BUFFERS * SURROUND_CHANNELS];
|
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
|
// 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
|
// 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.
|
// AL_FORMAT_50CHN32 to make this super-explicit.
|
||||||
// DPL2Decode output: LEFTFRONT, RIGHTFRONT, CENTREFRONT, (sub), LEFTREAR, RIGHTREAR
|
// 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;
|
dpl2[i * SURROUND_CHANNELS + 3 /*sub/lfe*/] = 0.0f;
|
||||||
}
|
}
|
||||||
@ -306,13 +258,13 @@ void OpenALStream::SoundLoop()
|
|||||||
if (float32_capable)
|
if (float32_capable)
|
||||||
{
|
{
|
||||||
alBufferData(uiBuffers[nextBuffer], AL_FORMAT_51CHN32, dpl2,
|
alBufferData(uiBuffers[nextBuffer], AL_FORMAT_51CHN32, dpl2,
|
||||||
nSamples * FRAME_SURROUND_FLOAT, ulFrequency);
|
numSamples * FRAME_SURROUND_FLOAT, ulFrequency);
|
||||||
}
|
}
|
||||||
else if (fixed32_capable)
|
else if (fixed32_capable)
|
||||||
{
|
{
|
||||||
int surround_int32[OAL_MAX_SAMPLES * SURROUND_CHANNELS * OAL_MAX_BUFFERS];
|
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.
|
// 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
|
// 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,
|
alBufferData(uiBuffers[nextBuffer], AL_FORMAT_51CHN32, surround_int32,
|
||||||
nSamples * FRAME_SURROUND_INT32, ulFrequency);
|
numSamples * FRAME_SURROUND_INT32, ulFrequency);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
short surround_short[OAL_MAX_SAMPLES * SURROUND_CHANNELS * OAL_MAX_BUFFERS];
|
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);
|
dpl2[i] = dpl2[i] * (1 << 15);
|
||||||
if (dpl2[i] > SHRT_MAX)
|
if (dpl2[i] > SHRT_MAX)
|
||||||
@ -345,7 +297,7 @@ void OpenALStream::SoundLoop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
alBufferData(uiBuffers[nextBuffer], AL_FORMAT_51CHN16, surround_short,
|
alBufferData(uiBuffers[nextBuffer], AL_FORMAT_51CHN16, surround_short,
|
||||||
nSamples * FRAME_SURROUND_SHORT, ulFrequency);
|
numSamples * FRAME_SURROUND_SHORT, ulFrequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
err = CheckALError("buffering data");
|
err = CheckALError("buffering data");
|
||||||
@ -362,7 +314,7 @@ void OpenALStream::SoundLoop()
|
|||||||
if (float32_capable)
|
if (float32_capable)
|
||||||
{
|
{
|
||||||
alBufferData(uiBuffers[nextBuffer], AL_FORMAT_STEREO_FLOAT32, sampleBuffer,
|
alBufferData(uiBuffers[nextBuffer], AL_FORMAT_STEREO_FLOAT32, sampleBuffer,
|
||||||
nSamples * FRAME_STEREO_FLOAT, ulFrequency);
|
numSamples * FRAME_STEREO_FLOAT, ulFrequency);
|
||||||
|
|
||||||
err = CheckALError("buffering float32 data");
|
err = CheckALError("buffering float32 data");
|
||||||
if (err == AL_INVALID_ENUM)
|
if (err == AL_INVALID_ENUM)
|
||||||
@ -374,21 +326,21 @@ void OpenALStream::SoundLoop()
|
|||||||
{
|
{
|
||||||
// Clamping is not necessary here, samples are always between (-1,1)
|
// Clamping is not necessary here, samples are always between (-1,1)
|
||||||
int stereo_int32[OAL_MAX_SAMPLES * STEREO_CHANNELS * OAL_MAX_BUFFERS];
|
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));
|
stereo_int32[i] = (int)((float)sampleBuffer[i] * (INT64_C(1) << 31));
|
||||||
|
|
||||||
alBufferData(uiBuffers[nextBuffer], AL_FORMAT_STEREO32, stereo_int32,
|
alBufferData(uiBuffers[nextBuffer], AL_FORMAT_STEREO32, stereo_int32,
|
||||||
nSamples * FRAME_STEREO_INT32, ulFrequency);
|
numSamples * FRAME_STEREO_INT32, ulFrequency);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Convert the samples from float to short
|
// Convert the samples from float to short
|
||||||
short stereo[OAL_MAX_SAMPLES * STEREO_CHANNELS * OAL_MAX_BUFFERS];
|
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));
|
stereo[i] = (short)((float)sampleBuffer[i] * (1 << 15));
|
||||||
|
|
||||||
alBufferData(uiBuffers[nextBuffer], AL_FORMAT_STEREO16, stereo,
|
alBufferData(uiBuffers[nextBuffer], AL_FORMAT_STEREO16, stereo,
|
||||||
nSamples * FRAME_STEREO_SHORT, ulFrequency);
|
numSamples * FRAME_STEREO_SHORT, ulFrequency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,18 +26,6 @@
|
|||||||
#include <AL/alext.h>
|
#include <AL/alext.h>
|
||||||
#endif
|
#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 <soundtouch/STTypes.h>
|
|
||||||
#include <soundtouch/SoundTouch.h>
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
|
||||||
#undef BOOL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define SFX_MAX_SOURCE 1
|
#define SFX_MAX_SOURCE 1
|
||||||
#define OAL_MAX_BUFFERS 32
|
#define OAL_MAX_BUFFERS 32
|
||||||
#define OAL_MAX_SAMPLES 256
|
#define OAL_MAX_SAMPLES 256
|
||||||
@ -89,7 +77,7 @@ private:
|
|||||||
Common::Event soundSyncEvent;
|
Common::Event soundSyncEvent;
|
||||||
|
|
||||||
short realtimeBuffer[OAL_MAX_SAMPLES * STEREO_CHANNELS];
|
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 uiBuffers[OAL_MAX_BUFFERS];
|
||||||
ALuint uiSource;
|
ALuint uiSource;
|
||||||
ALfloat fVolume;
|
ALfloat fVolume;
|
||||||
|
@ -249,6 +249,8 @@ void SConfig::SaveCoreSettings(IniFile& ini)
|
|||||||
core->Set("OverrideGCLang", bOverrideGCLanguage);
|
core->Set("OverrideGCLang", bOverrideGCLanguage);
|
||||||
core->Set("DPL2Decoder", bDPL2Decoder);
|
core->Set("DPL2Decoder", bDPL2Decoder);
|
||||||
core->Set("Latency", iLatency);
|
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("MemcardAPath", m_strMemoryCardA);
|
||||||
core->Set("MemcardBPath", m_strMemoryCardB);
|
core->Set("MemcardBPath", m_strMemoryCardB);
|
||||||
core->Set("AgpCartAPath", m_strGbaCartA);
|
core->Set("AgpCartAPath", m_strGbaCartA);
|
||||||
@ -567,6 +569,8 @@ void SConfig::LoadCoreSettings(IniFile& ini)
|
|||||||
core->Get("OverrideGCLang", &bOverrideGCLanguage, false);
|
core->Get("OverrideGCLang", &bOverrideGCLanguage, false);
|
||||||
core->Get("DPL2Decoder", &bDPL2Decoder, false);
|
core->Get("DPL2Decoder", &bDPL2Decoder, false);
|
||||||
core->Get("Latency", &iLatency, 2);
|
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("MemcardAPath", &m_strMemoryCardA);
|
||||||
core->Get("MemcardBPath", &m_strMemoryCardB);
|
core->Get("MemcardBPath", &m_strMemoryCardB);
|
||||||
core->Get("AgpCartAPath", &m_strGbaCartA);
|
core->Get("AgpCartAPath", &m_strGbaCartA);
|
||||||
@ -827,6 +831,8 @@ void SConfig::LoadDefaults()
|
|||||||
bWii = false;
|
bWii = false;
|
||||||
bDPL2Decoder = false;
|
bDPL2Decoder = false;
|
||||||
iLatency = 14;
|
iLatency = 14;
|
||||||
|
m_audio_stretch = false;
|
||||||
|
m_audio_stretch_max_latency = 80;
|
||||||
|
|
||||||
iPosX = INT_MIN;
|
iPosX = INT_MIN;
|
||||||
iPosY = INT_MIN;
|
iPosY = INT_MIN;
|
||||||
|
@ -110,6 +110,8 @@ struct SConfig : NonCopyable
|
|||||||
|
|
||||||
bool bDPL2Decoder = false;
|
bool bDPL2Decoder = false;
|
||||||
int iLatency = 14;
|
int iLatency = 14;
|
||||||
|
bool m_audio_stretch = false;
|
||||||
|
int m_audio_stretch_max_latency = 80;
|
||||||
|
|
||||||
bool bRunCompareServer = false;
|
bool bRunCompareServer = false;
|
||||||
bool bRunCompareClient = false;
|
bool bRunCompareClient = false;
|
||||||
|
@ -48,12 +48,21 @@ void AudioConfigPane::InitializeGUI()
|
|||||||
new wxSpinCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 30);
|
new wxSpinCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 30);
|
||||||
m_audio_latency_label = new wxStaticText(this, wxID_ANY, _("Latency:"));
|
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(
|
m_audio_backend_choice->SetToolTip(
|
||||||
_("Changing this will have no effect while the emulator is running."));
|
_("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 "
|
m_audio_latency_spinctrl->SetToolTip(_("Sets the latency (in ms). Higher values may reduce audio "
|
||||||
"crackling. Certain backends only."));
|
"crackling. Certain backends only."));
|
||||||
m_dpl2_decoder_checkbox->SetToolTip(
|
m_dpl2_decoder_checkbox->SetToolTip(
|
||||||
_("Enables Dolby Pro Logic II emulation using 5.1 surround. Certain backends only."));
|
_("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);
|
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->Add(volume_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, space5);
|
||||||
dsp_audio_sizer->AddSpacer(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);
|
wxBoxSizer* const main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
main_sizer->AddSpacer(space5);
|
main_sizer->AddSpacer(space5);
|
||||||
main_sizer->Add(dsp_audio_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
|
main_sizer->Add(dsp_audio_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
|
||||||
main_sizer->AddSpacer(space5);
|
main_sizer->AddSpacer(space5);
|
||||||
main_sizer->Add(backend_static_box_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
|
main_sizer->Add(backend_static_box_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
|
||||||
main_sizer->AddSpacer(space5);
|
main_sizer->AddSpacer(space5);
|
||||||
|
main_sizer->Add(stretching_box_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
|
||||||
|
main_sizer->AddSpacer(space5);
|
||||||
|
|
||||||
SetSizerAndFit(main_sizer);
|
SetSizerAndFit(main_sizer);
|
||||||
}
|
}
|
||||||
@ -119,6 +148,12 @@ void AudioConfigPane::LoadGUIValues()
|
|||||||
m_volume_text->SetLabel(wxString::Format("%d %%", SConfig::GetInstance().m_Volume));
|
m_volume_text->SetLabel(wxString::Format("%d %%", SConfig::GetInstance().m_Volume));
|
||||||
m_dpl2_decoder_checkbox->SetValue(startup_params.bDPL2Decoder);
|
m_dpl2_decoder_checkbox->SetValue(startup_params.bDPL2Decoder);
|
||||||
m_audio_latency_spinctrl->SetValue(startup_params.iLatency);
|
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)
|
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_SPINCTRL, &AudioConfigPane::OnLatencySpinCtrlChanged, this);
|
||||||
m_audio_latency_spinctrl->Bind(wxEVT_UPDATE_UI, &WxEventUtils::OnEnableIfCoreNotRunning);
|
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)
|
void AudioConfigPane::OnDSPEngineRadioBoxChanged(wxCommandEvent& event)
|
||||||
@ -186,6 +224,21 @@ void AudioConfigPane::OnLatencySpinCtrlChanged(wxCommandEvent& event)
|
|||||||
SConfig::GetInstance().iLatency = m_audio_latency_spinctrl->GetValue();
|
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()
|
void AudioConfigPane::PopulateBackendChoiceBox()
|
||||||
{
|
{
|
||||||
for (const std::string& backend : AudioCommon::GetSoundBackends())
|
for (const std::string& backend : AudioCommon::GetSoundBackends())
|
||||||
|
@ -33,6 +33,8 @@ private:
|
|||||||
void OnVolumeSliderChanged(wxCommandEvent&);
|
void OnVolumeSliderChanged(wxCommandEvent&);
|
||||||
void OnAudioBackendChanged(wxCommandEvent&);
|
void OnAudioBackendChanged(wxCommandEvent&);
|
||||||
void OnLatencySpinCtrlChanged(wxCommandEvent&);
|
void OnLatencySpinCtrlChanged(wxCommandEvent&);
|
||||||
|
void OnStretchCheckBoxChanged(wxCommandEvent&);
|
||||||
|
void OnStretchSliderChanged(wxCommandEvent&);
|
||||||
|
|
||||||
wxArrayString m_dsp_engine_strings;
|
wxArrayString m_dsp_engine_strings;
|
||||||
wxArrayString m_audio_backend_strings;
|
wxArrayString m_audio_backend_strings;
|
||||||
@ -44,4 +46,8 @@ private:
|
|||||||
wxChoice* m_audio_backend_choice;
|
wxChoice* m_audio_backend_choice;
|
||||||
wxSpinCtrl* m_audio_latency_spinctrl;
|
wxSpinCtrl* m_audio_latency_spinctrl;
|
||||||
wxStaticText* m_audio_latency_label;
|
wxStaticText* m_audio_latency_label;
|
||||||
|
wxCheckBox* m_stretch_checkbox;
|
||||||
|
wxStaticText* m_stretch_label;
|
||||||
|
DolphinSlider* m_stretch_slider;
|
||||||
|
wxStaticText* m_stretch_text;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user