Sound improvements (sync, etc.), added pause/resume/stop/reset in GUI

This commit is contained in:
Souryo 2014-06-23 13:52:53 -04:00
parent 62ddf5e8e5
commit b5b9a1ca53
13 changed files with 277 additions and 91 deletions

View file

@ -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;
}

View file

@ -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);
};

View file

@ -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) {

View file

@ -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;
}
}
}

View file

@ -28,6 +28,7 @@ class Console
wstring _romFilename;
bool _stop = false;
bool _reset = false;
public:
Console(wstring filename);

View file

@ -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;

View file

@ -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 } } };

Binary file not shown.

View file

@ -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;

View file

@ -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();
};
}

View file

@ -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(&currentPlayCursor, 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;
}
}
}

View file

@ -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;

Binary file not shown.