mirror of
https://github.com/SourMesen/Mesen.git
synced 2025-04-02 10:52:48 -04:00
Sound improvements (sync, etc.), added pause/resume/stop/reset in GUI
This commit is contained in:
parent
62ddf5e8e5
commit
b5b9a1ca53
13 changed files with 277 additions and 91 deletions
42
Core/APU.cpp
42
Core/APU.cpp
|
@ -4,8 +4,6 @@
|
|||
#include "APU.h"
|
||||
#include "CPU.h"
|
||||
|
||||
void output_samples(const blip_sample_t*, size_t count);
|
||||
|
||||
APU* APU::Instance = nullptr;
|
||||
IAudioDevice* APU::AudioDevice = nullptr;
|
||||
|
||||
|
@ -13,16 +11,24 @@ APU::APU()
|
|||
{
|
||||
APU::Instance = this;
|
||||
|
||||
blargg_err_t error = _buf.sample_rate(44100);
|
||||
if(error) {
|
||||
//report_error(error);
|
||||
}
|
||||
|
||||
_buf.clock_rate(1789773);
|
||||
_buf.sample_rate(APU::SampleRate);
|
||||
_buf.clock_rate(CPU::ClockRate);
|
||||
_apu.output(&_buf);
|
||||
|
||||
_apu.dmc_reader(&APU::DMCRead);
|
||||
//_apu.irq_notifier(irq_changed);
|
||||
|
||||
_outputBuffer = new int16_t[APU::SamplesPerFrame];
|
||||
}
|
||||
|
||||
APU::~APU()
|
||||
{
|
||||
delete[] _outputBuffer;
|
||||
}
|
||||
|
||||
void APU::Reset()
|
||||
{
|
||||
_apu.reset();
|
||||
}
|
||||
|
||||
int APU::DMCRead(void*, cpu_addr_t addr)
|
||||
|
@ -34,7 +40,7 @@ uint8_t APU::ReadRAM(uint16_t addr)
|
|||
{
|
||||
switch(addr) {
|
||||
case 0x4015:
|
||||
return _apu.read_status(0);
|
||||
return _apu.read_status(5);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -43,22 +49,22 @@ uint8_t APU::ReadRAM(uint16_t addr)
|
|||
|
||||
void APU::WriteRAM(uint16_t addr, uint8_t value)
|
||||
{
|
||||
_apu.write_register(0, addr, value);
|
||||
_apu.write_register(5, addr, value);
|
||||
}
|
||||
|
||||
void APU::Exec(uint32_t executedCycles)
|
||||
bool APU::Exec(uint32_t executedCycles)
|
||||
{
|
||||
_apu.end_frame(executedCycles);
|
||||
_buf.end_frame(executedCycles);
|
||||
|
||||
// Read some samples out of Blip_Buffer if there are enough to
|
||||
// fill our output buffer
|
||||
const size_t out_size = 4096;
|
||||
blip_sample_t out_buf[out_size];
|
||||
// Read some samples out of Blip_Buffer if there are enough to fill our output buffer
|
||||
uint32_t availableSampleCount = _buf.samples_avail();
|
||||
if(availableSampleCount >= APU::SamplesPerFrame) {
|
||||
size_t sampleCount = _buf.read_samples(_outputBuffer, APU::SamplesPerFrame);
|
||||
APU::AudioDevice->PlayBuffer(_outputBuffer, sampleCount * BitsPerSample / 8);
|
||||
|
||||
if(_buf.samples_avail() >= out_size) {
|
||||
size_t count = _buf.read_samples(out_buf, out_size);
|
||||
APU::AudioDevice->PlayBuffer(out_buf, count * sizeof(blip_sample_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
16
Core/APU.h
16
Core/APU.h
|
@ -7,16 +7,26 @@
|
|||
|
||||
class APU : public IMemoryHandler
|
||||
{
|
||||
Nes_Apu _apu;
|
||||
Blip_Buffer _buf;
|
||||
private:
|
||||
Nes_Apu _apu;
|
||||
Blip_Buffer _buf;
|
||||
int16_t* _outputBuffer;
|
||||
|
||||
private:
|
||||
static IAudioDevice* AudioDevice;
|
||||
static int DMCRead(void*, cpu_addr_t addr);
|
||||
static APU* Instance;
|
||||
|
||||
public:
|
||||
static const uint32_t SampleRate = 44100;
|
||||
static const uint32_t SamplesPerFrame = 44100 / 60;
|
||||
static const uint32_t BitsPerSample = 16;
|
||||
|
||||
public:
|
||||
APU();
|
||||
~APU();
|
||||
|
||||
void Reset();
|
||||
|
||||
vector<std::array<uint16_t, 2>> GetRAMAddresses()
|
||||
{
|
||||
|
@ -31,5 +41,5 @@ class APU : public IMemoryHandler
|
|||
uint8_t ReadRAM(uint16_t addr);
|
||||
void WriteRAM(uint16_t addr, uint8_t value);
|
||||
|
||||
void Exec(uint32_t executedCycles);
|
||||
bool Exec(uint32_t executedCycles);
|
||||
};
|
|
@ -620,6 +620,8 @@ private:
|
|||
#pragma endregion
|
||||
|
||||
public:
|
||||
static const uint32_t ClockRate = 1789773;
|
||||
|
||||
CPU(MemoryManager *memoryManager);
|
||||
static uint64_t GetCycleCount() { return CPU::CycleCount; }
|
||||
static void IncCycleCount(uint32_t cycles) {
|
||||
|
|
|
@ -30,7 +30,7 @@ Console::~Console()
|
|||
|
||||
void Console::Reset()
|
||||
{
|
||||
_cpu->Reset();
|
||||
_reset = true;
|
||||
}
|
||||
|
||||
void Console::Stop()
|
||||
|
@ -59,16 +59,13 @@ void Console::Run()
|
|||
Timer fpsTimer;
|
||||
uint32_t lastFrameCount = 0;
|
||||
double elapsedTime = 0;
|
||||
uint32_t cycleCount = 0;
|
||||
double targetTime = 16.6666666666666666;
|
||||
while(true) {
|
||||
uint32_t executedCycles = _cpu->Exec();
|
||||
_ppu->Exec();
|
||||
_apu->Exec(executedCycles);
|
||||
bool frameDone = _apu->Exec(executedCycles);
|
||||
|
||||
cycleCount += executedCycles;
|
||||
|
||||
if(CheckFlag(EmulationFlags::LimitFPS) && cycleCount >= 29780) {
|
||||
double targetTime = 16.638935108153078202995008319468;
|
||||
if(CheckFlag(EmulationFlags::LimitFPS) && frameDone) {
|
||||
elapsedTime = clockTimer.GetElapsedMS();
|
||||
while(targetTime > elapsedTime) {
|
||||
if(targetTime - elapsedTime > 2) {
|
||||
|
@ -76,7 +73,6 @@ void Console::Run()
|
|||
}
|
||||
elapsedTime = clockTimer.GetElapsedMS();
|
||||
}
|
||||
cycleCount = 0;
|
||||
clockTimer.Reset();
|
||||
}
|
||||
|
||||
|
@ -88,8 +84,20 @@ void Console::Run()
|
|||
}
|
||||
|
||||
if(_stop) {
|
||||
_stop = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(_reset) {
|
||||
clockTimer.Reset();
|
||||
fpsTimer.Reset();
|
||||
lastFrameCount = 0;
|
||||
elapsedTime = 0;
|
||||
_cpu->Reset();
|
||||
_ppu->Reset();
|
||||
_apu->Reset();
|
||||
_reset = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ class Console
|
|||
wstring _romFilename;
|
||||
|
||||
bool _stop = false;
|
||||
bool _reset = false;
|
||||
|
||||
public:
|
||||
Console(wstring filename);
|
||||
|
|
23
Core/PPU.cpp
23
Core/PPU.cpp
|
@ -23,13 +23,9 @@ uint32_t PPU_PALETTE_RGB[] = {
|
|||
PPU::PPU(MemoryManager *memoryManager)
|
||||
{
|
||||
_memoryManager = memoryManager;
|
||||
_state = {};
|
||||
_flags = {};
|
||||
_statusFlags = {};
|
||||
|
||||
memset(_spriteRAM, 0xFF, 0x100);
|
||||
|
||||
_outputBuffer = new uint8_t[256 * 240 * 4];
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
PPU::~PPU()
|
||||
|
@ -37,6 +33,21 @@ PPU::~PPU()
|
|||
delete[] _outputBuffer;
|
||||
}
|
||||
|
||||
void PPU::Reset()
|
||||
{
|
||||
_state = {};
|
||||
_flags = {};
|
||||
_statusFlags = {};
|
||||
|
||||
_scanline = 0;
|
||||
_cycle = 0;
|
||||
_frameCount = 0;
|
||||
_cycleCount = 0;
|
||||
_memoryReadBuffer = 0;
|
||||
|
||||
memset(_spriteRAM, 0xFF, 0x100);
|
||||
}
|
||||
|
||||
bool PPU::CheckFlag(PPUControlFlags flag)
|
||||
{
|
||||
return false;
|
||||
|
|
|
@ -160,6 +160,8 @@ class PPU : public IMemoryHandler
|
|||
PPU(MemoryManager *memoryManager);
|
||||
~PPU();
|
||||
|
||||
void Reset();
|
||||
|
||||
vector<std::array<uint16_t, 2>> GetRAMAddresses()
|
||||
{
|
||||
return{ { { 0x2000, 0x3FFF } }, { {0x4014, 0x4014 } } };
|
||||
|
|
BIN
GUI/GUI.rc
BIN
GUI/GUI.rc
Binary file not shown.
|
@ -7,8 +7,7 @@
|
|||
|
||||
using namespace DirectX;
|
||||
|
||||
namespace NES
|
||||
{
|
||||
namespace NES {
|
||||
MainWindow* MainWindow::Instance = nullptr;
|
||||
|
||||
bool MainWindow::Initialize()
|
||||
|
@ -69,15 +68,16 @@ namespace NES
|
|||
|
||||
Initialize();
|
||||
|
||||
InitializeOptions();
|
||||
InputManager inputManager;
|
||||
ControlManager::RegisterControlDevice(&inputManager, 0);
|
||||
|
||||
HACCEL hAccel = LoadAccelerators(_hInstance, MAKEINTRESOURCE(IDC_Accelerator));
|
||||
|
||||
HACCEL hAccel = LoadAccelerators(_hInstance, MAKEINTRESOURCE(IDC_Accelerator));
|
||||
if(hAccel == nullptr) {
|
||||
//error
|
||||
std::cout << "error";
|
||||
}
|
||||
|
||||
|
||||
MSG msg = { 0 };
|
||||
while(WM_QUIT != msg.message) {
|
||||
if(PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
|
@ -135,28 +135,31 @@ namespace NES
|
|||
INT_PTR CALLBACK MainWindow::About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(lParam);
|
||||
switch (message)
|
||||
{
|
||||
case WM_INITDIALOG:
|
||||
return (INT_PTR)TRUE;
|
||||
|
||||
case WM_COMMAND:
|
||||
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
|
||||
{
|
||||
EndDialog(hDlg, LOWORD(wParam));
|
||||
switch(message) {
|
||||
case WM_INITDIALOG:
|
||||
return (INT_PTR)TRUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_COMMAND:
|
||||
if(LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
|
||||
EndDialog(hDlg, LOWORD(wParam));
|
||||
return (INT_PTR)TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return (INT_PTR)FALSE;
|
||||
}
|
||||
|
||||
void MainWindow::OpenROM()
|
||||
void MainWindow::InitializeOptions()
|
||||
{
|
||||
Console::SetFlags(EmulationFlags::LimitFPS);
|
||||
}
|
||||
|
||||
wstring MainWindow::SelectROM()
|
||||
{
|
||||
wchar_t buffer[2000];
|
||||
|
||||
OPENFILENAME ofn;
|
||||
ZeroMemory(&ofn , sizeof(ofn));
|
||||
ZeroMemory(&ofn, sizeof(ofn));
|
||||
ofn.lStructSize = sizeof(ofn);
|
||||
ofn.hwndOwner = nullptr;
|
||||
ofn.lpstrFile = buffer;
|
||||
|
@ -165,38 +168,90 @@ namespace NES
|
|||
ofn.lpstrFilter = L"NES Roms\0*.NES\0All\0*.*";
|
||||
ofn.nFilterIndex = 1;
|
||||
ofn.lpstrFileTitle = nullptr;
|
||||
ofn.nMaxFileTitle = 0 ;
|
||||
ofn.lpstrInitialDir= nullptr;
|
||||
ofn.Flags = OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST ;
|
||||
ofn.nMaxFileTitle = 0;
|
||||
ofn.lpstrInitialDir = nullptr;
|
||||
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
|
||||
|
||||
GetOpenFileName(&ofn);
|
||||
|
||||
wstring filename = wstring(buffer);
|
||||
|
||||
if(filename.length() > 0) {
|
||||
Stop();
|
||||
return wstring(buffer);
|
||||
}
|
||||
|
||||
_console.reset(new Console(filename));
|
||||
void MainWindow::Start(wstring romFilename = L"")
|
||||
{
|
||||
if(_emuThread) {
|
||||
Stop(false);
|
||||
}
|
||||
|
||||
if(romFilename.length() > 0) {
|
||||
_currentROM = romFilename;
|
||||
_console.reset(new Console(_currentROM));
|
||||
}
|
||||
|
||||
if(!_console) {
|
||||
_console.reset(new Console(_currentROM));
|
||||
}
|
||||
|
||||
if(_console) {
|
||||
_emuThread.reset(new thread(&Console::Run, _console.get()));
|
||||
|
||||
SetMenuEnabled(ID_NES_PAUSE, true);
|
||||
SetMenuEnabled(ID_NES_RESET, true);
|
||||
SetMenuEnabled(ID_NES_STOP, true);
|
||||
SetMenuEnabled(ID_NES_RESUME, false);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::Stop()
|
||||
void MainWindow::Stop(bool powerOff)
|
||||
{
|
||||
_soundManager.Reset();
|
||||
if(_console) {
|
||||
_console->Stop();
|
||||
_emuThread->join();
|
||||
|
||||
_console.release();
|
||||
if(powerOff) {
|
||||
_console.release();
|
||||
}
|
||||
}
|
||||
if(_emuThread) {
|
||||
_emuThread->join();
|
||||
_emuThread.release();
|
||||
}
|
||||
|
||||
SetMenuEnabled(ID_NES_PAUSE, false);
|
||||
SetMenuEnabled(ID_NES_RESET, !powerOff);
|
||||
SetMenuEnabled(ID_NES_STOP, !powerOff);
|
||||
SetMenuEnabled(ID_NES_RESUME, true);
|
||||
}
|
||||
|
||||
void MainWindow::Reset()
|
||||
{
|
||||
if(_console) {
|
||||
_soundManager.Reset();
|
||||
_console->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::SetMenuEnabled(int resourceID, bool enabled)
|
||||
{
|
||||
HMENU hMenu = GetMenu(_hWnd);
|
||||
EnableMenuItem(hMenu, resourceID, enabled ? MF_ENABLED : MF_GRAYED);
|
||||
}
|
||||
|
||||
bool MainWindow::IsMenuChecked(int resourceID)
|
||||
{
|
||||
HMENU hMenu = GetMenu(_hWnd);
|
||||
return (GetMenuState(hMenu, resourceID, MF_BYCOMMAND) & MF_CHECKED) == MF_CHECKED;
|
||||
}
|
||||
|
||||
bool MainWindow::SetMenuCheck(int resourceID, bool checked)
|
||||
{
|
||||
HMENU hMenu = GetMenu(_hWnd);
|
||||
CheckMenuItem(hMenu, resourceID, MF_BYCOMMAND | (checked ? MF_CHECKED : MF_UNCHECKED));
|
||||
return checked;
|
||||
}
|
||||
|
||||
bool MainWindow::ToggleMenuCheck(int resourceID)
|
||||
{
|
||||
HMENU hMenu = GetMenu(_hWnd);
|
||||
bool checked = (GetMenuState(hMenu, resourceID, MF_BYCOMMAND) & MF_CHECKED) == MF_CHECKED;
|
||||
CheckMenuItem(hMenu, resourceID, MF_BYCOMMAND | (checked ? MF_UNCHECKED : MF_CHECKED));
|
||||
return !checked;
|
||||
return SetMenuCheck(resourceID, !IsMenuChecked(resourceID));
|
||||
}
|
||||
|
||||
void MainWindow::LimitFPS_Click()
|
||||
|
@ -235,7 +290,7 @@ namespace NES
|
|||
|
||||
void MainWindow::RunTests()
|
||||
{
|
||||
Stop();
|
||||
Stop(true);
|
||||
int passCount = 0;
|
||||
int failCount = 0;
|
||||
int totalCount = 0;
|
||||
|
@ -264,6 +319,7 @@ namespace NES
|
|||
}
|
||||
totalCount++;
|
||||
}
|
||||
Stop(true);
|
||||
|
||||
std::cout << "------------------------" << std::endl;
|
||||
std::cout << passCount << " / " << totalCount << " + " << failCount << " FAILED" << std::endl;
|
||||
|
@ -273,6 +329,7 @@ namespace NES
|
|||
LRESULT CALLBACK MainWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
static MainWindow *mainWindow = MainWindow::GetInstance();
|
||||
wstring filename;
|
||||
PAINTSTRUCT ps;
|
||||
int wmId, wmEvent;
|
||||
HDC hdc;
|
||||
|
@ -284,23 +341,43 @@ namespace NES
|
|||
// Parse the menu selections:
|
||||
switch (wmId) {
|
||||
case ID_FILE_OPEN:
|
||||
mainWindow->OpenROM();
|
||||
filename = mainWindow->SelectROM();
|
||||
if(filename.length() > 0) {
|
||||
mainWindow->Start(filename);
|
||||
}
|
||||
break;
|
||||
case ID_FILE_EXIT:
|
||||
DestroyWindow(hWnd);
|
||||
break;
|
||||
|
||||
case ID_NES_RESUME:
|
||||
mainWindow->Start();
|
||||
break;
|
||||
case ID_NES_PAUSE:
|
||||
mainWindow->Stop(false);
|
||||
break;
|
||||
case ID_NES_STOP:
|
||||
mainWindow->Stop(true);
|
||||
break;
|
||||
case ID_NES_RESET:
|
||||
mainWindow->Reset();
|
||||
break;
|
||||
|
||||
case ID_OPTIONS_LIMITFPS:
|
||||
mainWindow->LimitFPS_Click();
|
||||
break;
|
||||
|
||||
case ID_TESTS_RUNTESTS:
|
||||
mainWindow->RunTests();
|
||||
break;
|
||||
case ID_TESTS_SAVETESTRESULT:
|
||||
mainWindow->SaveTestResult();
|
||||
break;
|
||||
case ID_OPTIONS_LIMITFPS:
|
||||
mainWindow->LimitFPS_Click();
|
||||
break;
|
||||
|
||||
case ID_HELP_ABOUT:
|
||||
DialogBox(nullptr, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
|
||||
break;
|
||||
case ID_FILE_EXIT:
|
||||
DestroyWindow(hWnd);
|
||||
break;
|
||||
|
||||
default:
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
@ -328,7 +405,7 @@ namespace NES
|
|||
break;
|
||||
|
||||
case WM_DESTROY:
|
||||
mainWindow->Stop();
|
||||
mainWindow->Stop(true);
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
|
||||
|
|
|
@ -15,7 +15,9 @@ namespace NES {
|
|||
SoundManager _soundManager;
|
||||
unique_ptr<Console> _console;
|
||||
unique_ptr<thread> _emuThread;
|
||||
wstring _currentROM;
|
||||
|
||||
private:
|
||||
bool Initialize();
|
||||
HRESULT InitWindow();
|
||||
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
|
@ -30,15 +32,27 @@ namespace NES {
|
|||
|
||||
void LimitFPS_Click();
|
||||
|
||||
void SetMenuEnabled(int resourceID, bool enabled);
|
||||
|
||||
bool IsMenuChecked(int resourceID);
|
||||
bool SetMenuCheck(int resourceID, bool checked);
|
||||
bool ToggleMenuCheck(int resourceID);
|
||||
|
||||
wstring SelectROM();
|
||||
void Start(wstring romFilename);
|
||||
void Reset();
|
||||
void Stop(bool powerOff);
|
||||
|
||||
void InitializeOptions();
|
||||
|
||||
|
||||
|
||||
public:
|
||||
MainWindow(HINSTANCE hInstance, int nCmdShow) : _hInstance(hInstance), _nCmdShow(nCmdShow)
|
||||
{
|
||||
MainWindow::Instance = this;
|
||||
}
|
||||
|
||||
int Run();
|
||||
void OpenROM();
|
||||
void Stop();
|
||||
};
|
||||
}
|
|
@ -76,8 +76,8 @@ bool SoundManager::InitializeDirectSound(HWND hwnd)
|
|||
|
||||
// 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;
|
||||
bufferDesc.dwBufferBytes = 0xFFFFF;
|
||||
bufferDesc.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY;
|
||||
bufferDesc.dwBufferBytes = 0xFFFF;
|
||||
bufferDesc.dwReserved = 0;
|
||||
bufferDesc.lpwfxFormat = &waveFormat;
|
||||
bufferDesc.guid3DAlgorithm = GUID_NULL;
|
||||
|
@ -133,36 +133,86 @@ void SoundManager::ClearSecondaryBuffer()
|
|||
_secondaryBuffer->Lock(0, 0, (void**)&bufferPtr, (DWORD*)&bufferSize, nullptr, 0, DSBLOCK_ENTIREBUFFER);
|
||||
memset(bufferPtr, 0, bufferSize);
|
||||
_secondaryBuffer->Unlock((void*)bufferPtr, bufferSize, nullptr, 0);
|
||||
|
||||
_secondaryBuffer->SetCurrentPosition(0);
|
||||
_lastWriteOffset = 0;
|
||||
}
|
||||
|
||||
void SoundManager::CopyToSecondaryBuffer(uint8_t *data, uint32_t size)
|
||||
{
|
||||
unsigned char* bufferPtrA;
|
||||
unsigned char* bufferPtrB;
|
||||
uint8_t* bufferPtrA;
|
||||
uint8_t* bufferPtrB;
|
||||
DWORD bufferASize;
|
||||
DWORD bufferBSize;
|
||||
|
||||
_secondaryBuffer->Lock(0, size, (void**)&bufferPtrA, (DWORD*)&bufferASize, (void**)&bufferPtrB, (DWORD*)&bufferBSize, DSBLOCK_FROMWRITECURSOR);
|
||||
_secondaryBuffer->Lock(_lastWriteOffset, size, (void**)&bufferPtrA, (DWORD*)&bufferASize, (void**)&bufferPtrB, (DWORD*)&bufferBSize, 0);
|
||||
_lastWriteOffset += size;
|
||||
|
||||
memcpy(bufferPtrA, data, min(bufferASize, size));
|
||||
if(bufferPtrB) {
|
||||
memcpy(bufferPtrA, data, bufferASize);
|
||||
if(bufferPtrB && bufferBSize > 0) {
|
||||
memcpy(bufferPtrB, data + bufferASize, bufferBSize);
|
||||
}
|
||||
|
||||
_secondaryBuffer->Unlock((void*)bufferPtrA, bufferASize, (void*)bufferPtrB, bufferBSize);
|
||||
}
|
||||
|
||||
void SoundManager::Pause()
|
||||
{
|
||||
_secondaryBuffer->Stop();
|
||||
}
|
||||
|
||||
void SoundManager::Play()
|
||||
{
|
||||
_secondaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
|
||||
}
|
||||
|
||||
void SoundManager::Reset()
|
||||
{
|
||||
_secondaryBuffer->Stop();
|
||||
ClearSecondaryBuffer();
|
||||
}
|
||||
|
||||
void SoundManager::PlayBuffer(int16_t *soundBuffer, uint32_t soundBufferSize)
|
||||
{
|
||||
static int32_t byteLatency = _latency * (APU::BitsPerSample / 8);
|
||||
DWORD status;
|
||||
_secondaryBuffer->GetStatus(&status);
|
||||
|
||||
if(!(status & DSBSTATUS_PLAYING)) {
|
||||
ClearSecondaryBuffer();
|
||||
CopyToSecondaryBuffer((uint8_t*)soundBuffer, soundBufferSize);
|
||||
_secondaryBuffer->SetCurrentPosition(0);
|
||||
_secondaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
|
||||
if(_lastWriteOffset >= byteLatency) {
|
||||
Play();
|
||||
}
|
||||
} else {
|
||||
CopyToSecondaryBuffer((uint8_t*)soundBuffer, soundBufferSize);
|
||||
DWORD currentPlayCursor;
|
||||
_secondaryBuffer->GetCurrentPosition(¤tPlayCursor, nullptr);
|
||||
|
||||
int32_t playWriteByteLatency = (_lastWriteOffset - currentPlayCursor);
|
||||
if(playWriteByteLatency < -byteLatency * 2) {
|
||||
playWriteByteLatency = 0xFFFF - currentPlayCursor + _lastWriteOffset;
|
||||
}
|
||||
|
||||
int32_t latencyGap = playWriteByteLatency - byteLatency;
|
||||
if(abs(latencyGap) > 3000) {
|
||||
//Out of sync, move back to where we should be (start of the latency buffer)
|
||||
_secondaryBuffer->SetFrequency(44100);
|
||||
_secondaryBuffer->SetCurrentPosition(_lastWriteOffset - byteLatency);
|
||||
} else if(latencyGap < -200) {
|
||||
//Playing too fast, slow down playing
|
||||
_secondaryBuffer->SetFrequency(43900);
|
||||
} else if(latencyGap > 200) {
|
||||
//Playing too slow, speed up
|
||||
_secondaryBuffer->SetFrequency(44300);
|
||||
} else {
|
||||
//Normal playback
|
||||
_secondaryBuffer->SetFrequency(44100);
|
||||
}
|
||||
|
||||
static int counter = 0;
|
||||
counter++;
|
||||
if(counter % 5 == 0) {
|
||||
std::cout << latencyGap << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,9 @@ public:
|
|||
bool Initialize(HWND hWnd);
|
||||
void Release();
|
||||
void PlayBuffer(int16_t *soundBuffer, uint32_t bufferSize);
|
||||
void Play();
|
||||
void Pause();
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
bool InitializeDirectSound(HWND);
|
||||
|
@ -20,7 +23,9 @@ private:
|
|||
void CopyToSecondaryBuffer(uint8_t *data, uint32_t size);
|
||||
|
||||
private:
|
||||
vector<uint8_t> _buffer;
|
||||
uint16_t _lastWriteOffset = 0;
|
||||
const uint16_t _latency = APU::SampleRate / (1000 / 150); // == 150ms latency
|
||||
|
||||
IDirectSound8* _directSound;
|
||||
IDirectSoundBuffer* _primaryBuffer;
|
||||
IDirectSoundBuffer8* _secondaryBuffer;
|
||||
|
|
BIN
GUI/resource.h
BIN
GUI/resource.h
Binary file not shown.
Loading…
Add table
Reference in a new issue