From f8a0277c2e5eb0977f7e649b89877fd022313757 Mon Sep 17 00:00:00 2001 From: Souryo Date: Thu, 14 Jan 2016 19:33:16 -0500 Subject: [PATCH] Audio: Added option to select sample rate --- Core/APU.cpp | 17 ---- Core/APU.h | 14 --- Core/Console.cpp | 7 +- Core/Debugger.cpp | 3 +- Core/EmulationSettings.cpp | 1 + Core/EmulationSettings.h | 12 +++ Core/IAudioDevice.h | 2 +- Core/SoundMixer.cpp | 49 +++++++--- Core/SoundMixer.h | 15 ++++ GUI.NET/Config/AudioInfo.cs | 2 + .../Forms/Config/frmAudioConfig.Designer.cs | 90 +++++++++++++------ GUI.NET/Forms/Config/frmAudioConfig.cs | 1 + GUI.NET/InteropEmu.cs | 1 + InteropDLL/ConsoleWrapper.cpp | 1 + Windows/SoundManager.cpp | 43 +++++---- Windows/SoundManager.h | 9 +- 16 files changed, 176 insertions(+), 91 deletions(-) diff --git a/Core/APU.cpp b/Core/APU.cpp index 2a408f49..88ffa788 100644 --- a/Core/APU.cpp +++ b/Core/APU.cpp @@ -10,7 +10,6 @@ #include "SoundMixer.h" APU* APU::Instance = nullptr; -IAudioDevice* APU::AudioDevice = nullptr; APU::APU(MemoryManager* memoryManager) { @@ -199,17 +198,6 @@ void APU::Exec() } } -void APU::StopAudio(bool clearBuffer) -{ - if(APU::AudioDevice) { - if(clearBuffer) { - APU::AudioDevice->Stop(); - } else { - APU::AudioDevice->Pause(); - } - } -} - void APU::Reset(bool softReset) { _currentCycle = 0; @@ -237,9 +225,4 @@ void APU::StreamState(bool saving) if(!saving) { SetNesModel(_nesModel, true); } -} - -IAudioDevice* APU::GetAudioDevice() -{ - return APU::AudioDevice; } \ No newline at end of file diff --git a/Core/APU.h b/Core/APU.h index dc57e566..e0949d70 100644 --- a/Core/APU.h +++ b/Core/APU.h @@ -18,7 +18,6 @@ enum class NesModel; class APU : public Snapshotable, public IMemoryHandler { private: - static IAudioDevice* AudioDevice; static APU* Instance; uint32_t _previousCycle; @@ -46,22 +45,11 @@ class APU : public Snapshotable, public IMemoryHandler protected: void StreamState(bool saving); - public: - static const uint32_t SampleRate = 44100; - static const uint32_t SamplesPerFrame = 44100 / 60; - static const uint32_t BitsPerSample = 16; - public: APU(MemoryManager* memoryManager); ~APU(); void Reset(bool softReset); - - static void RegisterAudioDevice(IAudioDevice *audioDevice) - { - APU::AudioDevice = audioDevice; - } - void SetNesModel(NesModel model, bool forceInit = false); uint8_t ReadRAM(uint16_t addr); @@ -71,7 +59,5 @@ class APU : public Snapshotable, public IMemoryHandler void Exec(); static void ExecStatic(); - static IAudioDevice* GetAudioDevice(); static void StaticRun(); - static void StopAudio(bool clearBuffer = false); }; \ No newline at end of file diff --git a/Core/Console.cpp b/Core/Console.cpp index 28b4715c..e6c8f721 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -9,6 +9,7 @@ #include "../Utilities/Timer.h" #include "../Utilities/FolderUtilities.h" #include "HdPpu.h" +#include "SoundMixer.h" shared_ptr Console::Instance(new Console()); @@ -151,7 +152,7 @@ void Console::ResetComponents(bool softReset) _cpu->Reset(softReset); _memoryManager->Reset(softReset); - _apu->StopAudio(true); + SoundMixer::StopAudio(true); if(softReset) { MessageManager::SendNotification(ConsoleNotificationType::GameReset); @@ -226,7 +227,7 @@ void Console::Run() _runLock.Release(); //Prevent audio from looping endlessly while game is paused - _apu->StopAudio(); + SoundMixer::StopAudio(); while(EmulationSettings::CheckFlag(EmulationFlags::Paused)) { //Sleep until emulation is resumed @@ -251,7 +252,7 @@ void Console::Run() } } } - _apu->StopAudio(true); + SoundMixer::StopAudio(); Movie::Stop(); VideoDecoder::GetInstance()->StopThread(); diff --git a/Core/Debugger.cpp b/Core/Debugger.cpp index 5c178586..c74abc24 100644 --- a/Core/Debugger.cpp +++ b/Core/Debugger.cpp @@ -7,6 +7,7 @@ #include "Disassembler.h" #include "VideoDecoder.h" #include "APU.h" +#include "SoundMixer.h" #include "CodeDataLogger.h" #include "ExpressionEvaluator.h" @@ -283,7 +284,7 @@ bool Debugger::SleepUntilResume() if(stepCount == 0) { //Break - APU::StopAudio(); + SoundMixer::StopAudio(); MessageManager::SendNotification(ConsoleNotificationType::CodeBreak); _stepOverAddr = -1; while(stepCount == 0) { diff --git a/Core/EmulationSettings.cpp b/Core/EmulationSettings.cpp index bf0b0623..80bc4c05 100644 --- a/Core/EmulationSettings.cpp +++ b/Core/EmulationSettings.cpp @@ -6,6 +6,7 @@ uint32_t EmulationSettings::_flags = EmulationFlags::LowLatency; uint32_t EmulationSettings::_audioLatency = 20000; double EmulationSettings::_channelVolume[5] = { 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }; double EmulationSettings::_masterVolume = 1.0; +uint32_t EmulationSettings::_sampleRate = 44100; NesModel EmulationSettings::_model = NesModel::Auto; diff --git a/Core/EmulationSettings.h b/Core/EmulationSettings.h index 2045c353..8b128f48 100644 --- a/Core/EmulationSettings.h +++ b/Core/EmulationSettings.h @@ -67,6 +67,8 @@ private: static uint32_t _audioLatency; static double _channelVolume[5]; static double _masterVolume; + + static uint32_t _sampleRate; static NesModel _model; @@ -113,6 +115,16 @@ public: _masterVolume = volume; } + static void SetSampleRate(uint32_t sampleRate) + { + _sampleRate = sampleRate; + } + + static uint32_t GetSampleRate() + { + return _sampleRate; + } + static void SetAudioLatency(uint32_t msLatency) { _audioLatency = msLatency; diff --git a/Core/IAudioDevice.h b/Core/IAudioDevice.h index b5cfa83f..151ee8ec 100644 --- a/Core/IAudioDevice.h +++ b/Core/IAudioDevice.h @@ -5,7 +5,7 @@ class IAudioDevice { public: - virtual void PlayBuffer(int16_t *soundBuffer, uint32_t bufferSize) = 0; + virtual void PlayBuffer(int16_t *soundBuffer, uint32_t bufferSize, uint32_t sampleRate) = 0; virtual void Stop() = 0; virtual void Pause() = 0; }; \ No newline at end of file diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index d9e3a66a..ffdfb618 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -3,11 +3,13 @@ #include "APU.h" #include "CPU.h" +IAudioDevice* SoundMixer::AudioDevice = nullptr; + SoundMixer::SoundMixer() { - _outputBuffer = new int16_t[APU::SamplesPerFrame]; - _blipBuf = blip_new(APU::SamplesPerFrame); - + _outputBuffer = new int16_t[SoundMixer::MaxSamplesPerFrame]; + _blipBuf = blip_new(SoundMixer::MaxSamplesPerFrame); + _sampleRate = EmulationSettings::GetSampleRate(); InitializeLookupTables(); Reset(); @@ -21,6 +23,22 @@ SoundMixer::~SoundMixer() blip_delete(_blipBuf); } +void SoundMixer::RegisterAudioDevice(IAudioDevice *audioDevice) +{ + SoundMixer::AudioDevice = audioDevice; +} + +void SoundMixer::StopAudio(bool clearBuffer) +{ + if(SoundMixer::AudioDevice) { + if(clearBuffer) { + SoundMixer::AudioDevice->Stop(); + } else { + SoundMixer::AudioDevice->Pause(); + } + } +} + void SoundMixer::Reset() { _previousOutput = 0; @@ -36,16 +54,27 @@ void SoundMixer::Reset() void SoundMixer::PlayAudioBuffer(uint32_t time) { EndFrame(time); - size_t sampleCount = blip_read_samples(_blipBuf, _outputBuffer, APU::SamplesPerFrame, 0); - IAudioDevice *audioDevice = APU::GetAudioDevice(); - if(audioDevice) { - audioDevice->PlayBuffer(_outputBuffer, (uint32_t)(sampleCount * APU::BitsPerSample / 8)); + size_t sampleCount = blip_read_samples(_blipBuf, _outputBuffer, SoundMixer::MaxSamplesPerFrame, 0); + if(SoundMixer::AudioDevice) { + SoundMixer::AudioDevice->PlayBuffer(_outputBuffer, (uint32_t)(sampleCount * SoundMixer::BitsPerSample / 8), _sampleRate); + } + + if(EmulationSettings::GetSampleRate() != _sampleRate) { + //Update sample rate for next frame if setting changed + _sampleRate = EmulationSettings::GetSampleRate(); + UpdateRates(); } } void SoundMixer::SetNesModel(NesModel model) { - blip_set_rates(_blipBuf, model == NesModel::NTSC ? CPU::ClockRateNtsc : CPU::ClockRatePal, APU::SampleRate); + _clockRate = model == NesModel::NTSC ? CPU::ClockRateNtsc : CPU::ClockRatePal; + UpdateRates(); +} + +void SoundMixer::UpdateRates() +{ + blip_set_rates(_blipBuf, _clockRate, _sampleRate); } void SoundMixer::InitializeLookupTables() @@ -89,7 +118,7 @@ void SoundMixer::EndFrame(uint32_t time) _currentOutput[4] += _channelOutput[4][time]; int16_t currentOutput = GetOutputVolume(); - blip_add_delta(_blipBuf, time, (currentOutput - _previousOutput) * masterVolume); + blip_add_delta(_blipBuf, time, (int)((currentOutput - _previousOutput) * masterVolume)); _previousOutput = currentOutput; } blip_end_frame(_blipBuf, time); @@ -101,4 +130,4 @@ void SoundMixer::EndFrame(uint32_t time) _timestamps.clear(); memset(_channelOutput, 0, sizeof(_channelOutput)); -} +} \ No newline at end of file diff --git a/Core/SoundMixer.h b/Core/SoundMixer.h index 94689fb3..aa289631 100644 --- a/Core/SoundMixer.h +++ b/Core/SoundMixer.h @@ -2,10 +2,15 @@ #include "stdafx.h" #include "EmulationSettings.h" #include "../BlipBuffer/blip_buf.h" +#include "IAudioDevice.h" class SoundMixer { private: + static IAudioDevice* AudioDevice; + static const uint32_t MaxSampleRate = 48000; + static const uint32_t MaxSamplesPerFrame = MaxSampleRate / 60; + int16_t _previousOutput = 0; vector _timestamps; @@ -19,11 +24,18 @@ private: int16_t *_outputBuffer; double _volumes[5]; + uint32_t _sampleRate; + uint32_t _clockRate; + void InitializeLookupTables(); int16_t GetOutputVolume(); void EndFrame(uint32_t time); + void UpdateRates(); + public: + static const uint32_t BitsPerSample = 16; + SoundMixer(); ~SoundMixer(); @@ -32,4 +44,7 @@ public: void PlayAudioBuffer(uint32_t cycle); void AddDelta(AudioChannel channel, uint32_t time, int8_t output); + + static void StopAudio(bool clearBuffer = false); + static void RegisterAudioDevice(IAudioDevice *audioDevice); }; diff --git a/GUI.NET/Config/AudioInfo.cs b/GUI.NET/Config/AudioInfo.cs index 6894b966..8ed3b7f2 100644 --- a/GUI.NET/Config/AudioInfo.cs +++ b/GUI.NET/Config/AudioInfo.cs @@ -19,6 +19,7 @@ namespace Mesen.GUI.Config public UInt32 TriangleVolume = 100; public UInt32 NoiseVolume = 100; public UInt32 DmcVolume = 100; + public UInt32 SampleRate = 44100; public AudioInfo() { @@ -43,6 +44,7 @@ namespace Mesen.GUI.Config InteropEmu.SetChannelVolume(2, ConvertVolume(audioInfo.TriangleVolume)); InteropEmu.SetChannelVolume(3, ConvertVolume(audioInfo.NoiseVolume)); InteropEmu.SetChannelVolume(4, ConvertVolume(audioInfo.DmcVolume)); + InteropEmu.SetSampleRate(audioInfo.SampleRate); } } } diff --git a/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs b/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs index e9d94c40..64e99670 100644 --- a/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs +++ b/GUI.NET/Forms/Config/frmAudioConfig.Designer.cs @@ -37,10 +37,12 @@ this.trkMaster = new Mesen.GUI.Controls.ctrlTrackbar(); this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); this.chkEnableAudio = new System.Windows.Forms.CheckBox(); + this.lblSampleRate = new System.Windows.Forms.Label(); this.flowLayoutPanel2 = new System.Windows.Forms.FlowLayoutPanel(); - this.lblAudioLatency = new System.Windows.Forms.Label(); this.nudLatency = new System.Windows.Forms.NumericUpDown(); this.lblLatencyMs = new System.Windows.Forms.Label(); + this.lblAudioLatency = new System.Windows.Forms.Label(); + this.cboSampleRate = new System.Windows.Forms.ComboBox(); this.btnReset = new System.Windows.Forms.Button(); this.baseConfigPanel.SuspendLayout(); this.grpVolume.SuspendLayout(); @@ -53,14 +55,15 @@ // baseConfigPanel // this.baseConfigPanel.Controls.Add(this.btnReset); - this.baseConfigPanel.Location = new System.Drawing.Point(0, 245); + this.baseConfigPanel.Location = new System.Drawing.Point(0, 267); this.baseConfigPanel.Size = new System.Drawing.Size(470, 29); this.baseConfigPanel.Controls.SetChildIndex(this.btnReset, 0); // // grpVolume // + this.tableLayoutPanel2.SetColumnSpan(this.grpVolume, 2); this.grpVolume.Controls.Add(this.tableLayoutPanel1); - this.grpVolume.Location = new System.Drawing.Point(3, 29); + this.grpVolume.Location = new System.Drawing.Point(3, 80); this.grpVolume.Name = "grpVolume"; this.grpVolume.Size = new System.Drawing.Size(462, 185); this.grpVolume.TabIndex = 2; @@ -96,6 +99,7 @@ this.trkDmcVol.Caption = "DMC"; this.trkDmcVol.Location = new System.Drawing.Point(381, 0); this.trkDmcVol.Margin = new System.Windows.Forms.Padding(0); + this.trkDmcVol.Maximum = 100; this.trkDmcVol.MaximumSize = new System.Drawing.Size(63, 160); this.trkDmcVol.MinimumSize = new System.Drawing.Size(63, 160); this.trkDmcVol.Name = "trkDmcVol"; @@ -110,6 +114,7 @@ this.trkNoiseVol.Caption = "Noise"; this.trkNoiseVol.Location = new System.Drawing.Point(306, 0); this.trkNoiseVol.Margin = new System.Windows.Forms.Padding(0); + this.trkNoiseVol.Maximum = 100; this.trkNoiseVol.MaximumSize = new System.Drawing.Size(63, 160); this.trkNoiseVol.MinimumSize = new System.Drawing.Size(63, 160); this.trkNoiseVol.Name = "trkNoiseVol"; @@ -124,6 +129,7 @@ this.trkTriangleVol.Caption = "Triangle"; this.trkTriangleVol.Location = new System.Drawing.Point(231, 0); this.trkTriangleVol.Margin = new System.Windows.Forms.Padding(0); + this.trkTriangleVol.Maximum = 100; this.trkTriangleVol.MaximumSize = new System.Drawing.Size(63, 160); this.trkTriangleVol.MinimumSize = new System.Drawing.Size(63, 160); this.trkTriangleVol.Name = "trkTriangleVol"; @@ -138,6 +144,7 @@ this.trkSquare2Vol.Caption = "Square 2"; this.trkSquare2Vol.Location = new System.Drawing.Point(156, 0); this.trkSquare2Vol.Margin = new System.Windows.Forms.Padding(0); + this.trkSquare2Vol.Maximum = 100; this.trkSquare2Vol.MaximumSize = new System.Drawing.Size(63, 160); this.trkSquare2Vol.MinimumSize = new System.Drawing.Size(63, 160); this.trkSquare2Vol.Name = "trkSquare2Vol"; @@ -152,6 +159,7 @@ this.trkSquare1Vol.Caption = "Square 1"; this.trkSquare1Vol.Location = new System.Drawing.Point(81, 0); this.trkSquare1Vol.Margin = new System.Windows.Forms.Padding(0); + this.trkSquare1Vol.Maximum = 100; this.trkSquare1Vol.MaximumSize = new System.Drawing.Size(63, 160); this.trkSquare1Vol.MinimumSize = new System.Drawing.Size(63, 160); this.trkSquare1Vol.Name = "trkSquare1Vol"; @@ -177,11 +185,15 @@ // // tableLayoutPanel2 // - this.tableLayoutPanel2.ColumnCount = 1; - this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel2.Controls.Add(this.grpVolume, 0, 1); + this.tableLayoutPanel2.ColumnCount = 2; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); this.tableLayoutPanel2.Controls.Add(this.chkEnableAudio, 0, 0); - this.tableLayoutPanel2.Controls.Add(this.flowLayoutPanel2, 0, 2); + this.tableLayoutPanel2.Controls.Add(this.lblSampleRate, 0, 1); + this.tableLayoutPanel2.Controls.Add(this.flowLayoutPanel2, 1, 2); + this.tableLayoutPanel2.Controls.Add(this.lblAudioLatency, 0, 2); + this.tableLayoutPanel2.Controls.Add(this.cboSampleRate, 1, 1); + this.tableLayoutPanel2.Controls.Add(this.grpVolume, 0, 3); this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 0); this.tableLayoutPanel2.Name = "tableLayoutPanel2"; @@ -190,12 +202,14 @@ this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel2.Size = new System.Drawing.Size(470, 274); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(470, 296); this.tableLayoutPanel2.TabIndex = 3; // // chkEnableAudio // this.chkEnableAudio.AutoSize = true; + this.tableLayoutPanel2.SetColumnSpan(this.chkEnableAudio, 2); this.chkEnableAudio.Location = new System.Drawing.Point(6, 6); this.chkEnableAudio.Margin = new System.Windows.Forms.Padding(6, 6, 6, 3); this.chkEnableAudio.Name = "chkEnableAudio"; @@ -205,29 +219,29 @@ this.chkEnableAudio.UseVisualStyleBackColor = true; this.chkEnableAudio.CheckedChanged += new System.EventHandler(this.AudioConfig_ValueChanged); // + // lblSampleRate + // + this.lblSampleRate.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblSampleRate.AutoSize = true; + this.lblSampleRate.Location = new System.Drawing.Point(3, 33); + this.lblSampleRate.Name = "lblSampleRate"; + this.lblSampleRate.Size = new System.Drawing.Size(71, 13); + this.lblSampleRate.TabIndex = 0; + this.lblSampleRate.Text = "Sample Rate:"; + // // flowLayoutPanel2 // - this.flowLayoutPanel2.Controls.Add(this.lblAudioLatency); this.flowLayoutPanel2.Controls.Add(this.nudLatency); this.flowLayoutPanel2.Controls.Add(this.lblLatencyMs); - this.flowLayoutPanel2.Location = new System.Drawing.Point(3, 220); + this.flowLayoutPanel2.Location = new System.Drawing.Point(77, 53); + this.flowLayoutPanel2.Margin = new System.Windows.Forms.Padding(0); this.flowLayoutPanel2.Name = "flowLayoutPanel2"; - this.flowLayoutPanel2.Size = new System.Drawing.Size(200, 24); + this.flowLayoutPanel2.Size = new System.Drawing.Size(78, 24); this.flowLayoutPanel2.TabIndex = 4; // - // lblAudioLatency - // - this.lblAudioLatency.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.lblAudioLatency.AutoSize = true; - this.lblAudioLatency.Location = new System.Drawing.Point(3, 6); - this.lblAudioLatency.Name = "lblAudioLatency"; - this.lblAudioLatency.Size = new System.Drawing.Size(48, 13); - this.lblAudioLatency.TabIndex = 0; - this.lblAudioLatency.Text = "Latency:"; - // // nudLatency // - this.nudLatency.Location = new System.Drawing.Point(57, 3); + this.nudLatency.Location = new System.Drawing.Point(3, 3); this.nudLatency.Maximum = new decimal(new int[] { 300, 0, @@ -251,12 +265,36 @@ // this.lblLatencyMs.Anchor = System.Windows.Forms.AnchorStyles.Left; this.lblLatencyMs.AutoSize = true; - this.lblLatencyMs.Location = new System.Drawing.Point(108, 6); + this.lblLatencyMs.Location = new System.Drawing.Point(54, 6); this.lblLatencyMs.Name = "lblLatencyMs"; this.lblLatencyMs.Size = new System.Drawing.Size(20, 13); this.lblLatencyMs.TabIndex = 2; this.lblLatencyMs.Text = "ms"; // + // lblAudioLatency + // + this.lblAudioLatency.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblAudioLatency.AutoSize = true; + this.lblAudioLatency.Location = new System.Drawing.Point(3, 58); + this.lblAudioLatency.Name = "lblAudioLatency"; + this.lblAudioLatency.Size = new System.Drawing.Size(48, 13); + this.lblAudioLatency.TabIndex = 0; + this.lblAudioLatency.Text = "Latency:"; + // + // cboSampleRate + // + this.cboSampleRate.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cboSampleRate.FormattingEnabled = true; + this.cboSampleRate.Items.AddRange(new object[] { + "11,025 Hz", + "22,050 Hz", + "44,100 Hz", + "48,000 Hz"}); + this.cboSampleRate.Location = new System.Drawing.Point(80, 29); + this.cboSampleRate.Name = "cboSampleRate"; + this.cboSampleRate.Size = new System.Drawing.Size(75, 21); + this.cboSampleRate.TabIndex = 5; + // // btnReset // this.btnReset.AutoSize = true; @@ -272,7 +310,7 @@ // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(470, 274); + this.ClientSize = new System.Drawing.Size(470, 296); this.Controls.Add(this.tableLayoutPanel2); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; this.MaximizeBox = false; @@ -301,7 +339,6 @@ private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; private System.Windows.Forms.CheckBox chkEnableAudio; - private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel2; private System.Windows.Forms.Label lblAudioLatency; private System.Windows.Forms.NumericUpDown nudLatency; private System.Windows.Forms.Label lblLatencyMs; @@ -312,6 +349,9 @@ private Controls.ctrlTrackbar trkTriangleVol; private Controls.ctrlTrackbar trkSquare2Vol; private Controls.ctrlTrackbar trkSquare1Vol; + private System.Windows.Forms.Label lblSampleRate; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel2; + private System.Windows.Forms.ComboBox cboSampleRate; } } \ No newline at end of file diff --git a/GUI.NET/Forms/Config/frmAudioConfig.cs b/GUI.NET/Forms/Config/frmAudioConfig.cs index 5c544547..08c22202 100644 --- a/GUI.NET/Forms/Config/frmAudioConfig.cs +++ b/GUI.NET/Forms/Config/frmAudioConfig.cs @@ -27,6 +27,7 @@ namespace Mesen.GUI.Forms.Config AddBinding("NoiseVolume", trkNoiseVol); AddBinding("DmcVolume", trkDmcVol); AddBinding("AudioLatency", nudLatency); + AddBinding("SampleRate", cboSampleRate); } protected override void OnFormClosed(FormClosedEventArgs e) diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index a5461263..6ff74153 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -72,6 +72,7 @@ namespace Mesen.GUI [DllImport(DLLPath)] private static extern void ClearFlags(EmulationFlags flags); [DllImport(DLLPath)] public static extern void SetMasterVolume(double volume); [DllImport(DLLPath)] public static extern void SetChannelVolume(UInt32 channel, double volume); + [DllImport(DLLPath)] public static extern void SetSampleRate(UInt32 sampleRate); [DllImport(DLLPath)] public static extern void SetAudioLatency(UInt32 msLatency); [DllImport(DLLPath)] public static extern void SetNesModel(NesModel model); [DllImport(DLLPath)] public static extern void SetEmulationSpeed(UInt32 emulationSpeed); diff --git a/InteropDLL/ConsoleWrapper.cpp b/InteropDLL/ConsoleWrapper.cpp index ded817f2..8eb1ac66 100644 --- a/InteropDLL/ConsoleWrapper.cpp +++ b/InteropDLL/ConsoleWrapper.cpp @@ -205,6 +205,7 @@ namespace InteropEmu { DllExport void __stdcall ClearFlags(EmulationFlags flags) { EmulationSettings::ClearFlags(flags); } DllExport void __stdcall SetChannelVolume(uint32_t channel, double volume) { EmulationSettings::SetChannelVolume((AudioChannel)channel, volume); } DllExport void __stdcall SetMasterVolume(double volume) { EmulationSettings::SetMasterVolume(volume); } + DllExport void __stdcall SetSampleRate(uint32_t sampleRate) { EmulationSettings::SetSampleRate(sampleRate); } DllExport void __stdcall SetAudioLatency(uint32_t msLatency) { EmulationSettings::SetAudioLatency(msLatency); } DllExport void __stdcall SetNesModel(uint32_t model) { EmulationSettings::SetNesModel((NesModel)model); } DllExport void __stdcall SetOverscanDimensions(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom) { EmulationSettings::SetOverscanDimensions(left, right, top, bottom); } diff --git a/Windows/SoundManager.cpp b/Windows/SoundManager.cpp index c3d9db32..b7ac6044 100644 --- a/Windows/SoundManager.cpp +++ b/Windows/SoundManager.cpp @@ -1,16 +1,16 @@ #include "stdafx.h" #include "SoundManager.h" #include "../Core/EmulationSettings.h" +#include "../Core/SoundMixer.h" SoundManager::SoundManager(HWND hwnd) { - APU::RegisterAudioDevice(this); + SoundMixer::RegisterAudioDevice(this); + _hWnd = hwnd; _directSound = 0; _primaryBuffer = 0; _secondaryBuffer = 0; - - InitializeDirectSound(hwnd); } SoundManager::~SoundManager() @@ -18,13 +18,12 @@ SoundManager::~SoundManager() Release(); } -bool SoundManager::InitializeDirectSound(HWND hwnd) +bool SoundManager::InitializeDirectSound(uint32_t sampleRate) { HRESULT result; DSBUFFERDESC bufferDesc; WAVEFORMATEX waveFormat; - - + // Initialize the direct sound interface pointer for the default sound device. result = DirectSoundCreate8(NULL, &_directSound, NULL); if(FAILED(result)) { @@ -32,7 +31,7 @@ bool SoundManager::InitializeDirectSound(HWND hwnd) } // Set the cooperative level to priority so the format of the primary sound buffer can be modified. - result = _directSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY); + result = _directSound->SetCooperativeLevel(_hWnd, DSSCL_PRIORITY); if(FAILED(result)) { return false; } @@ -52,8 +51,10 @@ bool SoundManager::InitializeDirectSound(HWND hwnd) } // Setup the format of the primary sound bufffer. + _sampleRate = sampleRate; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; - waveFormat.nSamplesPerSec = 44100; + waveFormat.nSamplesPerSec = _sampleRate; waveFormat.wBitsPerSample = 16; waveFormat.nChannels = 1; waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; @@ -102,6 +103,8 @@ bool SoundManager::InitializeDirectSound(HWND hwnd) void SoundManager::Release() { + _lastWriteOffset = 0; + if(_secondaryBuffer) { _secondaryBuffer->Release(); _secondaryBuffer = nullptr; @@ -164,9 +167,14 @@ void SoundManager::Play() _secondaryBuffer->Play(0, 0, DSBPLAY_LOOPING); } -void SoundManager::PlayBuffer(int16_t *soundBuffer, uint32_t soundBufferSize) +void SoundManager::PlayBuffer(int16_t *soundBuffer, uint32_t soundBufferSize, uint32_t sampleRate) { - int32_t byteLatency = (int32_t)((float)(APU::SampleRate * EmulationSettings::GetAudioLatency()) / 1000.0f * (APU::BitsPerSample / 8)); + if(_sampleRate != sampleRate) { + Release(); + InitializeDirectSound(sampleRate); + } + + int32_t byteLatency = (int32_t)((float)(sampleRate * EmulationSettings::GetAudioLatency()) / 1000.0f * (SoundMixer::BitsPerSample / 8)); if(byteLatency != _previousLatency) { Stop(); _previousLatency = byteLatency; @@ -190,19 +198,20 @@ void SoundManager::PlayBuffer(int16_t *soundBuffer, uint32_t soundBufferSize) } int32_t latencyGap = playWriteByteLatency - byteLatency; - if(abs(latencyGap) > 3000) { + int32_t tolerance = byteLatency / 35; + if(abs(latencyGap) > byteLatency / 2) { //Out of sync, move back to where we should be (start of the latency buffer) - _secondaryBuffer->SetFrequency(44100); + _secondaryBuffer->SetFrequency(sampleRate); _secondaryBuffer->SetCurrentPosition(_lastWriteOffset - byteLatency); - } else if(latencyGap < -byteLatency/35) { + } else if(latencyGap < -tolerance) { //Playing too fast, slow down playing - _secondaryBuffer->SetFrequency(43900); - } else if(latencyGap > byteLatency/35) { + _secondaryBuffer->SetFrequency((DWORD)(sampleRate * 0.9975)); + } else if(latencyGap > tolerance) { //Playing too slow, speed up - _secondaryBuffer->SetFrequency(44300); + _secondaryBuffer->SetFrequency((DWORD)(sampleRate * 1.0025)); } else { //Normal playback - _secondaryBuffer->SetFrequency(44100); + _secondaryBuffer->SetFrequency(sampleRate); } } } \ No newline at end of file diff --git a/Windows/SoundManager.h b/Windows/SoundManager.h index fb495a3a..52b5f12c 100644 --- a/Windows/SoundManager.h +++ b/Windows/SoundManager.h @@ -1,7 +1,7 @@ #pragma once #include "stdafx.h" -#include "../Core/APU.h" +#include "../Core/IAudioDevice.h" class SoundManager : public IAudioDevice { @@ -10,20 +10,23 @@ public: ~SoundManager(); void Release(); - void PlayBuffer(int16_t *soundBuffer, uint32_t bufferSize); + void PlayBuffer(int16_t *soundBuffer, uint32_t bufferSize, uint32_t sampleRate); void Play(); void Pause(); void Stop(); private: - bool InitializeDirectSound(HWND); + bool InitializeDirectSound(uint32_t sampleRate); void ShutdownDirectSound(); void ClearSecondaryBuffer(); void CopyToSecondaryBuffer(uint8_t *data, uint32_t size); private: + HWND _hWnd; + uint16_t _lastWriteOffset = 0; uint16_t _previousLatency = 0; + uint32_t _sampleRate = 0; IDirectSound8* _directSound; IDirectSoundBuffer* _primaryBuffer;