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:
parent
1a5ec9ec0f
commit
175491f250
8 changed files with 82 additions and 47 deletions
2
linux.md
2
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.
|
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``
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Reference in a new issue