From 115f46e05f6aca4906bd8e1100c4076db9564eac Mon Sep 17 00:00:00 2001 From: Sour Date: Thu, 2 Aug 2018 22:09:55 -0400 Subject: [PATCH] Audio: Improved dynamic sample rate logic and added an option to disable it completely --- Core/Console.cpp | 5 +- Core/EmulationSettings.h | 2 + Core/SoundMixer.cpp | 47 +++++++++++++++---- Core/SoundMixer.h | 4 ++ GUI.NET/Config/AudioInfo.cs | 11 +++++ GUI.NET/Dependencies/resources.ca.xml | 1 + GUI.NET/Dependencies/resources.en.xml | 1 + GUI.NET/Dependencies/resources.es.xml | 1 + GUI.NET/Dependencies/resources.fr.xml | 1 + GUI.NET/Dependencies/resources.ja.xml | 1 + GUI.NET/Dependencies/resources.pt.xml | 1 + GUI.NET/Dependencies/resources.ru.xml | 1 + GUI.NET/Dependencies/resources.uk.xml | 1 + .../Forms/Config/frmAudioConfig.Designer.cs | 36 +++++++++----- GUI.NET/Forms/Config/frmAudioConfig.cs | 2 + GUI.NET/InteropEmu.cs | 2 + 16 files changed, 96 insertions(+), 21 deletions(-) diff --git a/Core/Console.cpp b/Core/Console.cpp index d8257220..335a31f8 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -1398,8 +1398,8 @@ void Console::DisplayDebugInformation(Timer &clockTimer, Timer &lastFrameTimer, int startFrame = _ppu->GetFrameCount(); - _debugHud->DrawRectangle(8, 8, 115, 40, 0x40000000, true, 1, startFrame); - _debugHud->DrawRectangle(8, 8, 115, 40, 0xFFFFFF, false, 1, startFrame); + _debugHud->DrawRectangle(8, 8, 115, 49, 0x40000000, true, 1, startFrame); + _debugHud->DrawRectangle(8, 8, 115, 49, 0xFFFFFF, false, 1, startFrame); _debugHud->DrawString(10, 10, "Audio Stats", 0xFFFFFF, 0xFF000000, 1, startFrame); _debugHud->DrawString(10, 21, "Latency: ", 0xFFFFFF, 0xFF000000, 1, startFrame); @@ -1411,6 +1411,7 @@ void Console::DisplayDebugInformation(Timer &clockTimer, Timer &lastFrameTimer, _debugHud->DrawString(10, 30, "Underruns: " + std::to_string(stats.BufferUnderrunEventCount), 0xFFFFFF, 0xFF000000, 1, startFrame); _debugHud->DrawString(10, 39, "Buffer Size: " + std::to_string(stats.BufferSize / 1024) + "kb", 0xFFFFFF, 0xFF000000, 1, startFrame); + _debugHud->DrawString(10, 48, "Rate: " + std::to_string((uint32_t)(_settings->GetSampleRate() * _soundMixer->GetRateAdjustment())) + "Hz", 0xFFFFFF, 0xFF000000, 1, startFrame); _debugHud->DrawRectangle(136, 8, 115, 58, 0x40000000, true, 1, startFrame); _debugHud->DrawRectangle(136, 8, 115, 58, 0xFFFFFF, false, 1, startFrame); diff --git a/Core/EmulationSettings.h b/Core/EmulationSettings.h index 08915611..24655093 100644 --- a/Core/EmulationSettings.h +++ b/Core/EmulationSettings.h @@ -19,6 +19,8 @@ enum EmulationFlags : uint64_t UseHdPacks = 0x20, HasFourScore = 0x40, + DisableDynamicSampleRate = 0x80, + PauseOnMovieEnd = 0x0100, PauseWhenInBackground = 0x0200, AllowBackgroundInput = 0x0400, diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index 97b24626..1f4d346d 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -420,26 +420,57 @@ void SoundMixer::ProcessEndOfFrame() } } +double SoundMixer::GetRateAdjustment() +{ + return _rateAdjustment; +} + double SoundMixer::GetTargetRateAdjustment() { bool isRecording = _waveRecorder || _console->GetVideoRenderer()->IsRecording(); - if(!isRecording) { + if(!isRecording && !_settings->CheckFlag(EmulationFlags::DisableDynamicSampleRate)) { //Don't deviate from selected sample rate while recording //TODO: Have 2 output streams (one for recording, one for the speakers) AudioStatistics stats = GetStatistics(); if(stats.AverageLatency > 0 && _settings->GetEmulationSpeed() == 100) { - int32_t requestedLatency = (int32_t)_settings->GetAudioLatency(); + //Try to stay within +/- 3ms of requested latency + constexpr int32_t maxGap = 3; + constexpr int32_t maxSubAdjustment = 3600; - //Try to stay within +/- 2ms of requested latency - if(stats.AverageLatency > requestedLatency + 2) { - return 0.995; - } else if(stats.AverageLatency < requestedLatency - 2) { - return 1.005; + int32_t requestedLatency = (int32_t)_settings->GetAudioLatency(); + double latencyGap = stats.AverageLatency - requestedLatency; + double adjustment = std::min(0.0025, (std::ceil((std::abs(latencyGap) - maxGap) * 8)) * 0.00003125); + + if(latencyGap < 0 && _underTarget < maxSubAdjustment) { + _underTarget++; + } else if(latencyGap > 0 && _underTarget > -maxSubAdjustment) { + _underTarget--; } + + //For every ~1 second spent under/over target latency, further adjust rate (GetTargetRate is called approx. 3x per frame) + //This should slowly get us closer to the actual output rate of the sound card + double subAdjustment = 0.00003125 * _underTarget / 180; + + if(adjustment > 0) { + if(latencyGap > maxGap) { + _rateAdjustment = 1 - adjustment + subAdjustment; + } else if(latencyGap < -maxGap) { + _rateAdjustment = 1 + adjustment + subAdjustment; + } + } else if(std::abs(latencyGap) < 1) { + //Restore normal rate once we get within +/- 1ms + _rateAdjustment = 1.0 + subAdjustment; + } + } else { + _underTarget = 0; + _rateAdjustment = 1.0; } + } else { + _underTarget = 0; + _rateAdjustment = 1.0; } - return 1.0; + return _rateAdjustment; } void SoundMixer::UpdateTargetSampleRate() diff --git a/Core/SoundMixer.h b/Core/SoundMixer.h index 90800bab..e82183d2 100644 --- a/Core/SoundMixer.h +++ b/Core/SoundMixer.h @@ -52,6 +52,9 @@ private: int16_t _previousOutputLeft = 0; int16_t _previousOutputRight = 0; + double _rateAdjustment = 1.0; + int32_t _underTarget = 0; + vector _timestamps; int16_t _channelOutput[MaxChannelCount][CycleLength]; int16_t _currentOutput[MaxChannelCount]; @@ -111,4 +114,5 @@ public: AudioStatistics GetStatistics(); void ProcessEndOfFrame(); + double GetRateAdjustment(); }; diff --git a/GUI.NET/Config/AudioInfo.cs b/GUI.NET/Config/AudioInfo.cs index 8a1d4a4f..460445d7 100644 --- a/GUI.NET/Config/AudioInfo.cs +++ b/GUI.NET/Config/AudioInfo.cs @@ -7,6 +7,8 @@ namespace Mesen.GUI.Config public string AudioDevice = ""; public bool EnableAudio = true; + public bool DisableDynamicSampleRate = false; + [MinMax(15, 300)] public UInt32 AudioLatency = 60; [MinMax(0, 100)] public UInt32 MasterVolume = 25; @@ -175,6 +177,7 @@ namespace Mesen.GUI.Config InteropEmu.SetFlag(EmulationFlags.ReduceSoundInBackground, audioInfo.ReduceSoundInBackground); InteropEmu.SetFlag(EmulationFlags.ReduceSoundInFastForward, audioInfo.ReduceSoundInFastForward); + InteropEmu.SetFlag(EmulationFlags.DisableDynamicSampleRate, audioInfo.DisableDynamicSampleRate); InteropEmu.SetFlag(EmulationFlags.SwapDutyCycles, audioInfo.SwapDutyCycles); InteropEmu.SetFlag(EmulationFlags.SilenceTriangleHighFreq, audioInfo.SilenceTriangleHighFreq); InteropEmu.SetFlag(EmulationFlags.ReduceDmcPopping, audioInfo.ReduceDmcPopping); @@ -204,4 +207,12 @@ namespace Mesen.GUI.Config TwinFamicom, TwinFamicom60 } + + public enum DynamicRateAdjustmentType + { + None = 0, + Low = 1, + Medium = 2, + High = 3 + } } diff --git a/GUI.NET/Dependencies/resources.ca.xml b/GUI.NET/Dependencies/resources.ca.xml index 4bf5c2d9..2af86d4b 100644 --- a/GUI.NET/Dependencies/resources.ca.xml +++ b/GUI.NET/Dependencies/resources.ca.xml @@ -123,6 +123,7 @@
General Avançat + Disable dynamic sample rate Inverteix els cicles dels canals quadrats (imita clons antics) Silencia les freqüències ultrasòniques al canal de triangle (redueix els "esclafits") Redueix els "esclafits" al canal DMC diff --git a/GUI.NET/Dependencies/resources.en.xml b/GUI.NET/Dependencies/resources.en.xml index 35065794..2690fe18 100644 --- a/GUI.NET/Dependencies/resources.en.xml +++ b/GUI.NET/Dependencies/resources.en.xml @@ -123,6 +123,7 @@ General Advanced + Disable dynamic sample rate Swap square channels duty cycles (Mimics old clones) Mute ultrasonic frequencies on triangle channel (reduces popping) Reduce popping sounds on the DMC channel diff --git a/GUI.NET/Dependencies/resources.es.xml b/GUI.NET/Dependencies/resources.es.xml index c4936b15..3756bec4 100644 --- a/GUI.NET/Dependencies/resources.es.xml +++ b/GUI.NET/Dependencies/resources.es.xml @@ -122,6 +122,7 @@ General Avanzado + Disable dynamic sample rate Invertir ciclos de canales cuadrados (imita viejos clones) Cortar el canal de triángulo a frecuencias ultrasónicas (reduce los "chasquidos") Reducir los "chasquidos" en el canal DMC diff --git a/GUI.NET/Dependencies/resources.fr.xml b/GUI.NET/Dependencies/resources.fr.xml index 57ca2458..c39b707f 100644 --- a/GUI.NET/Dependencies/resources.fr.xml +++ b/GUI.NET/Dependencies/resources.fr.xml @@ -123,6 +123,7 @@ Général Avancé + Désactiver le taux d'échantillonnage dynamique Inverser le rapport cyclique des canaux Square 1 et Square 2 Couper le canal triangle lors de fréquences ultrasoniques (réduit les "pops") Réduire l'intensité des "pops" du canal DMC diff --git a/GUI.NET/Dependencies/resources.ja.xml b/GUI.NET/Dependencies/resources.ja.xml index e0263f06..3aeede4b 100644 --- a/GUI.NET/Dependencies/resources.ja.xml +++ b/GUI.NET/Dependencies/resources.ja.xml @@ -124,6 +124,7 @@ 全般 詳細設定 + サンプルレートの自動調整を無効にする Squareチャンネルのデューティサイクルをスワップする Triangleチャンネルが超音波を出す場合、ミュートする (ポップノイズを軽減する) DMCチャンネルのポップノイズを軽減する diff --git a/GUI.NET/Dependencies/resources.pt.xml b/GUI.NET/Dependencies/resources.pt.xml index 69f364dd..ffa9458f 100644 --- a/GUI.NET/Dependencies/resources.pt.xml +++ b/GUI.NET/Dependencies/resources.pt.xml @@ -122,6 +122,7 @@ Geral Avançado + Disable dynamic sample rate Inverter ciclos de canais quadrados (Imita velhos clones) Cortar o canal de triângulo em frequências ultrassônicas (Reduz os "pops") Reduzir a intensidade dos "pops" no canal DMC diff --git a/GUI.NET/Dependencies/resources.ru.xml b/GUI.NET/Dependencies/resources.ru.xml index c125bc38..83be68cf 100644 --- a/GUI.NET/Dependencies/resources.ru.xml +++ b/GUI.NET/Dependencies/resources.ru.xml @@ -122,6 +122,7 @@ Общие Расширенные + Disable dynamic sample rate Перепутанная скважность (как на старых клонах) Заглушать ультразвуковые частоты на канале triangle (уменьшает "треск") Reduce popping sounds on the DMC channel diff --git a/GUI.NET/Dependencies/resources.uk.xml b/GUI.NET/Dependencies/resources.uk.xml index 635c907f..9855a975 100644 --- a/GUI.NET/Dependencies/resources.uk.xml +++ b/GUI.NET/Dependencies/resources.uk.xml @@ -122,6 +122,7 @@ Загальнi Розширені + Disable dynamic sample rate Переплутана шпаруватість (як на старих клонах) Заглушати ультразвукові частоти на каналі triangle (зменшує "трiск") Зменшити клацання на каналі DMC diff --git a/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs b/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs index 582c70aa..06f54653 100644 --- a/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs +++ b/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs @@ -136,6 +136,7 @@ namespace Mesen.GUI.Forms.Config this.chkSilenceTriangleHighFreq = new System.Windows.Forms.CheckBox(); this.chkSwapDutyCycles = new Mesen.GUI.Controls.ctrlRiskyOption(); this.chkReduceDmcPopping = new System.Windows.Forms.CheckBox(); + this.chkDisableDynamicSampleRate = new Mesen.GUI.Controls.ctrlRiskyOption(); this.baseConfigPanel.SuspendLayout(); this.grpVolume.SuspendLayout(); this.tableLayoutPanel1.SuspendLayout(); @@ -1739,18 +1740,20 @@ namespace Mesen.GUI.Forms.Config // this.tableLayoutPanel3.ColumnCount = 1; this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel3.Controls.Add(this.chkDisableNoiseModeFlag, 0, 3); + this.tableLayoutPanel3.Controls.Add(this.chkDisableDynamicSampleRate, 0, 2); + this.tableLayoutPanel3.Controls.Add(this.chkDisableNoiseModeFlag, 0, 4); this.tableLayoutPanel3.Controls.Add(this.chkSilenceTriangleHighFreq, 0, 0); - this.tableLayoutPanel3.Controls.Add(this.chkSwapDutyCycles, 0, 2); + this.tableLayoutPanel3.Controls.Add(this.chkSwapDutyCycles, 0, 3); this.tableLayoutPanel3.Controls.Add(this.chkReduceDmcPopping, 0, 1); this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel3.Location = new System.Drawing.Point(3, 3); this.tableLayoutPanel3.Name = "tableLayoutPanel3"; - this.tableLayoutPanel3.RowCount = 5; - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowCount = 6; + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F)); this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel3.Size = new System.Drawing.Size(463, 341); this.tableLayoutPanel3.TabIndex = 1; @@ -1759,9 +1762,9 @@ namespace Mesen.GUI.Forms.Config // this.chkDisableNoiseModeFlag.Checked = false; this.chkDisableNoiseModeFlag.Dock = System.Windows.Forms.DockStyle.Fill; - this.chkDisableNoiseModeFlag.Location = new System.Drawing.Point(0, 70); + this.chkDisableNoiseModeFlag.Location = new System.Drawing.Point(0, 96); this.chkDisableNoiseModeFlag.Name = "chkDisableNoiseModeFlag"; - this.chkDisableNoiseModeFlag.Size = new System.Drawing.Size(463, 23); + this.chkDisableNoiseModeFlag.Size = new System.Drawing.Size(463, 24); this.chkDisableNoiseModeFlag.TabIndex = 3; this.chkDisableNoiseModeFlag.Text = "Disable noise channel mode flag"; // @@ -1778,7 +1781,7 @@ namespace Mesen.GUI.Forms.Config // this.chkSwapDutyCycles.Checked = false; this.chkSwapDutyCycles.Dock = System.Windows.Forms.DockStyle.Fill; - this.chkSwapDutyCycles.Location = new System.Drawing.Point(0, 46); + this.chkSwapDutyCycles.Location = new System.Drawing.Point(0, 72); this.chkSwapDutyCycles.Name = "chkSwapDutyCycles"; this.chkSwapDutyCycles.Size = new System.Drawing.Size(463, 24); this.chkSwapDutyCycles.TabIndex = 0; @@ -1788,7 +1791,7 @@ namespace Mesen.GUI.Forms.Config // this.chkReduceDmcPopping.AutoSize = true; this.chkReduceDmcPopping.CheckAlign = System.Drawing.ContentAlignment.TopLeft; - this.chkReduceDmcPopping.Location = new System.Drawing.Point(3, 26); + this.chkReduceDmcPopping.Location = new System.Drawing.Point(3, 27); this.chkReduceDmcPopping.Name = "chkReduceDmcPopping"; this.chkReduceDmcPopping.Size = new System.Drawing.Size(243, 17); this.chkReduceDmcPopping.TabIndex = 2; @@ -1796,6 +1799,16 @@ namespace Mesen.GUI.Forms.Config this.chkReduceDmcPopping.TextAlign = System.Drawing.ContentAlignment.TopLeft; this.chkReduceDmcPopping.UseVisualStyleBackColor = true; // + // chkDisableDynamicSampleRate + // + this.chkDisableDynamicSampleRate.Checked = false; + this.chkDisableDynamicSampleRate.Dock = System.Windows.Forms.DockStyle.Fill; + this.chkDisableDynamicSampleRate.Location = new System.Drawing.Point(0, 48); + this.chkDisableDynamicSampleRate.Name = "chkDisableDynamicSampleRate"; + this.chkDisableDynamicSampleRate.Size = new System.Drawing.Size(463, 24); + this.chkDisableDynamicSampleRate.TabIndex = 4; + this.chkDisableDynamicSampleRate.Text = "Disable dynamic sample rate"; + // // frmAudioConfig // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -1964,5 +1977,6 @@ namespace Mesen.GUI.Forms.Config private System.Windows.Forms.TableLayoutPanel tableLayoutPanel8; private ctrlHorizontalTrackbar trkVolumeReduction; private System.Windows.Forms.CheckBox chkMuteSoundInBackground; + private ctrlRiskyOption chkDisableDynamicSampleRate; } } \ No newline at end of file diff --git a/GUI.NET/Forms/Config/frmAudioConfig.cs b/GUI.NET/Forms/Config/frmAudioConfig.cs index 8cd8dc43..2d51b7c4 100644 --- a/GUI.NET/Forms/Config/frmAudioConfig.cs +++ b/GUI.NET/Forms/Config/frmAudioConfig.cs @@ -60,6 +60,8 @@ namespace Mesen.GUI.Forms.Config AddBinding("SampleRate", cboSampleRate); AddBinding("AudioDevice", cboAudioDevice); + AddBinding("DisableDynamicSampleRate", chkDisableDynamicSampleRate); + AddBinding("EnableEqualizer", chkEnableEqualizer); //TODO: Uncomment when equalizer presets are implemented //AddBinding("EqualizerPreset", cboEqualizerPreset); diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 543a612f..6b9c1ecf 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -1512,6 +1512,8 @@ namespace Mesen.GUI UseHdPacks = 0x20, HasFourScore = 0x40, + DisableDynamicSampleRate = 0x80, + PauseOnMovieEnd = 0x0100, PauseWhenInBackground = 0x0200, AllowBackgroundInput = 0x0400,