Merge pull request #14307 from Sam-Belliveau/granular-audio-stretching

Add an option to preserve audio pitch when emulation speed changes
This commit is contained in:
JMC47
2026-01-30 14:51:08 -05:00
committed by GitHub
9 changed files with 30 additions and 3 deletions

View File

@@ -31,6 +31,7 @@ enum class BooleanSetting(
false
),
MAIN_AUDIO_FILL_GAPS(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "AudioFillGaps", true),
MAIN_AUDIO_PRESERVE_PITCH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "AudioPreservePitch", false),
MAIN_BBA_XLINK_CHAT_OSD(
Settings.FILE_DOLPHIN,
Settings.SECTION_INI_CORE,

View File

@@ -556,6 +556,14 @@ class SettingsFragmentPresenter(
R.string.audio_fill_gaps_description
)
)
sl.add(
SwitchSetting(
context,
BooleanSetting.MAIN_AUDIO_PRESERVE_PITCH,
R.string.audio_preserve_pitch,
R.string.audio_preserve_pitch_description
)
)
sl.add(
IntSliderSetting(
context,

View File

@@ -188,6 +188,8 @@
<string name="audio_buffer_size_description">Controls the number of audio samples buffered. Lower values reduce latency but may cause more crackling or stuttering. If unsure, set this to 80 ms.</string>
<string name="audio_fill_gaps">Fill Audio Gaps</string>
<string name="audio_fill_gaps_description">Repeat existing audio during lag spikes to prevent stuttering. If unsure, leave this checked.</string>
<string name="audio_preserve_pitch">Preserve Audio Pitch</string>
<string name="audio_preserve_pitch_description">Keeps audio at normal pitch when changing emulation speed. Without this, audio pitch changes proportionally with speed. If unsure, leave this unchecked.</string>
<string name="audio_volume">Audio Volume</string>
<!-- Path Settings -->

View File

@@ -71,7 +71,7 @@ void Mixer::MixerFifo::Mix(s16* samples, std::size_t num_samples)
static_cast<double>(FIXED_SAMPLE_RATE_DIVIDEND) / m_input_sample_rate_divisor;
const double emulation_speed = m_mixer->m_config_emulation_speed;
if (0 < emulation_speed && emulation_speed != 1.0)
if (!m_mixer->m_config_audio_preserve_pitch && 0 < emulation_speed && emulation_speed != 1.0)
in_sample_rate *= emulation_speed;
const double base = static_cast<double>(1 << GRANULE_FRAC_BITS);
@@ -430,6 +430,7 @@ void Mixer::StopLogDSPAudio()
void Mixer::RefreshConfig()
{
m_config_emulation_speed = Config::Get(Config::MAIN_EMULATION_SPEED);
m_config_audio_preserve_pitch = Config::Get(Config::MAIN_AUDIO_PRESERVE_PITCH);
m_config_fill_audio_gaps = Config::Get(Config::MAIN_AUDIO_FILL_GAPS);
m_config_audio_buffer_ms = Config::Get(Config::MAIN_AUDIO_BUFFER_SIZE);
}

View File

@@ -163,6 +163,7 @@ private:
bool m_log_dsp_audio = false;
float m_config_emulation_speed;
bool m_config_audio_preserve_pitch;
bool m_config_fill_audio_gaps;
int m_config_audio_buffer_ms;

View File

@@ -71,6 +71,7 @@ const Info<AudioCommon::DPL2Quality> MAIN_DPL2_QUALITY{{System::Main, "Core", "D
const Info<int> MAIN_AUDIO_LATENCY{{System::Main, "Core", "AudioLatency"}, 20};
const Info<int> MAIN_AUDIO_BUFFER_SIZE{{System::Main, "Core", "AudioBufferSize"}, 80};
const Info<bool> MAIN_AUDIO_FILL_GAPS{{System::Main, "Core", "AudioFillGaps"}, true};
const Info<bool> MAIN_AUDIO_PRESERVE_PITCH{{System::Main, "Core", "AudioPreservePitch"}, false};
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)

View File

@@ -79,6 +79,7 @@ extern const Info<AudioCommon::DPL2Quality> MAIN_DPL2_QUALITY;
extern const Info<int> MAIN_AUDIO_LATENCY;
extern const Info<int> MAIN_AUDIO_BUFFER_SIZE;
extern const Info<bool> MAIN_AUDIO_FILL_GAPS;
extern const Info<bool> MAIN_AUDIO_PRESERVE_PITCH;
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);

View File

@@ -181,6 +181,9 @@ void AudioPane::CreateWidgets()
m_audio_fill_gaps = new ConfigBool(tr("Fill Audio Gaps"), Config::MAIN_AUDIO_FILL_GAPS);
m_audio_preserve_pitch =
new ConfigBool(tr("Preserve Audio Pitch"), Config::MAIN_AUDIO_PRESERVE_PITCH);
m_speed_up_mute_enable = new ConfigBool(tr("Mute When Disabling Speed Limit"),
Config::MAIN_AUDIO_MUTE_ON_DISABLED_SPEED_LIMIT);
@@ -192,8 +195,9 @@ void AudioPane::CreateWidgets()
playback_layout->addLayout(buffer_layout, 0, 0);
playback_layout->addWidget(m_audio_fill_gaps, 1, 0);
playback_layout->addWidget(m_speed_up_mute_enable, 2, 0);
playback_layout->setRowStretch(3, 1);
playback_layout->addWidget(m_audio_preserve_pitch, 2, 0);
playback_layout->addWidget(m_speed_up_mute_enable, 3, 0);
playback_layout->setRowStretch(4, 1);
playback_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
auto* const main_vbox_layout = new QVBoxLayout;
@@ -317,6 +321,10 @@ void AudioPane::AddDescriptions()
static const char TR_FILL_AUDIO_GAPS_DESCRIPTION[] = QT_TR_NOOP(
"Repeat existing audio during lag spikes to prevent stuttering.<br><br><dolphin_emphasis>If "
"unsure, leave this checked.</dolphin_emphasis>");
static const char TR_PRESERVE_AUDIO_PITCH_DESCRIPTION[] = QT_TR_NOOP(
"Keeps audio at normal pitch when changing emulation speed. Without this, audio pitch "
"changes proportionally with speed.<br><br><dolphin_emphasis>If unsure, leave this "
"unchecked.</dolphin_emphasis>");
static const char TR_SPEED_UP_MUTE_DESCRIPTION[] =
QT_TR_NOOP("Mutes the audio when overriding the emulation speed limit (default hotkey: Tab). "
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
@@ -357,4 +365,7 @@ void AudioPane::AddDescriptions()
m_audio_fill_gaps->SetTitle(tr("Fill Audio Gaps"));
m_audio_fill_gaps->SetDescription(tr(TR_FILL_AUDIO_GAPS_DESCRIPTION));
m_audio_preserve_pitch->SetTitle(tr("Preserve Audio Pitch"));
m_audio_preserve_pitch->SetDescription(tr(TR_PRESERVE_AUDIO_PITCH_DESCRIPTION));
}

View File

@@ -66,5 +66,6 @@ private:
// Misc Settings
ConfigBool* m_audio_fill_gaps;
ConfigBool* m_audio_preserve_pitch;
ConfigBool* m_speed_up_mute_enable;
};