From 175491f250011e9c08c203ebdecebd9116a71dcf Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Sat, 26 Dec 2020 19:28:51 +0000 Subject: [PATCH] libretro core: fix (most?) audio glitches. It was reading a number of bytes not multiple of "2 * channels". Signed-off-by: Andrea Odetti --- linux.md | 2 +- source/frontends/common2/speed.cpp | 10 ++-- source/frontends/common2/speed.h | 4 +- source/frontends/libretro/game.cpp | 39 ++++++------- source/frontends/libretro/game.h | 3 + source/frontends/libretro/libretro.cpp | 5 +- source/frontends/libretro/rdirectsound.cpp | 64 ++++++++++++++++------ source/frontends/sdl/emulator.cpp | 2 +- 8 files changed, 82 insertions(+), 47 deletions(-) diff --git a/linux.md b/linux.md index d4123b0e..2dc365e7 100644 --- a/linux.md +++ b/linux.md @@ -108,7 +108,7 @@ In order to have a better experience with the keyboard, one should probably enab Video works, but the vertical flip is done in software. -Audio (speaker) works with some glitches. +Audio (speaker) works. Must be manually configured: ``cmake -DLIBRETRO_PATH=/path/to/libretro-common`` diff --git a/source/frontends/common2/speed.cpp b/source/frontends/common2/speed.cpp index 2be698b0..17ba0c54 100644 --- a/source/frontends/common2/speed.cpp +++ b/source/frontends/common2/speed.cpp @@ -16,11 +16,11 @@ void Speed::reset() myStartCycles = g_nCumulativeCycles; } -size_t Speed::getCyclesTillNext(const size_t milliseconds) const +size_t Speed::getCyclesTillNext(const size_t microseconds) const { if (myFixedSpeed) { - const size_t cycles = static_cast(milliseconds * g_fCurrentCLK6502 * 1.0e-3); + const size_t cycles = static_cast(microseconds * g_fCurrentCLK6502 * 1.0e-6); return cycles; } else @@ -28,11 +28,11 @@ size_t Speed::getCyclesTillNext(const size_t milliseconds) const const uint64_t currentCycles = g_nCumulativeCycles; const auto currentTime = std::chrono::steady_clock::now(); - const auto currentDelta = std::chrono::duration_cast(currentTime - myStartTime).count(); + const auto currentDelta = std::chrono::duration_cast(currentTime - myStartTime).count(); // target the next time we will be called - const auto targetDeltaInMillis = currentDelta + milliseconds; + const auto targetDeltaInMicros = currentDelta + microseconds; - const uint64_t targetCycles = static_cast(targetDeltaInMillis * g_fCurrentCLK6502 * 1.0e-3) + myStartCycles; + const uint64_t targetCycles = static_cast(targetDeltaInMicros * g_fCurrentCLK6502 * 1.0e-6) + myStartCycles; if (targetCycles > currentCycles) { // number of cycles to fill this period diff --git a/source/frontends/common2/speed.h b/source/frontends/common2/speed.h index 081b7ab4..df3db64e 100644 --- a/source/frontends/common2/speed.h +++ b/source/frontends/common2/speed.h @@ -10,8 +10,8 @@ public: void reset(); // calculate the number of cycles to execute in the current period - // assuming the next call will happen in x milliseconds - size_t getCyclesTillNext(const size_t milliseconds) const; + // assuming the next call will happen in x microseconds + size_t getCyclesTillNext(const size_t microseconds) const; private: diff --git a/source/frontends/libretro/game.cpp b/source/frontends/libretro/game.cpp index 2a07fba4..ea0052ad 100644 --- a/source/frontends/libretro/game.cpp +++ b/source/frontends/libretro/game.cpp @@ -26,24 +26,6 @@ namespace { - void runOneFrame(Speed & speed) - { - if (g_nAppMode == MODE_RUNNING) - { - const size_t cyclesToExecute = speed.getCyclesTillNext(16); - - const bool bVideoUpdate = true; - const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame(); - - const DWORD executedCycles = CpuExecute(cyclesToExecute, bVideoUpdate); - - g_dwCyclesThisFrame = (g_dwCyclesThisFrame + executedCycles) % dwClksPerFrame; - GetCardMgr().GetDisk2CardMgr().UpdateDriveState(executedCycles); - MB_PeriodicUpdate(executedCycles); - SpkrUpdate(executedCycles); - } - } - void updateWindowTitle() { GetAppleWindowTitle(); @@ -88,9 +70,23 @@ Game::~Game() uninitialiseEmulator(); } +retro_usec_t Game::ourFrameTime = 0; + void Game::executeOneFrame() { - runOneFrame(mySpeed); + const size_t cyclesToExecute = mySpeed.getCyclesTillNext(ourFrameTime); + if (g_nAppMode == MODE_RUNNING) + { + const bool bVideoUpdate = true; + const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame(); + + const DWORD executedCycles = CpuExecute(cyclesToExecute, bVideoUpdate); + + g_dwCyclesThisFrame = (g_dwCyclesThisFrame + executedCycles) % dwClksPerFrame; + GetCardMgr().GetDisk2CardMgr().UpdateDriveState(executedCycles); + MB_PeriodicUpdate(executedCycles); + SpkrUpdate(executedCycles); + } } void Game::processInputEvents() @@ -111,6 +107,11 @@ void Game::keyboardCallback(bool down, unsigned keycode, uint32_t character, uin } } +void Game::frameTimeCallback(retro_usec_t usec) +{ + ourFrameTime = usec; +} + void Game::processKeyDown(unsigned keycode, uint32_t character, uint16_t key_modifiers) { BYTE ch = 0; diff --git a/source/frontends/libretro/game.h b/source/frontends/libretro/game.h index b9bf5620..1f8aaca2 100644 --- a/source/frontends/libretro/game.h +++ b/source/frontends/libretro/game.h @@ -18,7 +18,10 @@ public: static void keyboardCallback(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers); + static void frameTimeCallback(retro_usec_t usec); + static constexpr size_t FPS = 60; static unsigned input_devices[MAX_PADS]; + static retro_usec_t ourFrameTime; private: Speed mySpeed; // fixed speed diff --git a/source/frontends/libretro/libretro.cpp b/source/frontends/libretro/libretro.cpp index 4a6bc18b..56b39536 100644 --- a/source/frontends/libretro/libretro.cpp +++ b/source/frontends/libretro/libretro.cpp @@ -91,7 +91,7 @@ void retro_get_system_av_info(retro_system_av_info *info) info->geometry.max_height = GetFrameBufferBorderlessHeight(); info->geometry.aspect_ratio = 0; - info->timing.fps = 60; + info->timing.fps = Game::FPS; info->timing.sample_rate = SPKR_SAMPLE_RATE; } @@ -124,6 +124,9 @@ void retro_set_environment(retro_environment_t cb) retro_audio_buffer_status_callback audioCallback = {&RDirectSound::bufferStatusCallback}; cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, &audioCallback); + + retro_frame_time_callback timeCallback = {&Game::frameTimeCallback, 1000000 / Game::FPS}; + cb(RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK, &timeCallback); } void retro_set_audio_sample(retro_audio_sample_t cb) diff --git a/source/frontends/libretro/rdirectsound.cpp b/source/frontends/libretro/rdirectsound.cpp index 78e293d5..8256c514 100644 --- a/source/frontends/libretro/rdirectsound.cpp +++ b/source/frontends/libretro/rdirectsound.cpp @@ -12,6 +12,10 @@ namespace { + // we can only run 1 generator at a time + // 1 is for speaker (2 would be Mockingboard) + const size_t ourChannels = 1; + class DirectSoundGenerator { public: @@ -20,6 +24,9 @@ namespace void writeAudio(const size_t ms); void playSilence(const size_t ms); + bool isRunning() const; + size_t getNumberOfChannels() const; + private: IDirectSoundBuffer * myBuffer; @@ -27,14 +34,24 @@ namespace size_t myBytesPerSecond; - bool isRunning() const; - void mixBuffer(const void * ptr, const size_t size); }; - std::unordered_map> activeSoundGenerators; + std::shared_ptr findRunningGenerator(const size_t channels) + { + for (auto & it : activeSoundGenerators) + { + const std::shared_ptr & generator = it.second; + if (generator->isRunning() && generator->getNumberOfChannels() == channels) + { + return generator; + } + } + return std::shared_ptr(); + } + DirectSoundGenerator::DirectSoundGenerator(IDirectSoundBuffer * buffer) : myBuffer(buffer) { @@ -45,17 +62,21 @@ namespace { DWORD dwStatus; myBuffer->GetStatus(&dwStatus); - if (!(dwStatus & DSBSTATUS_PLAYING)) + if (dwStatus & DSBSTATUS_PLAYING) { - return false; + return true; } else { - // for now, just play speaker audio - return myBuffer->channels == 1; + return false; } } + size_t DirectSoundGenerator::getNumberOfChannels() const + { + return myBuffer->channels; + } + void DirectSoundGenerator::mixBuffer(const void * ptr, const size_t size) { const int16_t frames = size / (sizeof(int16_t) * myBuffer->channels); @@ -67,7 +88,7 @@ namespace } else { - myMixerBuffer.resize(2 * size); + myMixerBuffer.resize(2 * frames); for (int16_t i = 0; i < frames; ++i) { myMixerBuffer[i * 2] = data[i]; @@ -96,8 +117,7 @@ namespace } const size_t frames = ms * myBuffer->sampleRate / 1000; - const size_t bytesToWrite = frames * sizeof(int16_t) * 2; - myMixerBuffer.resize(bytesToWrite); + myMixerBuffer.resize(2 * frames); std::fill(myMixerBuffer.begin(), myMixerBuffer.end(), 0); audio_batch_cb(myMixerBuffer.data(), frames); } @@ -111,7 +131,8 @@ namespace return; } - const size_t bytesToRead = ms * myBytesPerSecond / 1000; + const size_t frames = ms * myBuffer->sampleRate / 1000; + const size_t bytesToRead = frames * myBuffer->channels * sizeof(int16_t); LPVOID lpvAudioPtr1, lpvAudioPtr2; DWORD dwAudioBytes1, dwAudioBytes2; @@ -149,18 +170,18 @@ namespace RDirectSound void writeAudio(const size_t ms) { - for (auto & it : activeSoundGenerators) + const auto generator = findRunningGenerator(ourChannels); + if (generator) { - const std::shared_ptr & generator = it.second; generator->writeAudio(ms); } } void playSilence(const size_t ms) { - for (auto & it : activeSoundGenerators) + const auto generator = findRunningGenerator(ourChannels); + if (generator) { - const std::shared_ptr & generator = it.second; generator->playSilence(ms); } } @@ -170,10 +191,17 @@ namespace RDirectSound if (active) { // I am not sure this is any useful - if (underrun_likely || occupancy < 40) + if (underrun_likely) { - log_cb(RETRO_LOG_INFO, "RA2: %s occupancy = %d, underrun_likely = %d\n", __FUNCTION__, occupancy, underrun_likely); - playSilence(10); + if (occupancy < 20) + { + log_cb(RETRO_LOG_INFO, "RA2: %s occupancy = %d, underrun_likely = %d\n", __FUNCTION__, occupancy, underrun_likely); + playSilence(10); + } + if (occupancy > 80) + { + log_cb(RETRO_LOG_INFO, "RA2: %s occupancy = %d, underrun_likely = %d\n", __FUNCTION__, occupancy, underrun_likely); + } } } } diff --git a/source/frontends/sdl/emulator.cpp b/source/frontends/sdl/emulator.cpp index a318e2c7..787d2162 100644 --- a/source/frontends/sdl/emulator.cpp +++ b/source/frontends/sdl/emulator.cpp @@ -168,7 +168,7 @@ void Emulator::execute(const size_t next) { if (g_nAppMode == MODE_RUNNING) { - const size_t cyclesToExecute = mySpeed.getCyclesTillNext(next); + const size_t cyclesToExecute = mySpeed.getCyclesTillNext(next * 1000); const bool bVideoUpdate = true; const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame();