mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
AudioCommon: Added Granular Synthesis
This commit is contained in:
committed by
Jordan Woyak
parent
e82f03b825
commit
f09ba10daa
@@ -1,85 +0,0 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "AudioCommon/AudioStretcher.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
|
||||
namespace AudioCommon
|
||||
{
|
||||
AudioStretcher::AudioStretcher(unsigned int sample_rate) : m_sample_rate(sample_rate)
|
||||
{
|
||||
m_sound_touch.setChannels(2);
|
||||
m_sound_touch.setSampleRate(sample_rate);
|
||||
m_sound_touch.setPitch(1.0);
|
||||
m_sound_touch.setTempo(1.0);
|
||||
}
|
||||
|
||||
void AudioStretcher::Clear()
|
||||
{
|
||||
m_sound_touch.clear();
|
||||
}
|
||||
|
||||
void AudioStretcher::ProcessSamples(const short* in, unsigned int num_in, unsigned int num_out)
|
||||
{
|
||||
const double time_delta = static_cast<double>(num_out) / m_sample_rate; // 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 = Config::Get(Config::MAIN_AUDIO_STRETCH_LATENCY);
|
||||
const double max_backlog = m_sample_rate * 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 lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale);
|
||||
m_stretch_ratio += 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_FMT(AUDIO, "Audio stretching: samples:{}/{} ratio:{} backlog:{} gain: {}", num_in,
|
||||
num_out, m_stretch_ratio, backlog_fullness, lpf_gain);
|
||||
|
||||
m_sound_touch.putSamples(in, num_in);
|
||||
}
|
||||
|
||||
void AudioStretcher::GetStretchedSamples(short* out, unsigned int num_out)
|
||||
{
|
||||
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];
|
||||
}
|
||||
|
||||
// Perform 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];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCommon
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <SoundTouch.h>
|
||||
|
||||
namespace AudioCommon
|
||||
{
|
||||
class AudioStretcher
|
||||
{
|
||||
public:
|
||||
explicit AudioStretcher(unsigned int sample_rate);
|
||||
void ProcessSamples(const short* in, unsigned int num_in, unsigned int num_out);
|
||||
void GetStretchedSamples(short* out, unsigned int num_out);
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
unsigned int m_sample_rate;
|
||||
std::array<short, 2> m_last_stretched_sample = {};
|
||||
soundtouch::SoundTouch m_sound_touch;
|
||||
double m_stretch_ratio = 1.0;
|
||||
};
|
||||
|
||||
} // namespace AudioCommon
|
||||
@@ -1,8 +1,6 @@
|
||||
add_library(audiocommon
|
||||
AudioCommon.cpp
|
||||
AudioCommon.h
|
||||
AudioStretcher.cpp
|
||||
AudioStretcher.h
|
||||
CubebStream.h
|
||||
Enums.h
|
||||
Mixer.cpp
|
||||
@@ -90,7 +88,6 @@ PUBLIC
|
||||
common
|
||||
|
||||
PRIVATE
|
||||
SoundTouch
|
||||
FreeSurround)
|
||||
|
||||
if(ENABLE_CUBEB)
|
||||
|
||||
+248
-208
File diff suppressed because it is too large
Load Diff
+103
-44
@@ -3,10 +3,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
|
||||
#include "AudioCommon/AudioStretcher.h"
|
||||
#include "AudioCommon/SurroundDecoder.h"
|
||||
#include "AudioCommon/WaveFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
@@ -17,32 +19,32 @@ class PointerWrap;
|
||||
class Mixer final
|
||||
{
|
||||
public:
|
||||
explicit Mixer(unsigned int BackendSampleRate);
|
||||
explicit Mixer(u32 BackendSampleRate);
|
||||
~Mixer();
|
||||
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
// Called from audio threads
|
||||
unsigned int Mix(short* samples, unsigned int numSamples);
|
||||
unsigned int MixSurround(float* samples, unsigned int num_samples);
|
||||
std::size_t Mix(s16* samples, std::size_t numSamples);
|
||||
std::size_t MixSurround(float* samples, std::size_t num_samples);
|
||||
|
||||
// Called from main thread
|
||||
void PushSamples(const short* samples, unsigned int num_samples);
|
||||
void PushStreamingSamples(const short* samples, unsigned int num_samples);
|
||||
void PushWiimoteSpeakerSamples(const short* samples, unsigned int num_samples,
|
||||
unsigned int sample_rate_divisor);
|
||||
void PushSkylanderPortalSamples(const u8* samples, unsigned int num_samples);
|
||||
void PushGBASamples(int device_number, const short* samples, unsigned int num_samples);
|
||||
void PushSamples(const s16* samples, std::size_t num_samples);
|
||||
void PushStreamingSamples(const s16* samples, std::size_t num_samples);
|
||||
void PushWiimoteSpeakerSamples(const s16* samples, std::size_t num_samples,
|
||||
u32 sample_rate_divisor);
|
||||
void PushSkylanderPortalSamples(const u8* samples, std::size_t num_samples);
|
||||
void PushGBASamples(std::size_t device_number, const s16* samples, std::size_t num_samples);
|
||||
|
||||
unsigned int GetSampleRate() const { return m_sampleRate; }
|
||||
u32 GetSampleRate() const { return m_output_sample_rate; }
|
||||
|
||||
void SetDMAInputSampleRateDivisor(unsigned int rate_divisor);
|
||||
void SetStreamInputSampleRateDivisor(unsigned int rate_divisor);
|
||||
void SetGBAInputSampleRateDivisors(int device_number, unsigned int rate_divisor);
|
||||
void SetDMAInputSampleRateDivisor(u32 rate_divisor);
|
||||
void SetStreamInputSampleRateDivisor(u32 rate_divisor);
|
||||
void SetGBAInputSampleRateDivisors(std::size_t device_number, u32 rate_divisor);
|
||||
|
||||
void SetStreamingVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
void SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
void SetGBAVolume(int device_number, unsigned int lvolume, unsigned int rvolume);
|
||||
void SetStreamingVolume(u32 lvolume, u32 rvolume);
|
||||
void SetWiimoteSpeakerVolume(u32 lvolume, u32 rvolume);
|
||||
void SetGBAVolume(std::size_t device_number, u32 lvolume, u32 rvolume);
|
||||
|
||||
void StartLogDTKAudio(const std::string& filename);
|
||||
void StopLogDTKAudio();
|
||||
@@ -54,44 +56,105 @@ public:
|
||||
static constexpr u64 FIXED_SAMPLE_RATE_DIVIDEND = 54000000 * 2;
|
||||
|
||||
private:
|
||||
static constexpr u32 MAX_SAMPLES = 1024 * 4; // 128 ms
|
||||
static constexpr u32 INDEX_MASK = MAX_SAMPLES * 2 - 1;
|
||||
static constexpr int MAX_FREQ_SHIFT = 200; // Per 32000 Hz
|
||||
static constexpr float CONTROL_FACTOR = 0.2f;
|
||||
static constexpr u32 CONTROL_AVG = 32; // In freq_shift per FIFO size offset
|
||||
|
||||
const unsigned int SURROUND_CHANNELS = 6;
|
||||
const std::size_t SURROUND_CHANNELS = 6;
|
||||
|
||||
class MixerFifo final
|
||||
{
|
||||
static constexpr std::size_t GRANULE_QUEUE_SIZE = 20;
|
||||
|
||||
template <typename T>
|
||||
static s16 ToShort(const T x)
|
||||
{
|
||||
return static_cast<s16>(std::clamp<T>(x, static_cast<T>(std::numeric_limits<s16>::min()),
|
||||
static_cast<T>(std::numeric_limits<s16>::max())));
|
||||
}
|
||||
struct StereoPair final
|
||||
{
|
||||
float l = 0.f;
|
||||
float r = 0.f;
|
||||
|
||||
constexpr StereoPair() = default;
|
||||
constexpr explicit StereoPair(float mono) : l(mono), r(mono) {}
|
||||
constexpr StereoPair(float left, float right) : l(left), r(right) {}
|
||||
constexpr StereoPair(s16 left, s16 right) : l(left), r(right) {}
|
||||
|
||||
StereoPair operator+(const StereoPair& other) const
|
||||
{
|
||||
return StereoPair(l + other.l, r + other.r);
|
||||
}
|
||||
|
||||
StereoPair& operator*=(const StereoPair& other)
|
||||
{
|
||||
l *= other.l;
|
||||
r *= other.r;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr std::size_t GRANULE_BUFFER_SIZE = 256;
|
||||
static constexpr std::size_t GRANULE_BUFFER_MASK = GRANULE_BUFFER_SIZE - 1;
|
||||
static constexpr std::size_t GRANULE_BUFFER_BITS = std::countr_one(GRANULE_BUFFER_MASK);
|
||||
static constexpr std::size_t GRANULE_BUFFER_FRAC_BITS = 32 - GRANULE_BUFFER_BITS;
|
||||
|
||||
using GranuleBuffer = std::array<StereoPair, GRANULE_BUFFER_SIZE>;
|
||||
class Granule final
|
||||
{
|
||||
public:
|
||||
constexpr Granule() = default;
|
||||
constexpr Granule(const GranuleBuffer& input, std::size_t start_index);
|
||||
|
||||
static StereoPair InterpStereoPair(const Granule& front, const Granule& back, u32 frac);
|
||||
|
||||
Granule& operator*=(const StereoPair& x)
|
||||
{
|
||||
for (auto& sample : m_buffer)
|
||||
sample *= x;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
GranuleBuffer m_buffer{};
|
||||
};
|
||||
|
||||
public:
|
||||
MixerFifo(Mixer* mixer, unsigned sample_rate_divisor, bool little_endian)
|
||||
MixerFifo(Mixer* mixer, u32 sample_rate_divisor, bool little_endian)
|
||||
: m_mixer(mixer), m_input_sample_rate_divisor(sample_rate_divisor),
|
||||
m_little_endian(little_endian)
|
||||
{
|
||||
}
|
||||
void DoState(PointerWrap& p);
|
||||
void PushSamples(const short* samples, unsigned int num_samples);
|
||||
unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit,
|
||||
float emulationspeed, int timing_variance);
|
||||
void SetInputSampleRateDivisor(unsigned int rate_divisor);
|
||||
unsigned int GetInputSampleRateDivisor() const;
|
||||
void SetVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
void PushSamples(const s16* samples, std::size_t num_samples);
|
||||
void Mix(s16* samples, std::size_t num_samples);
|
||||
void SetInputSampleRateDivisor(u32 rate_divisor);
|
||||
u32 GetInputSampleRateDivisor() const;
|
||||
void SetVolume(u32 lvolume, u32 rvolume);
|
||||
std::pair<s32, s32> GetVolume() const;
|
||||
unsigned int AvailableSamples() const;
|
||||
|
||||
private:
|
||||
Mixer* m_mixer;
|
||||
unsigned m_input_sample_rate_divisor;
|
||||
u32 m_input_sample_rate_divisor;
|
||||
bool m_little_endian;
|
||||
std::array<short, MAX_SAMPLES * 2> m_buffer{};
|
||||
std::atomic<u32> m_indexW{0};
|
||||
std::atomic<u32> m_indexR{0};
|
||||
|
||||
std::size_t m_buffer_index = 0;
|
||||
GranuleBuffer m_buffer{};
|
||||
|
||||
u32 m_current_index = 0;
|
||||
Granule m_front, m_back;
|
||||
|
||||
std::array<Granule, GRANULE_QUEUE_SIZE> m_queue;
|
||||
std::atomic<std::size_t> m_queue_head{0};
|
||||
std::atomic<std::size_t> m_queue_tail{0};
|
||||
std::atomic<bool> m_queue_looping{false};
|
||||
std::size_t m_queue_fade_index = 0;
|
||||
|
||||
void Enqueue(const Granule& granule);
|
||||
void Dequeue(Granule* granule);
|
||||
|
||||
// Volume ranges from 0-256
|
||||
std::atomic<s32> m_LVolume{256};
|
||||
std::atomic<s32> m_RVolume{256};
|
||||
float m_numLeftI = 0.0f;
|
||||
u32 m_frac = 0;
|
||||
|
||||
StereoPair m_quantization_error;
|
||||
};
|
||||
|
||||
void RefreshConfig();
|
||||
@@ -104,12 +167,9 @@ private:
|
||||
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000, true},
|
||||
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000, true},
|
||||
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000, true}};
|
||||
unsigned int m_sampleRate;
|
||||
u32 m_output_sample_rate;
|
||||
|
||||
bool m_is_stretching = false;
|
||||
AudioCommon::AudioStretcher m_stretcher;
|
||||
AudioCommon::SurroundDecoder m_surround_decoder;
|
||||
std::array<short, MAX_SAMPLES * 2> m_scratch_buffer{};
|
||||
|
||||
WaveFileWriter m_wave_writer_dtk;
|
||||
WaveFileWriter m_wave_writer_dsp;
|
||||
@@ -118,8 +178,7 @@ private:
|
||||
bool m_log_dsp_audio = false;
|
||||
|
||||
float m_config_emulation_speed;
|
||||
int m_config_timing_variance;
|
||||
bool m_config_audio_stretch;
|
||||
bool m_audio_fill_gaps = true;
|
||||
|
||||
Config::ConfigChangedCallbackID m_config_changed_callback_id;
|
||||
};
|
||||
|
||||
@@ -293,7 +293,7 @@ void OpenALStream::SoundLoop()
|
||||
if (use_surround)
|
||||
{
|
||||
std::array<float, OAL_MAX_FRAMES * SURROUND_CHANNELS> dpl2;
|
||||
u32 rendered_frames = m_mixer->MixSurround(dpl2.data(), min_frames);
|
||||
u32 rendered_frames = static_cast<u32>(m_mixer->MixSurround(dpl2.data(), min_frames));
|
||||
|
||||
if (rendered_frames < min_frames)
|
||||
continue;
|
||||
@@ -351,7 +351,7 @@ void OpenALStream::SoundLoop()
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 rendered_frames = m_mixer->Mix(m_realtime_buffer.data(), min_frames);
|
||||
u32 rendered_frames = static_cast<u32>(m_mixer->Mix(m_realtime_buffer.data(), min_frames));
|
||||
|
||||
if (!rendered_frames)
|
||||
continue;
|
||||
|
||||
@@ -56,8 +56,7 @@ const Info<bool> MAIN_DPL2_DECODER{{System::Main, "Core", "DPL2Decoder"}, false}
|
||||
const Info<AudioCommon::DPL2Quality> MAIN_DPL2_QUALITY{{System::Main, "Core", "DPL2Quality"},
|
||||
AudioCommon::GetDefaultDPL2Quality()};
|
||||
const Info<int> MAIN_AUDIO_LATENCY{{System::Main, "Core", "AudioLatency"}, 20};
|
||||
const Info<bool> MAIN_AUDIO_STRETCH{{System::Main, "Core", "AudioStretch"}, false};
|
||||
const Info<int> MAIN_AUDIO_STRETCH_LATENCY{{System::Main, "Core", "AudioStretchMaxLatency"}, 80};
|
||||
const Info<bool> MAIN_AUDIO_FILL_GAPS{{System::Main, "Core", "AudioFillGaps"}, true};
|
||||
const Info<std::string> MAIN_MEMCARD_A_PATH{{System::Main, "Core", "MemcardAPath"}, ""};
|
||||
const Info<std::string> MAIN_MEMCARD_B_PATH{{System::Main, "Core", "MemcardBPath"}, ""};
|
||||
const Info<std::string>& GetInfoForMemcardPath(ExpansionInterface::Slot slot)
|
||||
|
||||
@@ -72,8 +72,7 @@ extern const Info<bool> MAIN_OVERRIDE_REGION_SETTINGS;
|
||||
extern const Info<bool> MAIN_DPL2_DECODER;
|
||||
extern const Info<AudioCommon::DPL2Quality> MAIN_DPL2_QUALITY;
|
||||
extern const Info<int> MAIN_AUDIO_LATENCY;
|
||||
extern const Info<bool> MAIN_AUDIO_STRETCH;
|
||||
extern const Info<int> MAIN_AUDIO_STRETCH_LATENCY;
|
||||
extern const Info<bool> MAIN_AUDIO_FILL_GAPS;
|
||||
extern const Info<std::string> MAIN_MEMCARD_A_PATH;
|
||||
extern const Info<std::string> MAIN_MEMCARD_B_PATH;
|
||||
const Info<std::string>& GetInfoForMemcardPath(ExpansionInterface::Slot slot);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AudioCommon\AudioCommon.h" />
|
||||
<ClInclude Include="AudioCommon\AudioStretcher.h" />
|
||||
<ClInclude Include="AudioCommon\CubebStream.h" />
|
||||
<ClInclude Include="AudioCommon\CubebUtils.h" />
|
||||
<ClInclude Include="AudioCommon\Enums.h" />
|
||||
@@ -769,7 +768,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AudioCommon\AudioCommon.cpp" />
|
||||
<ClCompile Include="AudioCommon\AudioStretcher.cpp" />
|
||||
<ClCompile Include="AudioCommon\CubebStream.cpp" />
|
||||
<ClCompile Include="AudioCommon\CubebUtils.cpp" />
|
||||
<ClCompile Include="AudioCommon\Mixer.cpp" />
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
<Import Project="$(ExternalsDir)rcheevos\exports.props" />
|
||||
<Import Project="$(ExternalsDir)SDL\exports.props" />
|
||||
<Import Project="$(ExternalsDir)SFML\exports.props" />
|
||||
<Import Project="$(ExternalsDir)soundtouch\exports.props" />
|
||||
<Import Project="$(ExternalsDir)spirv_cross\exports.props" />
|
||||
<Import Project="$(ExternalsDir)tinygltf\exports.props" />
|
||||
<Import Project="$(ExternalsDir)xxhash\exports.props" />
|
||||
|
||||
@@ -482,7 +482,6 @@
|
||||
<Import Project="$(ExternalsDir)picojson\exports.props" />
|
||||
<Import Project="$(ExternalsDir)rcheevos\exports.props" />
|
||||
<Import Project="$(ExternalsDir)SFML\exports.props" />
|
||||
<Import Project="$(ExternalsDir)soundtouch\exports.props" />
|
||||
<Import Project="$(ExternalsDir)xxhash\exports.props" />
|
||||
<Import Project="$(ExternalsDir)zstd\exports.props" />
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "Core/Core.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
|
||||
#include "DolphinQt/Config/SettingsWindow.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
@@ -137,43 +138,29 @@ void AudioPane::CreateWidgets()
|
||||
backend_layout->addRow(dolby_quality_layout);
|
||||
backend_layout->addRow(m_dolby_quality_latency_label);
|
||||
|
||||
auto* stretching_box = new QGroupBox(tr("Audio Stretching Settings"));
|
||||
auto* stretching_layout = new QGridLayout;
|
||||
m_stretching_enable = new QCheckBox(tr("Enable Audio Stretching"));
|
||||
m_stretching_buffer_slider = new QSlider(Qt::Horizontal);
|
||||
m_stretching_buffer_indicator = new QLabel();
|
||||
m_stretching_buffer_label = new QLabel(tr("Buffer Size:"));
|
||||
stretching_box->setLayout(stretching_layout);
|
||||
|
||||
m_stretching_buffer_slider->setMinimum(5);
|
||||
m_stretching_buffer_slider->setMaximum(300);
|
||||
|
||||
m_stretching_enable->setToolTip(tr("Enables stretching of the audio to match emulation speed."));
|
||||
m_stretching_buffer_slider->setToolTip(tr("Size of stretch buffer in milliseconds. "
|
||||
"Values too low may cause audio crackling."));
|
||||
|
||||
stretching_layout->addWidget(m_stretching_enable, 0, 0, 1, -1);
|
||||
stretching_layout->addWidget(m_stretching_buffer_label, 1, 0);
|
||||
stretching_layout->addWidget(m_stretching_buffer_slider, 1, 1);
|
||||
stretching_layout->addWidget(m_stretching_buffer_indicator, 1, 2);
|
||||
|
||||
dsp_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
auto* misc_box = new QGroupBox(tr("Miscellaneous Settings"));
|
||||
auto* misc_layout = new QGridLayout;
|
||||
misc_box->setLayout(misc_layout);
|
||||
|
||||
m_speed_up_mute_enable = new QCheckBox(tr("Mute When Disabling Speed Limit"));
|
||||
m_speed_up_mute_enable->setToolTip(
|
||||
m_audio_fill_gaps = new ConfigBool(tr("Fill Audio Gaps"), Config::MAIN_AUDIO_FILL_GAPS);
|
||||
m_audio_fill_gaps->SetDescription(
|
||||
tr("Repeat existing audio during lag spikes to prevent stuttering."
|
||||
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>"));
|
||||
|
||||
m_speed_up_mute_enable = new ConfigBool(tr("Mute When Disabling Speed Limit"),
|
||||
Config::MAIN_AUDIO_MUTE_ON_DISABLED_SPEED_LIMIT);
|
||||
m_speed_up_mute_enable->SetDescription(
|
||||
tr("Mutes the audio when overriding the emulation speed limit (default hotkey: Tab)."));
|
||||
|
||||
misc_layout->addWidget(m_speed_up_mute_enable, 0, 0, 1, 1);
|
||||
misc_layout->addWidget(m_audio_fill_gaps, 0, 0, 1, 1);
|
||||
misc_layout->addWidget(m_speed_up_mute_enable, 1, 0, 1, 1);
|
||||
|
||||
auto* const main_vbox_layout = new QVBoxLayout;
|
||||
|
||||
main_vbox_layout->addWidget(dsp_box);
|
||||
main_vbox_layout->addWidget(backend_box);
|
||||
main_vbox_layout->addWidget(stretching_box);
|
||||
main_vbox_layout->addWidget(misc_box);
|
||||
|
||||
m_main_layout = new QHBoxLayout;
|
||||
@@ -192,14 +179,11 @@ void AudioPane::ConnectWidgets()
|
||||
{
|
||||
connect(m_latency_spin, &QSpinBox::valueChanged, this, &AudioPane::SaveSettings);
|
||||
}
|
||||
connect(m_stretching_buffer_slider, &QSlider::valueChanged, this, &AudioPane::SaveSettings);
|
||||
connect(m_dolby_pro_logic, &QCheckBox::toggled, this, &AudioPane::SaveSettings);
|
||||
connect(m_dolby_quality_slider, &QSlider::valueChanged, this, &AudioPane::SaveSettings);
|
||||
connect(m_stretching_enable, &QCheckBox::toggled, this, &AudioPane::SaveSettings);
|
||||
connect(m_dsp_hle, &QRadioButton::toggled, this, &AudioPane::SaveSettings);
|
||||
connect(m_dsp_lle, &QRadioButton::toggled, this, &AudioPane::SaveSettings);
|
||||
connect(m_dsp_interpreter, &QRadioButton::toggled, this, &AudioPane::SaveSettings);
|
||||
connect(m_speed_up_mute_enable, &QCheckBox::toggled, this, &AudioPane::SaveSettings);
|
||||
|
||||
#ifdef _WIN32
|
||||
connect(m_wasapi_device_combo, &QComboBox::currentIndexChanged, this, &AudioPane::SaveSettings);
|
||||
@@ -255,17 +239,6 @@ void AudioPane::LoadSettings()
|
||||
if (m_latency_control_supported)
|
||||
m_latency_spin->setValue(Config::Get(Config::MAIN_AUDIO_LATENCY));
|
||||
|
||||
// Stretch
|
||||
m_stretching_enable->setChecked(Config::Get(Config::MAIN_AUDIO_STRETCH));
|
||||
m_stretching_buffer_label->setEnabled(m_stretching_enable->isChecked());
|
||||
m_stretching_buffer_slider->setValue(Config::Get(Config::MAIN_AUDIO_STRETCH_LATENCY));
|
||||
m_stretching_buffer_slider->setEnabled(m_stretching_enable->isChecked());
|
||||
m_stretching_buffer_indicator->setEnabled(m_stretching_enable->isChecked());
|
||||
m_stretching_buffer_indicator->setText(tr("%1 ms").arg(m_stretching_buffer_slider->value()));
|
||||
|
||||
// Misc
|
||||
m_speed_up_mute_enable->setChecked(Config::Get(Config::MAIN_AUDIO_MUTE_ON_DISABLED_SPEED_LIMIT));
|
||||
|
||||
#ifdef _WIN32
|
||||
if (Config::Get(Config::MAIN_WASAPI_DEVICE) == "default")
|
||||
{
|
||||
@@ -326,16 +299,8 @@ void AudioPane::SaveSettings()
|
||||
if (m_latency_control_supported)
|
||||
Config::SetBaseOrCurrent(Config::MAIN_AUDIO_LATENCY, m_latency_spin->value());
|
||||
|
||||
// Stretch
|
||||
Config::SetBaseOrCurrent(Config::MAIN_AUDIO_STRETCH, m_stretching_enable->isChecked());
|
||||
Config::SetBaseOrCurrent(Config::MAIN_AUDIO_STRETCH_LATENCY, m_stretching_buffer_slider->value());
|
||||
m_stretching_buffer_label->setEnabled(m_stretching_enable->isChecked());
|
||||
m_stretching_buffer_slider->setEnabled(m_stretching_enable->isChecked());
|
||||
m_stretching_buffer_indicator->setEnabled(m_stretching_enable->isChecked());
|
||||
m_stretching_buffer_indicator->setText(
|
||||
tr("%1 ms").arg(Config::Get(Config::MAIN_AUDIO_STRETCH_LATENCY)));
|
||||
|
||||
// Misc
|
||||
Config::SetBaseOrCurrent(Config::MAIN_AUDIO_FILL_GAPS, m_audio_fill_gaps->isChecked());
|
||||
Config::SetBaseOrCurrent(Config::MAIN_AUDIO_MUTE_ON_DISABLED_SPEED_LIMIT,
|
||||
m_speed_up_mute_enable->isChecked());
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ class QRadioButton;
|
||||
class QSlider;
|
||||
class QSpinBox;
|
||||
class SettingsWindow;
|
||||
class ConfigBool;
|
||||
|
||||
class AudioPane final : public QWidget
|
||||
{
|
||||
@@ -71,12 +72,7 @@ private:
|
||||
QComboBox* m_wasapi_device_combo;
|
||||
#endif
|
||||
|
||||
// Audio Stretching
|
||||
QCheckBox* m_stretching_enable;
|
||||
QLabel* m_stretching_buffer_label;
|
||||
QSlider* m_stretching_buffer_slider;
|
||||
QLabel* m_stretching_buffer_indicator;
|
||||
|
||||
// Misc Settings
|
||||
QCheckBox* m_speed_up_mute_enable;
|
||||
ConfigBool* m_audio_fill_gaps;
|
||||
ConfigBool* m_speed_up_mute_enable;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user