diff --git a/Core/BaseSoundManager.cpp b/Core/BaseSoundManager.cpp new file mode 100644 index 00000000..dc959204 --- /dev/null +++ b/Core/BaseSoundManager.cpp @@ -0,0 +1,50 @@ +#include "stdafx.h" +#include "BaseSoundManager.h" + +void BaseSoundManager::ProcessLatency(uint32_t readPosition, uint32_t writePosition) +{ + //Record latency between read & write cursors once per frame + int32_t cursorGap; + if(writePosition < readPosition) { + cursorGap = writePosition - readPosition + _bufferSize; + } else { + cursorGap = writePosition - readPosition; + } + + _cursorGaps[_cursorGapIndex] = cursorGap; + _cursorGapIndex = (_cursorGapIndex + 1) % 60; + if(_cursorGapIndex == 0) { + _cursorGapFilled = true; + } + + if(_cursorGapFilled) { + //Once we have 60+ frames worth of data to work with, adjust playback frequency by +/- 0.5% + //To speed up or slow down playback in order to reach our latency goal. + uint32_t bytesPerSample = _isStereo ? 4 : 2; + + int32_t gapSum = 0; + for(int i = 0; i < 60; i++) { + gapSum += _cursorGaps[i]; + } + int32_t gapAverage = gapSum / 60; + + _averageLatency = (gapAverage / bytesPerSample) / (double)_sampleRate * 1000; + } +} + +AudioStatistics BaseSoundManager::GetStatistics() +{ + AudioStatistics stats; + stats.AverageLatency = _averageLatency; + stats.BufferUnderrunEventCount = _bufferUnderrunEventCount; + stats.BufferSize = _bufferSize; + return stats; +} + +void BaseSoundManager::ResetStats() +{ + _cursorGapIndex = 0; + _cursorGapFilled = false; + _bufferUnderrunEventCount = 0; + _averageLatency = 0; +} diff --git a/Core/BaseSoundManager.h b/Core/BaseSoundManager.h new file mode 100644 index 00000000..f02d289c --- /dev/null +++ b/Core/BaseSoundManager.h @@ -0,0 +1,23 @@ +#pragma once +#include "../Core/IAudioDevice.h" + +class BaseSoundManager : public IAudioDevice +{ +public: + void ProcessLatency(uint32_t readPosition, uint32_t writePosition); + AudioStatistics GetStatistics(); + +protected: + bool _isStereo; + uint32_t _sampleRate = 0; + + double _averageLatency = 0; + uint32_t _bufferSize = 0x10000; + uint32_t _bufferUnderrunEventCount = 0; + + int32_t _cursorGaps[60]; + int32_t _cursorGapIndex = 0; + bool _cursorGapFilled = false; + + void ResetStats(); +}; diff --git a/Core/BaseVideoFilter.cpp b/Core/BaseVideoFilter.cpp index 80be6372..b98476d8 100644 --- a/Core/BaseVideoFilter.cpp +++ b/Core/BaseVideoFilter.cpp @@ -60,9 +60,9 @@ void BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber) UpdateBufferSize(); OnBeforeApplyFilter(); ApplyFilter(ppuOutputBuffer); - if(DebugHud::GetInstance()) { - DebugHud::GetInstance()->Draw((uint32_t*)_outputBuffer, _overscan, GetFrameInfo().Width, frameNumber); - } + + DebugHud::GetInstance()->Draw((uint32_t*)_outputBuffer, _overscan, GetFrameInfo().Width, frameNumber); + _frameLock.Release(); } diff --git a/Core/Console.cpp b/Core/Console.cpp index 60bc2bb7..b15d85c5 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -38,6 +38,7 @@ #include "IBattery.h" #include "KeyManager.h" #include "BatteryManager.h" +#include "DebugHud.h" shared_ptr Console::Instance(new Console()); @@ -100,7 +101,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile) BatteryManager::Initialize(FolderUtilities::GetFilename(romFile.GetFileName(), false)); shared_ptr mapper = MapperFactory::InitializeFromFile(romFile.GetFileName(), fileData); if(mapper) { - SoundMixer::StopAudio(); + SoundMixer::StopAudio(true); if(_mapper) { //Send notification only if a game was already running and we successfully loaded the new one @@ -336,6 +337,8 @@ void Console::Reset(bool softReset) void Console::ResetComponents(bool softReset) { + SoundMixer::StopAudio(true); + _memoryManager->Reset(softReset); if(!EmulationSettings::CheckFlag(EmulationFlags::DisablePpuReset) || !softReset) { _ppu->Reset(); @@ -418,7 +421,13 @@ void Console::RunSingleFrame() void Console::Run() { Timer clockTimer; + Timer lastFrameTimer; double targetTime; + double timeLagData[16] = {}; + int timeLagDataIndex = 0; + double lastFrameMin = 9999; + double lastFrameMax = 0; + uint32_t lastFrameNumber = -1; _autoSaveManager.reset(new AutoSaveManager()); @@ -445,6 +454,14 @@ void Console::Run() uint32_t currentFrameNumber = PPU::GetFrameCount(); if(currentFrameNumber != lastFrameNumber) { + SoundMixer::ProcessEndOfFrame(); + + bool displayDebugInfo = EmulationSettings::CheckFlag(EmulationFlags::DisplayDebugInfo); + if(displayDebugInfo) { + DisplayDebugInformation(clockTimer, lastFrameTimer, lastFrameMin, lastFrameMax, timeLagData); + } + lastFrameTimer.Reset(); + _rewindManager->ProcessEndOfFrame(); EmulationSettings::DisableOverclocking(_disableOcNextFrame || NsfMapper::GetInstance()); _disableOcNextFrame = false; @@ -487,6 +504,7 @@ void Console::Run() PlatformUtilities::DisableScreensaver(); _runLock.Acquire(); MessageManager::SendNotification(ConsoleNotificationType::GameResumed); + lastFrameTimer.Reset(); } if(EmulationSettings::CheckFlag(EmulationFlags::UseHighResolutionTimer)) { @@ -499,6 +517,10 @@ void Console::Run() //Get next target time, and adjust based on whether we are ahead or behind double timeLag = EmulationSettings::GetEmulationSpeed() == 0 ? 0 : clockTimer.GetElapsedMS() - targetTime; + if(displayDebugInfo) { + timeLagData[timeLagDataIndex] = timeLag; + timeLagDataIndex = (timeLagDataIndex + 1) & 0x0F; + } UpdateNesModel(true); targetTime = GetFrameDelay(); @@ -938,4 +960,63 @@ uint8_t* Console::GetRamBuffer(DebugMemoryType memoryType, uint32_t &size, int32 } throw std::runtime_error("unsupported memory type"); +} + +void Console::DisplayDebugInformation(Timer &clockTimer, Timer &lastFrameTimer, double &lastFrameMin, double &lastFrameMax, double *timeLagData) +{ + AudioStatistics stats = SoundMixer::GetStatistics(); + DebugHud* hud = DebugHud::GetInstance(); + hud->DrawRectangle(8, 8, 115, 40, 0x40000000, true, 1); + hud->DrawRectangle(8, 8, 115, 40, 0xFFFFFF, false, 1); + + hud->DrawString(10, 10, "Audio Stats", 0xFFFFFF, 0xFF000000, 1); + hud->DrawString(10, 21, "Latency: ", 0xFFFFFF, 0xFF000000, 1); + + int color = (stats.AverageLatency > 0 && std::abs(stats.AverageLatency - EmulationSettings::GetAudioLatency()) > 3) ? 0xFF0000 : 0xFFFFFF; + std::stringstream ss; + ss << std::fixed << std::setprecision(2) << stats.AverageLatency << " ms"; + hud->DrawString(54, 21, ss.str(), color, 0xFF000000, 1); + + hud->DrawString(10, 30, "Underruns: " + std::to_string(stats.BufferUnderrunEventCount), 0xFFFFFF, 0xFF000000, 1); + hud->DrawString(10, 39, "Buffer Size: " + std::to_string(stats.BufferSize / 1024) + "kb", 0xFFFFFF, 0xFF000000, 1); + + hud->DrawRectangle(136, 8, 115, 58, 0x40000000, true, 1); + hud->DrawRectangle(136, 8, 115, 58, 0xFFFFFF, false, 1); + hud->DrawString(138, 10, "Video Stats", 0xFFFFFF, 0xFF000000, 1); + + ss = std::stringstream(); + ss << "Exec Time: " << std::fixed << std::setprecision(2) << clockTimer.GetElapsedMS() << " ms"; + hud->DrawString(138, 21, ss.str(), 0xFFFFFF, 0xFF000000, 1); + + double avgTimeLag = 0; + for(int i = 0; i < 16; i++) { + avgTimeLag += timeLagData[i]; + } + avgTimeLag /= 16; + + ss = std::stringstream(); + ss << "Time Gap: " << std::fixed << std::setprecision(2) << avgTimeLag << " ms"; + hud->DrawString(138, 30, ss.str(), 0xFFFFFF, 0xFF000000, 1); + + double lastFrame = lastFrameTimer.GetElapsedMS(); + + ss = std::stringstream(); + ss << "Last Frame: " << std::fixed << std::setprecision(2) << lastFrame << " ms"; + hud->DrawString(138, 39, ss.str(), 0xFFFFFF, 0xFF000000, 1); + + if(PPU::GetFrameCount() > 60) { + lastFrameMin = (std::min)(lastFrame, lastFrameMin); + lastFrameMax = (std::max)(lastFrame, lastFrameMax); + } else { + lastFrameMin = 9999; + lastFrameMax = 0; + } + + ss = std::stringstream(); + ss << "Min Delay: " << std::fixed << std::setprecision(2) << ((lastFrameMin < 9999) ? lastFrameMin : 0.0) << " ms"; + hud->DrawString(138, 48, ss.str(), 0xFFFFFF, 0xFF000000, 1); + + ss = std::stringstream(); + ss << "Max Delay: " << std::fixed << std::setprecision(2) << lastFrameMax << " ms"; + hud->DrawString(138, 57, ss.str(), 0xFFFFFF, 0xFF000000, 1); } \ No newline at end of file diff --git a/Core/Console.h b/Core/Console.h index 9f589c34..eb3aa9c5 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -19,6 +19,7 @@ class AutoSaveManager; class HdPackBuilder; class HdAudioDevice; class SystemActionManager; +class Timer; struct HdPackData; enum class NesModel; enum class ScaleFilterType; @@ -70,6 +71,7 @@ class Console bool Initialize(VirtualFile &romFile, VirtualFile &patchFile); void UpdateNesModel(bool sendNotification); double GetFrameDelay(); + void DisplayDebugInformation(Timer &clockTimer, Timer &lastFrameTimer, double &lastFrameMin, double &lastFrameMax, double *timeLagData); public: Console(); diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 754d7b4f..94f79764 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -515,6 +515,7 @@ + @@ -927,6 +928,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 7f061606..28bda578 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1369,6 +1369,9 @@ VideoDecoder + + Misc + @@ -1635,5 +1638,8 @@ VideoDecoder + + Misc + \ No newline at end of file diff --git a/Core/DebugHud.cpp b/Core/DebugHud.cpp index 8f00f9ef..3428e5c8 100644 --- a/Core/DebugHud.cpp +++ b/Core/DebugHud.cpp @@ -8,7 +8,7 @@ #include "DrawStringCommand.h" #include "DrawScreenBufferCommand.h" -DebugHud* DebugHud::_instance = nullptr; +DebugHud* DebugHud::_instance = new DebugHud(); DebugHud::DebugHud() { diff --git a/Core/Debugger.cpp b/Core/Debugger.cpp index 480abc92..612b4e9c 100644 --- a/Core/Debugger.cpp +++ b/Core/Debugger.cpp @@ -23,7 +23,6 @@ #include "RewindManager.h" #include "DebugBreakHelper.h" #include "ScriptHost.h" -#include "DebugHud.h" #include "StandardController.h" #ifndef UINT32_MAX @@ -52,7 +51,6 @@ Debugger::Debugger(shared_ptr console, shared_ptr cpu, shared_ptr< _memoryAccessCounter.reset(new MemoryAccessCounter(this)); _profiler.reset(new Profiler(this)); _traceLogger.reset(new TraceLogger(this, memoryManager, _labelManager)); - _debugHud.reset(new DebugHud()); _stepOut = false; _stepCount = -1; diff --git a/Core/Debugger.h b/Core/Debugger.h index b0e69f6c..22d6f943 100644 --- a/Core/Debugger.h +++ b/Core/Debugger.h @@ -28,7 +28,6 @@ class Profiler; class CodeRunner; class BaseMapper; class ScriptHost; -class DebugHud; class Debugger { @@ -49,7 +48,6 @@ private: shared_ptr _traceLogger; shared_ptr _profiler; unique_ptr _codeRunner; - unique_ptr _debugHud; shared_ptr _console; shared_ptr _cpu; diff --git a/Core/EmulationSettings.h b/Core/EmulationSettings.h index 3d8920bf..c8fed99a 100644 --- a/Core/EmulationSettings.h +++ b/Core/EmulationSettings.h @@ -83,6 +83,7 @@ enum EmulationFlags : uint64_t RandomizeMapperPowerOnState = 0x20000000000000, UseHighResolutionTimer = 0x40000000000000, + DisplayDebugInfo = 0x80000000000000, ForceMaxSpeed = 0x4000000000000000, ConsoleMode = 0x8000000000000000, @@ -449,6 +450,7 @@ enum class EmulatorShortcut ToggleAlwaysOnTop, ToggleSprites, ToggleBackground, + ToggleDebugInfo, LoadRandomGame, SaveStateSlot1, diff --git a/Core/IAudioDevice.h b/Core/IAudioDevice.h index 19dcf227..105a5111 100644 --- a/Core/IAudioDevice.h +++ b/Core/IAudioDevice.h @@ -2,6 +2,13 @@ #include "stdafx.h" +struct AudioStatistics +{ + double AverageLatency = 0; + uint32_t BufferUnderrunEventCount = 0; + uint32_t BufferSize = 0; +}; + class IAudioDevice { public: @@ -9,8 +16,10 @@ class IAudioDevice virtual void PlayBuffer(int16_t *soundBuffer, uint32_t bufferSize, uint32_t sampleRate, bool isStereo) = 0; virtual void Stop() = 0; virtual void Pause() = 0; + virtual void ProcessEndOfFrame() = 0; virtual string GetAvailableDevices() = 0; virtual void SetAudioDevice(string deviceName) = 0; + virtual AudioStatistics GetStatistics() = 0; }; \ No newline at end of file diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index c5886b46..5bcac40c 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -92,10 +92,13 @@ void SoundMixer::Reset() UpdateRates(true); UpdateEqualizers(true); + + _previousTargetRate = _sampleRate; } void SoundMixer::PlayAudioBuffer(uint32_t time) { + UpdateTargetSampleRate(); EndFrame(time); size_t sampleCount = blip_read_samples(_blipBufLeft, _outputBuffer, SoundMixer::MaxSamplesPerFrame, 1); @@ -191,10 +194,19 @@ void SoundMixer::UpdateRates(bool forceUpdate) } } + AudioStatistics stats = GetStatistics(); + int32_t requestedLatency = (int32_t)EmulationSettings::GetAudioLatency(); + double targetRate = _sampleRate; + if(stats.AverageLatency > requestedLatency + 2) { + targetRate *= 1.005; + } else if(stats.AverageLatency < requestedLatency - 2) { + targetRate *= 0.995; + } + if(_clockRate != newRate || forceUpdate) { _clockRate = newRate; - blip_set_rates(_blipBufLeft, _clockRate, _sampleRate); - blip_set_rates(_blipBufRight, _clockRate, _sampleRate); + blip_set_rates(_blipBufLeft, _clockRate, targetRate); + blip_set_rates(_blipBufRight, _clockRate, targetRate); if(_oggMixer) { _oggMixer->SetSampleRate(_sampleRate); } @@ -381,4 +393,42 @@ OggMixer* SoundMixer::GetOggMixer() _oggMixer.reset(new OggMixer()); } return _oggMixer.get(); +} + +AudioStatistics SoundMixer::GetStatistics() +{ + if(SoundMixer::AudioDevice) { + return SoundMixer::AudioDevice->GetStatistics(); + } else { + return AudioStatistics(); + } +} + +void SoundMixer::ProcessEndOfFrame() +{ + if(SoundMixer::AudioDevice) { + SoundMixer::AudioDevice->ProcessEndOfFrame(); + } +} + +void SoundMixer::UpdateTargetSampleRate() +{ + AudioStatistics stats = GetStatistics(); + if(stats.AverageLatency > 0 && EmulationSettings::GetEmulationSpeed() == 100) { + int32_t requestedLatency = (int32_t)EmulationSettings::GetAudioLatency(); + double targetRate = _sampleRate; + + //Try to stay within +/- 2ms of requested latency + if(stats.AverageLatency > requestedLatency + 2) { + targetRate *= 0.995; + } else if(stats.AverageLatency < requestedLatency - 2) { + targetRate *= 1.005; + } + + if(targetRate != _previousTargetRate) { + blip_set_rates(_blipBufLeft, _clockRate, targetRate); + blip_set_rates(_blipBufRight, _clockRate, targetRate); + _previousTargetRate = targetRate; + } + } } \ No newline at end of file diff --git a/Core/SoundMixer.h b/Core/SoundMixer.h index d4213295..16b42e0f 100644 --- a/Core/SoundMixer.h +++ b/Core/SoundMixer.h @@ -22,7 +22,7 @@ namespace orfanidis_eq { class SoundMixer : public Snapshotable { public: - static const uint32_t CycleLength = 1000; + static const uint32_t CycleLength = 10000; static const uint32_t BitsPerSample = 16; private: @@ -66,6 +66,8 @@ private: bool _hasPanning; + double _previousTargetRate; + double GetChannelOutput(AudioChannel channel, bool forRightChannel); int16_t GetOutputVolume(bool forRightChannel); void EndFrame(uint32_t time); @@ -74,6 +76,7 @@ private: void UpdateEqualizers(bool forceUpdate); void ApplyEqualizer(orfanidis_eq::eq1* equalizer, size_t sampleCount); + void UpdateTargetSampleRate(); protected: virtual void StreamState(bool saving) override; @@ -101,4 +104,7 @@ public: static void RegisterAudioDevice(IAudioDevice *audioDevice); static OggMixer* GetOggMixer(); + + static AudioStatistics GetStatistics(); + static void ProcessEndOfFrame(); }; diff --git a/GUI.NET/Config/AudioInfo.cs b/GUI.NET/Config/AudioInfo.cs index d869cfe2..9794a7dd 100644 --- a/GUI.NET/Config/AudioInfo.cs +++ b/GUI.NET/Config/AudioInfo.cs @@ -1,11 +1,4 @@ using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Mesen.GUI.Config { @@ -14,7 +7,7 @@ namespace Mesen.GUI.Config public string AudioDevice = ""; public bool EnableAudio = true; - [MinMax(15, 300)] public UInt32 AudioLatency = 50; + [MinMax(15, 300)] public UInt32 AudioLatency = 60; [MinMax(0, 100)] public UInt32 MasterVolume = 25; [MinMax(0, 100)] public UInt32 Square1Volume = 100; @@ -41,7 +34,7 @@ namespace Mesen.GUI.Config [MinMax(-100, 100)] public Int32 Namco163Panning = 0; [MinMax(-100, 100)] public Int32 Sunsoft5bPanning = 0; - [ValidValues(11025, 22050, 44100, 48000, 96000)] public UInt32 SampleRate = 44100; + [ValidValues(11025, 22050, 44100, 48000, 96000)] public UInt32 SampleRate = 48000; public bool ReduceSoundInBackground = true; public bool MuteSoundInBackground = false; public bool SwapDutyCycles = false; diff --git a/GUI.NET/Config/PreferenceInfo.cs b/GUI.NET/Config/PreferenceInfo.cs index f42aafd5..7be229ae 100644 --- a/GUI.NET/Config/PreferenceInfo.cs +++ b/GUI.NET/Config/PreferenceInfo.cs @@ -46,6 +46,8 @@ namespace Mesen.GUI.Config public bool NsfRepeat = false; public bool NsfShuffle = false; + public bool DisplayDebugInfo = false; + public bool PauseOnMovieEnd = true; public bool AutomaticallyCheckForUpdates = true; @@ -195,6 +197,8 @@ namespace Mesen.GUI.Config InteropEmu.SetFlag(EmulationFlags.NsfRepeat, preferenceInfo.NsfRepeat); InteropEmu.SetFlag(EmulationFlags.NsfShuffle, preferenceInfo.NsfShuffle); + InteropEmu.SetFlag(EmulationFlags.DisplayDebugInfo, preferenceInfo.DisplayDebugInfo); + InteropEmu.SetAutoSaveOptions(preferenceInfo.AutoSave ? (uint)preferenceInfo.AutoSaveDelay : 0, preferenceInfo.AutoSaveNotify); InteropEmu.ClearShortcutKeys(); diff --git a/GUI.NET/Dependencies/resources.ca.xml b/GUI.NET/Dependencies/resources.ca.xml index 1e800400..34ba16f7 100644 --- a/GUI.NET/Dependencies/resources.ca.xml +++ b/GUI.NET/Dependencies/resources.ca.xml @@ -806,6 +806,7 @@ Mida de vídeo 6x Activa/Desactiva el mode de pantalla completa Mostra/Amaga els FPS + Toggle Debug Information Mostra/Amaga el comptador de temps de joc Mostra/Amaga el comptador de fotogrames Mostra/Amaga el comptador de latència diff --git a/GUI.NET/Dependencies/resources.en.xml b/GUI.NET/Dependencies/resources.en.xml index b8611549..201d0227 100644 --- a/GUI.NET/Dependencies/resources.en.xml +++ b/GUI.NET/Dependencies/resources.en.xml @@ -839,6 +839,7 @@ Set Scale 6x Toggle Fullscreen Mode Toggle FPS Counter + Toggle Debug Information Toggle Game Timer Toggle Frame Counter Toggle Lag Counter diff --git a/GUI.NET/Dependencies/resources.es.xml b/GUI.NET/Dependencies/resources.es.xml index e71990ab..a0022034 100644 --- a/GUI.NET/Dependencies/resources.es.xml +++ b/GUI.NET/Dependencies/resources.es.xml @@ -823,6 +823,7 @@ Establecer escala 6x Alternar pantalla completa Alternar contador de FPS + Toggle Debug Information Alternar temporizador de juego Alternar contador de frames Alternar contador de lag diff --git a/GUI.NET/Dependencies/resources.fr.xml b/GUI.NET/Dependencies/resources.fr.xml index 52e4dd48..e12224f0 100644 --- a/GUI.NET/Dependencies/resources.fr.xml +++ b/GUI.NET/Dependencies/resources.fr.xml @@ -837,6 +837,7 @@ Taille de l'image 6x Activer/désactiver le mode plein écran Activer/désactiver le compteur FPS + Activer/désactiver l'info de debug Activer/désactiver le compteur de temps Activer/désactiver le compteur d'images Activer/désactiver le compteur de lag diff --git a/GUI.NET/Dependencies/resources.ja.xml b/GUI.NET/Dependencies/resources.ja.xml index 71132d09..a34dffc2 100644 --- a/GUI.NET/Dependencies/resources.ja.xml +++ b/GUI.NET/Dependencies/resources.ja.xml @@ -822,6 +822,7 @@ 映像サイズ 6倍 全画面表示 フレームレート表示 + デバッグ情報表示 ゲームタイマー表示 フレームカウンタ表示 ラグカウンタ表示 diff --git a/GUI.NET/Dependencies/resources.pt.xml b/GUI.NET/Dependencies/resources.pt.xml index 063e7497..19dcdcc9 100644 --- a/GUI.NET/Dependencies/resources.pt.xml +++ b/GUI.NET/Dependencies/resources.pt.xml @@ -823,6 +823,7 @@ Definir escala 6x Alternar tela cheia Alternar contador de quadros por segundo + Toggle Debug Information Alternar tempo de jogo Alternar contador de quadro Alternar contador de lagr diff --git a/GUI.NET/Dependencies/resources.ru.xml b/GUI.NET/Dependencies/resources.ru.xml index 180f2734..1e6dc413 100644 --- a/GUI.NET/Dependencies/resources.ru.xml +++ b/GUI.NET/Dependencies/resources.ru.xml @@ -825,6 +825,7 @@ Set Scale 6x Toggle Fullscreen Mode Toggle FPS Counter + Toggle Debug Information Toggle Game Timer Toggle Frame Counter Toggle Lag Counter diff --git a/GUI.NET/Dependencies/resources.uk.xml b/GUI.NET/Dependencies/resources.uk.xml index 85bd2cf4..5b0381c7 100644 --- a/GUI.NET/Dependencies/resources.uk.xml +++ b/GUI.NET/Dependencies/resources.uk.xml @@ -825,6 +825,7 @@ Встановити масштаб 6x Увімкнути/вимкнути повноекранний режим Увімкнути/вимкнути лічильник кадрiв + Toggle Debug Information Увімкнути/вимкнути таймер гри Увімкнути/вимкнути лічильник кадрів Увімкнути/вимкнути лічильник відставання diff --git a/GUI.NET/Forms/Config/ctrlEmulatorShortcuts.cs b/GUI.NET/Forms/Config/ctrlEmulatorShortcuts.cs index 7f2b2402..cbc1df6e 100644 --- a/GUI.NET/Forms/Config/ctrlEmulatorShortcuts.cs +++ b/GUI.NET/Forms/Config/ctrlEmulatorShortcuts.cs @@ -65,6 +65,7 @@ namespace Mesen.GUI.Forms.Config EmulatorShortcut.SetScale6x, EmulatorShortcut.ToggleFullscreen, + EmulatorShortcut.ToggleDebugInfo, EmulatorShortcut.ToggleFps, EmulatorShortcut.ToggleGameTimer, EmulatorShortcut.ToggleFrameCounter, diff --git a/GUI.NET/Forms/Config/frmAudioConfig.cs b/GUI.NET/Forms/Config/frmAudioConfig.cs index 07cbb755..f836452e 100644 --- a/GUI.NET/Forms/Config/frmAudioConfig.cs +++ b/GUI.NET/Forms/Config/frmAudioConfig.cs @@ -139,8 +139,8 @@ namespace Mesen.GUI.Forms.Config private void UpdateLatencyWarning() { - picLatencyWarning.Visible = nudLatency.Value <= 30; - lblLatencyWarning.Visible = nudLatency.Value <= 30; + picLatencyWarning.Visible = nudLatency.Value <= 55; + lblLatencyWarning.Visible = nudLatency.Value <= 55; } private void nudLatency_ValueChanged(object sender, EventArgs e) diff --git a/GUI.NET/Forms/frmMain.Help.cs b/GUI.NET/Forms/frmMain.Help.cs index 36e3ce98..368a08e6 100644 --- a/GUI.NET/Forms/frmMain.Help.cs +++ b/GUI.NET/Forms/frmMain.Help.cs @@ -90,6 +90,11 @@ namespace Mesen.GUI.Forms ConfigManager.Config.InputInfo.Controllers[0].Keys[0].SuborKeyboardButtons = presets.SuborKeyboard.SuborKeyboardButtons; ConfigManager.Config.InputInfo.Controllers[0].Keys[0].BandaiMicrophoneButtons = presets.BandaiMicrophone.BandaiMicrophoneButtons; } + + //Set the audio latency setting back to a sane default (since the way the code uses the value has changed) + if(ConfigManager.Config.AudioInfo.AudioLatency < 60) { + ConfigManager.Config.AudioInfo.AudioLatency = 60; + } } ConfigManager.Config.MesenVersion = InteropEmu.GetMesenVersion(); diff --git a/GUI.NET/Forms/frmMain.cs b/GUI.NET/Forms/frmMain.cs index 5a4aa20e..eb29acac 100644 --- a/GUI.NET/Forms/frmMain.cs +++ b/GUI.NET/Forms/frmMain.cs @@ -755,6 +755,7 @@ namespace Mesen.GUI.Forms case EmulatorShortcut.ToggleLagCounter: ToggleLagCounter(); break; case EmulatorShortcut.ToggleOsd: ToggleOsd(); break; case EmulatorShortcut.ToggleAlwaysOnTop: ToggleAlwaysOnTop(); break; + case EmulatorShortcut.ToggleDebugInfo: ToggleDebugInfo(); break; case EmulatorShortcut.MaxSpeed: ToggleMaxSpeed(); break; case EmulatorShortcut.ToggleFullscreen: ToggleFullscreen(); restoreFullscreen = false; break; @@ -893,6 +894,13 @@ namespace Mesen.GUI.Forms ConfigManager.ApplyChanges(); } + private void ToggleDebugInfo() + { + ConfigManager.Config.PreferenceInfo.DisplayDebugInfo = !ConfigManager.Config.PreferenceInfo.DisplayDebugInfo; + PreferenceInfo.ApplyConfig(); + ConfigManager.ApplyChanges(); + } + private void ToggleCheats() { ConfigManager.Config.DisableAllCheats = !ConfigManager.Config.DisableAllCheats; diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 9e44a0d8..5bd4fdc8 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -1533,6 +1533,7 @@ namespace Mesen.GUI RandomizeMapperPowerOnState = 0x20000000000000, UseHighResolutionTimer = 0x40000000000000, + DisplayDebugInfo = 0x80000000000000, ForceMaxSpeed = 0x4000000000000000, ConsoleMode = 0x8000000000000000, @@ -1749,6 +1750,7 @@ namespace Mesen.GUI ToggleAlwaysOnTop, ToggleSprites, ToggleBackground, + ToggleDebugInfo, LoadRandomGame, SaveStateSlot1, diff --git a/Linux/SdlSoundManager.cpp b/Linux/SdlSoundManager.cpp index 251ce1bb..5ddbf0e8 100755 --- a/Linux/SdlSoundManager.cpp +++ b/Linux/SdlSoundManager.cpp @@ -6,16 +6,13 @@ SdlSoundManager::SdlSoundManager() { if(InitializeAudio(44100, false)) { - _buffer = new uint8_t[0xFFFF]; SoundMixer::RegisterAudioDevice(this); } } SdlSoundManager::~SdlSoundManager() { - if(_buffer) { - delete[] _buffer; - } + Release(); } void SdlSoundManager::FillAudioBuffer(void *userData, uint8_t *stream, int len) @@ -25,6 +22,20 @@ void SdlSoundManager::FillAudioBuffer(void *userData, uint8_t *stream, int len) soundManager->ReadFromBuffer(stream, len); } +void SdlSoundManager::Release() +{ + if(_audioDeviceID != 0) { + Stop(); + SDL_CloseAudioDevice(_audioDeviceID); + } + + if(_buffer) { + delete[] _buffer; + _buffer = nullptr; + _bufferSize = 0; + } +} + bool SdlSoundManager::InitializeAudio(uint32_t sampleRate, bool isStereo) { if(SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { @@ -35,6 +46,12 @@ bool SdlSoundManager::InitializeAudio(uint32_t sampleRate, bool isStereo) _sampleRate = sampleRate; _isStereo = isStereo; + _previousLatency = EmulationSettings::GetAudioLatency(); + + int bytesPerSample = 2 * (isStereo ? 2 : 1); + int32_t requestedByteLatency = (int32_t)((float)(sampleRate * EmulationSettings::GetAudioLatency()) / 1000.0f * bytesPerSample); + _bufferSize = (int32_t)std::ceil((double)requestedByteLatency * 2 / 0x10000) * 0x10000; + _buffer = new uint8_t[_bufferSize]; SDL_AudioSpec audioSpec; SDL_memset(&audioSpec, 0, sizeof(audioSpec)); @@ -97,62 +114,50 @@ void SdlSoundManager::SetAudioDevice(string deviceName) void SdlSoundManager::ReadFromBuffer(uint8_t* output, uint32_t len) { - if(_readPosition + len < 65536) { + if(_readPosition + len < _bufferSize) { memcpy(output, _buffer+_readPosition, len); _readPosition += len; } else { - int remainingBytes = (65536 - _readPosition); + int remainingBytes = (_bufferSize - _readPosition); memcpy(output, _buffer+_readPosition, remainingBytes); memcpy(output+remainingBytes, _buffer, len - remainingBytes); _readPosition = len - remainingBytes; } + + if(_readPosition >= _writePosition && _readPosition - _writePosition < _bufferSize / 2) { + _bufferUnderrunEventCount++; + } } void SdlSoundManager::WriteToBuffer(uint8_t* input, uint32_t len) { - if(_writePosition + len < 65536) { + if(_writePosition + len < _bufferSize) { memcpy(_buffer+_writePosition, input, len); _writePosition += len; } else { - int remainingBytes = 65536 - _writePosition; + int remainingBytes = _bufferSize - _writePosition; memcpy(_buffer+_writePosition, input, remainingBytes); memcpy(_buffer, ((uint8_t*)input)+remainingBytes, len - remainingBytes); _writePosition = len - remainingBytes; } } - void SdlSoundManager::PlayBuffer(int16_t *soundBuffer, uint32_t sampleCount, uint32_t sampleRate, bool isStereo) { - uint32_t bytesPerSample = (SoundMixer::BitsPerSample / 8); - if(_sampleRate != sampleRate || _isStereo != isStereo || _needReset) { - Stop(); + uint32_t bytesPerSample = (SoundMixer::BitsPerSample / 8) * (isStereo ? 2 : 1); + uint32_t latency = EmulationSettings::GetAudioLatency(); + if(_sampleRate != sampleRate || _isStereo != isStereo || _needReset || _previousLatency != latency) { + Release(); InitializeAudio(sampleRate, isStereo); } - if(isStereo) { - bytesPerSample *= 2; - } - - int32_t byteLatency = (int32_t)((float)(sampleRate * EmulationSettings::GetAudioLatency()) / 1000.0f * bytesPerSample); - if(byteLatency != _previousLatency) { - Stop(); - _previousLatency = byteLatency; - } - WriteToBuffer((uint8_t*)soundBuffer, sampleCount * bytesPerSample); + int32_t byteLatency = (int32_t)((float)(sampleRate * latency) / 1000.0f * bytesPerSample); int32_t playWriteByteLatency = _writePosition - _readPosition; if(playWriteByteLatency < 0) { - playWriteByteLatency = 0xFFFF - _readPosition + _writePosition; + playWriteByteLatency = _bufferSize - _readPosition + _writePosition; } - if(playWriteByteLatency > byteLatency * 3) { - //Out of sync, resync - Stop(); - WriteToBuffer((uint8_t*)soundBuffer, sampleCount * bytesPerSample); - playWriteByteLatency = _writePosition - _readPosition; - } - if(playWriteByteLatency > byteLatency) { //Start playing SDL_PauseAudioDevice(_audioDeviceID, 0); @@ -167,6 +172,19 @@ void SdlSoundManager::Pause() void SdlSoundManager::Stop() { Pause(); + _readPosition = 0; _writePosition = 0; + ResetStats(); +} + +void SdlSoundManager::ProcessEndOfFrame() +{ + ProcessLatency(_readPosition, _writePosition); + + uint32_t emulationSpeed = EmulationSettings::GetEmulationSpeed(); + if(_averageLatency > 0 && emulationSpeed <= 100 && emulationSpeed > 0 && std::abs(_averageLatency - EmulationSettings::GetAudioLatency()) > 50) { + //Latency is way off (over 50ms gap), stop audio & start again + Stop(); + } } diff --git a/Linux/SdlSoundManager.h b/Linux/SdlSoundManager.h index 3feac7c3..c52dbe95 100755 --- a/Linux/SdlSoundManager.h +++ b/Linux/SdlSoundManager.h @@ -1,8 +1,8 @@ #pragma once #include -#include "../Core/IAudioDevice.h" +#include "../Core/BaseSoundManager.h" -class SdlSoundManager : public IAudioDevice +class SdlSoundManager : public BaseSoundManager { public: SdlSoundManager(); @@ -12,12 +12,15 @@ public: void Pause(); void Stop(); + void ProcessEndOfFrame(); + string GetAvailableDevices(); void SetAudioDevice(string deviceName); private: vector GetAvailableDeviceInfo(); bool InitializeAudio(uint32_t sampleRate, bool isStereo); + void Release(); static void FillAudioBuffer(void *userData, uint8_t *stream, int len); @@ -30,8 +33,6 @@ private: bool _needReset = false; uint16_t _previousLatency = 0; - uint32_t _sampleRate = 0; - bool _isStereo = false; uint8_t* _buffer = nullptr; uint32_t _writePosition = 0; diff --git a/Windows/SoundManager.cpp b/Windows/SoundManager.cpp index 2950b59d..53019d5b 100644 --- a/Windows/SoundManager.cpp +++ b/Windows/SoundManager.cpp @@ -123,10 +123,13 @@ bool SoundManager::InitializeDirectSound(uint32_t sampleRate, bool isStereo) return false; } + int32_t requestedByteLatency = (int32_t)((float)(sampleRate * EmulationSettings::GetAudioLatency()) / 1000.0f * waveFormat.nBlockAlign); + _bufferSize = (int32_t)std::ceil((double)requestedByteLatency * 2 / 0x10000) * 0x10000; + // Set the buffer description of the secondary sound buffer that the wave file will be loaded onto. bufferDesc.dwSize = sizeof(DSBUFFERDESC); bufferDesc.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY; - bufferDesc.dwBufferBytes = 0xFFFF; + bufferDesc.dwBufferBytes = _bufferSize; bufferDesc.dwReserved = 0; bufferDesc.lpwfxFormat = &waveFormat; bufferDesc.guid3DAlgorithm = GUID_NULL; @@ -161,7 +164,6 @@ bool SoundManager::InitializeDirectSound(uint32_t sampleRate, bool isStereo) return true; } - void SoundManager::Release() { _playing = false; @@ -204,7 +206,7 @@ void SoundManager::CopyToSecondaryBuffer(uint8_t *data, uint32_t size) DWORD bufferBSize; _secondaryBuffer->Lock(_lastWriteOffset, size, (void**)&bufferPtrA, (DWORD*)&bufferASize, (void**)&bufferPtrB, (DWORD*)&bufferBSize, 0); - _lastWriteOffset += size; + _lastWriteOffset = (_lastWriteOffset + size) % _bufferSize; memcpy(bufferPtrA, data, bufferASize); if(bufferPtrB && bufferBSize > 0) { @@ -228,7 +230,9 @@ void SoundManager::Stop() _secondaryBuffer->Stop(); ClearSecondaryBuffer(); } + _playing = false; + ResetStats(); } void SoundManager::Play() @@ -239,56 +243,62 @@ void SoundManager::Play() } } -void SoundManager::PlayBuffer(int16_t *soundBuffer, uint32_t sampleCount, uint32_t sampleRate, bool isStereo) +void SoundManager::ValidateWriteCursor(DWORD safeWriteCursor) { - uint32_t bytesPerSample = (SoundMixer::BitsPerSample / 8); - if(_sampleRate != sampleRate || _isStereo != isStereo || _needReset) { - Release(); - InitializeDirectSound(sampleRate, isStereo); - _secondaryBuffer->SetFrequency(sampleRate); - _emulationSpeed = 100; + int32_t writeGap = _lastWriteOffset - safeWriteCursor; + if(writeGap < -10000) { + writeGap += _bufferSize; + } else if(writeGap < 0) { + _bufferUnderrunEventCount++; + _lastWriteOffset = safeWriteCursor; } +} - uint32_t emulationSpeed = EmulationSettings::GetEmulationSpeed(); - if(emulationSpeed != _emulationSpeed) { - uint32_t targetRate = sampleRate; - if(emulationSpeed > 0 && emulationSpeed < 100) { - targetRate = (uint32_t)(targetRate * ((double)emulationSpeed / 100.0)); - } - _secondaryBuffer->SetFrequency(targetRate); - _emulationSpeed = emulationSpeed; - } - - if(isStereo) { - bytesPerSample *= 2; - } - - uint32_t soundBufferSize = sampleCount * bytesPerSample; - - int32_t byteLatency = (int32_t)((float)(sampleRate * EmulationSettings::GetAudioLatency()) / 1000.0f * bytesPerSample); +void SoundManager::ProcessEndOfFrame() +{ DWORD currentPlayCursor; DWORD safeWriteCursor; _secondaryBuffer->GetCurrentPosition(¤tPlayCursor, &safeWriteCursor); + ValidateWriteCursor(safeWriteCursor); - if(safeWriteCursor > _lastWriteOffset && safeWriteCursor - _lastWriteOffset < 10000) { - _lastWriteOffset = (uint16_t)safeWriteCursor; + uint32_t emulationSpeed = EmulationSettings::GetEmulationSpeed(); + uint32_t targetRate = _sampleRate; + if(emulationSpeed > 0 && emulationSpeed < 100) { + //Slow down playback when playing at less than 100% + targetRate = (uint32_t)(targetRate * ((double)emulationSpeed / 100.0)); } + _secondaryBuffer->SetFrequency((DWORD)(targetRate)); - int32_t playWriteByteLatency = (_lastWriteOffset - currentPlayCursor); - if(playWriteByteLatency < 0) { - playWriteByteLatency = 0xFFFF + playWriteByteLatency; - } + ProcessLatency(currentPlayCursor, _lastWriteOffset); - if(byteLatency != _previousLatency) { + if(_averageLatency > 0 && emulationSpeed <= 100 && emulationSpeed > 0 && std::abs(_averageLatency - EmulationSettings::GetAudioLatency()) > 50) { + //Latency is way off (over 50ms gap), stop audio & start again Stop(); - _previousLatency = byteLatency; - } else if(playWriteByteLatency > byteLatency * 3) { - _secondaryBuffer->SetCurrentPosition(_lastWriteOffset - byteLatency); + } +} + +void SoundManager::PlayBuffer(int16_t *soundBuffer, uint32_t sampleCount, uint32_t sampleRate, bool isStereo) +{ + uint32_t bytesPerSample = (SoundMixer::BitsPerSample / 8) * (isStereo ? 2 : 1); + uint32_t latency = EmulationSettings::GetAudioLatency(); + if(_sampleRate != sampleRate || _isStereo != isStereo || _needReset || latency != _previousLatency) { + _previousLatency = latency; + Release(); + InitializeDirectSound(sampleRate, isStereo); + _secondaryBuffer->SetFrequency(sampleRate); } - CopyToSecondaryBuffer((uint8_t*)soundBuffer, soundBufferSize); + DWORD currentPlayCursor, safeWriteCursor; + _secondaryBuffer->GetCurrentPosition(¤tPlayCursor, &safeWriteCursor); + ValidateWriteCursor(safeWriteCursor); - if(!_playing && _lastWriteOffset >= byteLatency) { - Play(); + uint32_t soundBufferSize = sampleCount * bytesPerSample; + CopyToSecondaryBuffer((uint8_t*)soundBuffer, soundBufferSize); + + if(!_playing) { + DWORD byteLatency = (int32_t)((float)(sampleRate * latency) / 1000.0f * bytesPerSample); + if(_lastWriteOffset >= byteLatency / 2) { + Play(); + } } } \ No newline at end of file diff --git a/Windows/SoundManager.h b/Windows/SoundManager.h index 4dafb35a..77b1b64e 100644 --- a/Windows/SoundManager.h +++ b/Windows/SoundManager.h @@ -1,7 +1,7 @@ #pragma once #include "stdafx.h" -#include "../Core/IAudioDevice.h" +#include "../Core/BaseSoundManager.h" struct SoundDeviceInfo { @@ -9,13 +9,14 @@ struct SoundDeviceInfo GUID guid; }; -class SoundManager : public IAudioDevice +class SoundManager : public BaseSoundManager { public: SoundManager(HWND hWnd); ~SoundManager(); void Release(); + void ProcessEndOfFrame(); void PlayBuffer(int16_t *soundBuffer, uint32_t bufferSize, uint32_t sampleRate, bool isStereo); void Play(); void Pause(); @@ -30,18 +31,16 @@ private: bool InitializeDirectSound(uint32_t sampleRate, bool isStereo); void ClearSecondaryBuffer(); void CopyToSecondaryBuffer(uint8_t *data, uint32_t size); + void ValidateWriteCursor(DWORD safeWriteCursor); private: HWND _hWnd; GUID _audioDeviceID; bool _needReset = false; - - uint16_t _lastWriteOffset = 0; - uint16_t _previousLatency = 0; - uint32_t _sampleRate = 0; - bool _isStereo = false; + + DWORD _lastWriteOffset = 0; + uint32_t _previousLatency = 0; bool _playing = false; - uint32_t _emulationSpeed = 100; IDirectSound8* _directSound; IDirectSoundBuffer* _primaryBuffer;