libretro core: fix (most?) audio glitches.

It was reading a number of bytes not multiple of "2 * channels".

Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
This commit is contained in:
Andrea Odetti 2020-12-26 19:28:51 +00:00
parent 1a5ec9ec0f
commit 175491f250
8 changed files with 82 additions and 47 deletions

View file

@ -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. Video works, but the vertical flip is done in software.
Audio (speaker) works with some glitches. Audio (speaker) works.
Must be manually configured: Must be manually configured:
``cmake -DLIBRETRO_PATH=/path/to/libretro-common`` ``cmake -DLIBRETRO_PATH=/path/to/libretro-common``

View file

@ -16,11 +16,11 @@ void Speed::reset()
myStartCycles = g_nCumulativeCycles; myStartCycles = g_nCumulativeCycles;
} }
size_t Speed::getCyclesTillNext(const size_t milliseconds) const size_t Speed::getCyclesTillNext(const size_t microseconds) const
{ {
if (myFixedSpeed) if (myFixedSpeed)
{ {
const size_t cycles = static_cast<uint64_t>(milliseconds * g_fCurrentCLK6502 * 1.0e-3); const size_t cycles = static_cast<uint64_t>(microseconds * g_fCurrentCLK6502 * 1.0e-6);
return cycles; return cycles;
} }
else else
@ -28,11 +28,11 @@ size_t Speed::getCyclesTillNext(const size_t milliseconds) const
const uint64_t currentCycles = g_nCumulativeCycles; const uint64_t currentCycles = g_nCumulativeCycles;
const auto currentTime = std::chrono::steady_clock::now(); const auto currentTime = std::chrono::steady_clock::now();
const auto currentDelta = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - myStartTime).count(); const auto currentDelta = std::chrono::duration_cast<std::chrono::microseconds>(currentTime - myStartTime).count();
// target the next time we will be called // target the next time we will be called
const auto targetDeltaInMillis = currentDelta + milliseconds; const auto targetDeltaInMicros = currentDelta + microseconds;
const uint64_t targetCycles = static_cast<uint64_t>(targetDeltaInMillis * g_fCurrentCLK6502 * 1.0e-3) + myStartCycles; const uint64_t targetCycles = static_cast<uint64_t>(targetDeltaInMicros * g_fCurrentCLK6502 * 1.0e-6) + myStartCycles;
if (targetCycles > currentCycles) if (targetCycles > currentCycles)
{ {
// number of cycles to fill this period // number of cycles to fill this period

View file

@ -10,8 +10,8 @@ public:
void reset(); void reset();
// calculate the number of cycles to execute in the current period // calculate the number of cycles to execute in the current period
// assuming the next call will happen in x milliseconds // assuming the next call will happen in x microseconds
size_t getCyclesTillNext(const size_t milliseconds) const; size_t getCyclesTillNext(const size_t microseconds) const;
private: private:

View file

@ -26,24 +26,6 @@
namespace 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() void updateWindowTitle()
{ {
GetAppleWindowTitle(); GetAppleWindowTitle();
@ -88,9 +70,23 @@ Game::~Game()
uninitialiseEmulator(); uninitialiseEmulator();
} }
retro_usec_t Game::ourFrameTime = 0;
void Game::executeOneFrame() 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() 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) void Game::processKeyDown(unsigned keycode, uint32_t character, uint16_t key_modifiers)
{ {
BYTE ch = 0; BYTE ch = 0;

View file

@ -18,7 +18,10 @@ public:
static void keyboardCallback(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers); 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 unsigned input_devices[MAX_PADS];
static retro_usec_t ourFrameTime;
private: private:
Speed mySpeed; // fixed speed Speed mySpeed; // fixed speed

View file

@ -91,7 +91,7 @@ void retro_get_system_av_info(retro_system_av_info *info)
info->geometry.max_height = GetFrameBufferBorderlessHeight(); info->geometry.max_height = GetFrameBufferBorderlessHeight();
info->geometry.aspect_ratio = 0; info->geometry.aspect_ratio = 0;
info->timing.fps = 60; info->timing.fps = Game::FPS;
info->timing.sample_rate = SPKR_SAMPLE_RATE; 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}; retro_audio_buffer_status_callback audioCallback = {&RDirectSound::bufferStatusCallback};
cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, &audioCallback); 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) void retro_set_audio_sample(retro_audio_sample_t cb)

View file

@ -12,6 +12,10 @@
namespace 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 class DirectSoundGenerator
{ {
public: public:
@ -20,6 +24,9 @@ namespace
void writeAudio(const size_t ms); void writeAudio(const size_t ms);
void playSilence(const size_t ms); void playSilence(const size_t ms);
bool isRunning() const;
size_t getNumberOfChannels() const;
private: private:
IDirectSoundBuffer * myBuffer; IDirectSoundBuffer * myBuffer;
@ -27,14 +34,24 @@ namespace
size_t myBytesPerSecond; size_t myBytesPerSecond;
bool isRunning() const;
void mixBuffer(const void * ptr, const size_t size); void mixBuffer(const void * ptr, const size_t size);
}; };
std::unordered_map<IDirectSoundBuffer *, std::shared_ptr<DirectSoundGenerator>> activeSoundGenerators; std::unordered_map<IDirectSoundBuffer *, std::shared_ptr<DirectSoundGenerator>> activeSoundGenerators;
std::shared_ptr<DirectSoundGenerator> findRunningGenerator(const size_t channels)
{
for (auto & it : activeSoundGenerators)
{
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
if (generator->isRunning() && generator->getNumberOfChannels() == channels)
{
return generator;
}
}
return std::shared_ptr<DirectSoundGenerator>();
}
DirectSoundGenerator::DirectSoundGenerator(IDirectSoundBuffer * buffer) DirectSoundGenerator::DirectSoundGenerator(IDirectSoundBuffer * buffer)
: myBuffer(buffer) : myBuffer(buffer)
{ {
@ -45,17 +62,21 @@ namespace
{ {
DWORD dwStatus; DWORD dwStatus;
myBuffer->GetStatus(&dwStatus); myBuffer->GetStatus(&dwStatus);
if (!(dwStatus & DSBSTATUS_PLAYING)) if (dwStatus & DSBSTATUS_PLAYING)
{ {
return false; return true;
} }
else else
{ {
// for now, just play speaker audio return false;
return myBuffer->channels == 1;
} }
} }
size_t DirectSoundGenerator::getNumberOfChannels() const
{
return myBuffer->channels;
}
void DirectSoundGenerator::mixBuffer(const void * ptr, const size_t size) void DirectSoundGenerator::mixBuffer(const void * ptr, const size_t size)
{ {
const int16_t frames = size / (sizeof(int16_t) * myBuffer->channels); const int16_t frames = size / (sizeof(int16_t) * myBuffer->channels);
@ -67,7 +88,7 @@ namespace
} }
else else
{ {
myMixerBuffer.resize(2 * size); myMixerBuffer.resize(2 * frames);
for (int16_t i = 0; i < frames; ++i) for (int16_t i = 0; i < frames; ++i)
{ {
myMixerBuffer[i * 2] = data[i]; myMixerBuffer[i * 2] = data[i];
@ -96,8 +117,7 @@ namespace
} }
const size_t frames = ms * myBuffer->sampleRate / 1000; const size_t frames = ms * myBuffer->sampleRate / 1000;
const size_t bytesToWrite = frames * sizeof(int16_t) * 2; myMixerBuffer.resize(2 * frames);
myMixerBuffer.resize(bytesToWrite);
std::fill(myMixerBuffer.begin(), myMixerBuffer.end(), 0); std::fill(myMixerBuffer.begin(), myMixerBuffer.end(), 0);
audio_batch_cb(myMixerBuffer.data(), frames); audio_batch_cb(myMixerBuffer.data(), frames);
} }
@ -111,7 +131,8 @@ namespace
return; 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; LPVOID lpvAudioPtr1, lpvAudioPtr2;
DWORD dwAudioBytes1, dwAudioBytes2; DWORD dwAudioBytes1, dwAudioBytes2;
@ -149,18 +170,18 @@ namespace RDirectSound
void writeAudio(const size_t ms) void writeAudio(const size_t ms)
{ {
for (auto & it : activeSoundGenerators) const auto generator = findRunningGenerator(ourChannels);
if (generator)
{ {
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
generator->writeAudio(ms); generator->writeAudio(ms);
} }
} }
void playSilence(const size_t ms) void playSilence(const size_t ms)
{ {
for (auto & it : activeSoundGenerators) const auto generator = findRunningGenerator(ourChannels);
if (generator)
{ {
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
generator->playSilence(ms); generator->playSilence(ms);
} }
} }
@ -170,10 +191,17 @@ namespace RDirectSound
if (active) if (active)
{ {
// I am not sure this is any useful // 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); if (occupancy < 20)
playSilence(10); {
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);
}
} }
} }
} }

View file

@ -168,7 +168,7 @@ void Emulator::execute(const size_t next)
{ {
if (g_nAppMode == MODE_RUNNING) 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 bool bVideoUpdate = true;
const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame(); const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame();