mirror of
https://github.com/libretro/Mesen.git
synced 2025-01-31 13:52:19 +00:00
Audio: Improved audio/video sync and reduced static/glitches in output (on both Windows & Linux)
This commit is contained in:
parent
e2ef33d8e7
commit
335a133e0a
50
Core/BaseSoundManager.cpp
Normal file
50
Core/BaseSoundManager.cpp
Normal file
@ -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;
|
||||
}
|
23
Core/BaseSoundManager.h
Normal file
23
Core/BaseSoundManager.h
Normal file
@ -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();
|
||||
};
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include "IBattery.h"
|
||||
#include "KeyManager.h"
|
||||
#include "BatteryManager.h"
|
||||
#include "DebugHud.h"
|
||||
|
||||
shared_ptr<Console> Console::Instance(new Console());
|
||||
|
||||
@ -100,7 +101,7 @@ bool Console::Initialize(VirtualFile &romFile, VirtualFile &patchFile)
|
||||
BatteryManager::Initialize(FolderUtilities::GetFilename(romFile.GetFileName(), false));
|
||||
shared_ptr<BaseMapper> 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);
|
||||
}
|
@ -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();
|
||||
|
@ -515,6 +515,7 @@
|
||||
<ClInclude Include="BarcodeBattlerReader.h" />
|
||||
<ClInclude Include="BaseLoader.h" />
|
||||
<ClInclude Include="BaseRenderer.h" />
|
||||
<ClInclude Include="BaseSoundManager.h" />
|
||||
<ClInclude Include="BatteryManager.h" />
|
||||
<ClInclude Include="BattleBox.h" />
|
||||
<ClInclude Include="ControlDeviceState.h" />
|
||||
@ -927,6 +928,7 @@
|
||||
<ClCompile Include="Assembler.cpp" />
|
||||
<ClCompile Include="AutomaticRomTest.cpp" />
|
||||
<ClCompile Include="BaseRenderer.cpp" />
|
||||
<ClCompile Include="BaseSoundManager.cpp" />
|
||||
<ClCompile Include="BatteryManager.cpp" />
|
||||
<ClCompile Include="DebugHud.cpp" />
|
||||
<ClCompile Include="DrawRectangleCommand.h" />
|
||||
|
@ -1369,6 +1369,9 @@
|
||||
<ClInclude Include="RawVideoFilter.h">
|
||||
<Filter>VideoDecoder</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="BaseSoundManager.h">
|
||||
<Filter>Misc</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
@ -1635,5 +1638,8 @@
|
||||
<ClCompile Include="RawVideoFilter.cpp">
|
||||
<Filter>VideoDecoder</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="BaseSoundManager.cpp">
|
||||
<Filter>Misc</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -8,7 +8,7 @@
|
||||
#include "DrawStringCommand.h"
|
||||
#include "DrawScreenBufferCommand.h"
|
||||
|
||||
DebugHud* DebugHud::_instance = nullptr;
|
||||
DebugHud* DebugHud::_instance = new DebugHud();
|
||||
|
||||
DebugHud::DebugHud()
|
||||
{
|
||||
|
@ -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> console, shared_ptr<CPU> 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;
|
||||
|
@ -28,7 +28,6 @@ class Profiler;
|
||||
class CodeRunner;
|
||||
class BaseMapper;
|
||||
class ScriptHost;
|
||||
class DebugHud;
|
||||
|
||||
class Debugger
|
||||
{
|
||||
@ -49,7 +48,6 @@ private:
|
||||
shared_ptr<TraceLogger> _traceLogger;
|
||||
shared_ptr<Profiler> _profiler;
|
||||
unique_ptr<CodeRunner> _codeRunner;
|
||||
unique_ptr<DebugHud> _debugHud;
|
||||
|
||||
shared_ptr<Console> _console;
|
||||
shared_ptr<CPU> _cpu;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
};
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -806,6 +806,7 @@
|
||||
<Message ID="EmulatorShortcutMappings_SetScale6x">Mida de vídeo 6x</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Activa/Desactiva el mode de pantalla completa</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFps">Mostra/Amaga els FPS</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Mostra/Amaga el comptador de temps de joc</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Mostra/Amaga el comptador de fotogrames</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Mostra/Amaga el comptador de latència</Message>
|
||||
|
@ -839,6 +839,7 @@
|
||||
<Message ID="EmulatorShortcutMappings_SetScale6x">Set Scale 6x</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Toggle Fullscreen Mode</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFps">Toggle FPS Counter</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Toggle Game Timer</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Toggle Frame Counter</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Toggle Lag Counter</Message>
|
||||
|
@ -823,6 +823,7 @@
|
||||
<Message ID="EmulatorShortcutMappings_SetScale6x">Establecer escala 6x</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Alternar pantalla completa</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFps">Alternar contador de FPS</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Alternar temporizador de juego</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Alternar contador de frames</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Alternar contador de lag</Message>
|
||||
|
@ -837,6 +837,7 @@
|
||||
<Message ID="EmulatorShortcutMappings_SetScale6x">Taille de l'image 6x</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Activer/désactiver le mode plein écran</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFps">Activer/désactiver le compteur FPS</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Activer/désactiver l'info de debug</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Activer/désactiver le compteur de temps</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Activer/désactiver le compteur d'images</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Activer/désactiver le compteur de lag</Message>
|
||||
|
@ -822,6 +822,7 @@
|
||||
<Message ID="EmulatorShortcutMappings_SetScale6x">映像サイズ 6倍</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">全画面表示</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFps">フレームレート表示</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">デバッグ情報表示</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">ゲームタイマー表示</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">フレームカウンタ表示</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">ラグカウンタ表示</Message>
|
||||
|
@ -823,6 +823,7 @@
|
||||
<Message ID="EmulatorShortcutMappings_SetScale6x">Definir escala 6x</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Alternar tela cheia</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFps">Alternar contador de quadros por segundo</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Alternar tempo de jogo</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Alternar contador de quadro</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Alternar contador de lagr</Message>
|
||||
|
@ -825,6 +825,7 @@
|
||||
<Message ID="EmulatorShortcutMappings_SetScale6x">Set Scale 6x</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Toggle Fullscreen Mode</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFps">Toggle FPS Counter</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Toggle Game Timer</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Toggle Frame Counter</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Toggle Lag Counter</Message>
|
||||
|
@ -825,6 +825,7 @@
|
||||
<Message ID="EmulatorShortcutMappings_SetScale6x">Встановити масштаб 6x</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFullscreen">Увімкнути/вимкнути повноекранний режим</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFps">Увімкнути/вимкнути лічильник кадрiв</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleDebugInfo">Toggle Debug Information</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleGameTimer">Увімкнути/вимкнути таймер гри</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleFrameCounter">Увімкнути/вимкнути лічильник кадрів</Message>
|
||||
<Message ID="EmulatorShortcutMappings_ToggleLagCounter">Увімкнути/вимкнути лічильник відставання</Message>
|
||||
|
@ -65,6 +65,7 @@ namespace Mesen.GUI.Forms.Config
|
||||
EmulatorShortcut.SetScale6x,
|
||||
EmulatorShortcut.ToggleFullscreen,
|
||||
|
||||
EmulatorShortcut.ToggleDebugInfo,
|
||||
EmulatorShortcut.ToggleFps,
|
||||
EmulatorShortcut.ToggleGameTimer,
|
||||
EmulatorShortcut.ToggleFrameCounter,
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
#include <SDL2/SDL.h>
|
||||
#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<string> 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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user