libretro: play speaker audio (with some glitches).

Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
This commit is contained in:
Andrea Odetti 2020-12-25 21:59:48 +00:00
parent 9b90335c63
commit 1a5ec9ec0f
5 changed files with 196 additions and 5 deletions

View file

@ -23,6 +23,7 @@ There are 4 projects
* applen: a frontend based on ncurses * applen: a frontend based on ncurses
* qapple: Qt frontend * qapple: Qt frontend
* sa2: SDL2 frontend * sa2: SDL2 frontend
* libra2: a libretro core
The libapple interface is a *link time* interface: some functions are not defined and must be provided in order to properly link The libapple interface is a *link time* interface: some functions are not defined and must be provided in order to properly link
the application. These functions are listed in [interface.h](source/linux/interface.h). the application. These functions are listed in [interface.h](source/linux/interface.h).
@ -107,6 +108,8 @@ 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.
Must be manually configured: Must be manually configured:
``cmake -DLIBRETRO_PATH=/path/to/libretro-common`` ``cmake -DLIBRETRO_PATH=/path/to/libretro-common``

View file

@ -15,6 +15,7 @@ if (EXISTS ${LIBRETRO_PATH}/include/libretro.h)
joypadbase.cpp joypadbase.cpp
joypad.cpp joypad.cpp
analog.cpp analog.cpp
rdirectsound.cpp
) )
target_include_directories(ra2 PRIVATE target_include_directories(ra2 PRIVATE

View file

@ -4,6 +4,7 @@
#include "StdAfx.h" #include "StdAfx.h"
#include "Frame.h" #include "Frame.h"
#include "Common.h"
#include "linux/version.h" #include "linux/version.h"
#include "linux/paddle.h" #include "linux/paddle.h"
@ -12,6 +13,7 @@
#include "frontends/libretro/environment.h" #include "frontends/libretro/environment.h"
#include "frontends/libretro/joypad.h" #include "frontends/libretro/joypad.h"
#include "frontends/libretro/analog.h" #include "frontends/libretro/analog.h"
#include "frontends/libretro/rdirectsound.h"
namespace namespace
{ {
@ -90,7 +92,7 @@ void retro_get_system_av_info(retro_system_av_info *info)
info->geometry.aspect_ratio = 0; info->geometry.aspect_ratio = 0;
info->timing.fps = 60; info->timing.fps = 60;
info->timing.sample_rate = 0; info->timing.sample_rate = SPKR_SAMPLE_RATE;
} }
void retro_set_environment(retro_environment_t cb) void retro_set_environment(retro_environment_t cb)
@ -117,8 +119,11 @@ void retro_set_environment(retro_environment_t cb)
cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
retro_keyboard_callback callback = {&Game::keyboardCallback}; retro_keyboard_callback keyboardCallback = {&Game::keyboardCallback};
cb(RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK, &callback); cb(RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK, &keyboardCallback);
retro_audio_buffer_status_callback audioCallback = {&RDirectSound::bufferStatusCallback};
cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, &audioCallback);
} }
void retro_set_audio_sample(retro_audio_sample_t cb) void retro_set_audio_sample(retro_audio_sample_t cb)
@ -156,6 +161,8 @@ void retro_run(void)
game->processInputEvents(); game->processInputEvents();
game->executeOneFrame(); game->executeOneFrame();
game->drawVideoBuffer(); game->drawVideoBuffer();
const size_t ms = (1000 + 60 - 1) / 60; // round up
RDirectSound::writeAudio(ms);
} }
bool retro_load_game(const retro_game_info *info) bool retro_load_game(const retro_game_info *info)

View file

@ -1,10 +1,181 @@
#include <linux/interface.h> #include "frontends/libretro/rdirectsound.h"
#include "frontends/libretro/environment.h"
#include "linux/windows/dsound.h"
#include <unordered_map>
#include <memory>
#include <iostream>
#include <iomanip>
#include <cmath>
namespace
{
class DirectSoundGenerator
{
public:
DirectSoundGenerator(IDirectSoundBuffer * buffer);
void writeAudio(const size_t ms);
void playSilence(const size_t ms);
private:
IDirectSoundBuffer * myBuffer;
std::vector<int16_t> myMixerBuffer;
size_t myBytesPerSecond;
bool isRunning() const;
void mixBuffer(const void * ptr, const size_t size);
};
std::unordered_map<IDirectSoundBuffer *, std::shared_ptr<DirectSoundGenerator>> activeSoundGenerators;
DirectSoundGenerator::DirectSoundGenerator(IDirectSoundBuffer * buffer)
: myBuffer(buffer)
{
myBytesPerSecond = myBuffer->channels * myBuffer->sampleRate * sizeof(int16_t);
}
bool DirectSoundGenerator::isRunning() const
{
DWORD dwStatus;
myBuffer->GetStatus(&dwStatus);
if (!(dwStatus & DSBSTATUS_PLAYING))
{
return false;
}
else
{
// for now, just play speaker audio
return myBuffer->channels == 1;
}
}
void DirectSoundGenerator::mixBuffer(const void * ptr, const size_t size)
{
const int16_t frames = size / (sizeof(int16_t) * myBuffer->channels);
const int16_t * data = static_cast<const int16_t *>(ptr);
if (myBuffer->channels == 2)
{
myMixerBuffer.assign(data, data + frames * myBuffer->channels);
}
else
{
myMixerBuffer.resize(2 * size);
for (int16_t i = 0; i < frames; ++i)
{
myMixerBuffer[i * 2] = data[i];
myMixerBuffer[i * 2 + 1] = data[i];
}
}
const double logVolume = myBuffer->GetLogarithmicVolume();
// same formula as QAudio::convertVolume()
const double linVolume = logVolume > 0.99 ? 1.0 : -std::log(1.0 - logVolume) / std::log(100.0);
const int16_t rvolume = int16_t(linVolume * 128);
for (int16_t & sample : myMixerBuffer)
{
sample = (sample * rvolume) / 128;
}
audio_batch_cb(myMixerBuffer.data(), frames);
}
void DirectSoundGenerator::playSilence(const size_t ms)
{
if (!isRunning())
{
return;
}
const size_t frames = ms * myBuffer->sampleRate / 1000;
const size_t bytesToWrite = frames * sizeof(int16_t) * 2;
myMixerBuffer.resize(bytesToWrite);
std::fill(myMixerBuffer.begin(), myMixerBuffer.end(), 0);
audio_batch_cb(myMixerBuffer.data(), frames);
}
void DirectSoundGenerator::writeAudio(const size_t ms)
{
// this is autostart as we only do for the palying buffers
// and AW might activate one later
if (!isRunning())
{
return;
}
const size_t bytesToRead = ms * myBytesPerSecond / 1000;
LPVOID lpvAudioPtr1, lpvAudioPtr2;
DWORD dwAudioBytes1, dwAudioBytes2;
myBuffer->Read(bytesToRead, &lpvAudioPtr1, &dwAudioBytes1, &lpvAudioPtr2, &dwAudioBytes2);
if (lpvAudioPtr1 && dwAudioBytes1)
{
mixBuffer(lpvAudioPtr1, dwAudioBytes1);
}
if (lpvAudioPtr2 && dwAudioBytes2)
{
mixBuffer(lpvAudioPtr2, dwAudioBytes2);
}
}
}
// Mockingboard
void registerSoundBuffer(IDirectSoundBuffer * buffer) void registerSoundBuffer(IDirectSoundBuffer * buffer)
{ {
const std::shared_ptr<DirectSoundGenerator> generator = std::make_shared<DirectSoundGenerator>(buffer);
activeSoundGenerators[buffer] = generator;
} }
void unregisterSoundBuffer(IDirectSoundBuffer * buffer) void unregisterSoundBuffer(IDirectSoundBuffer * buffer)
{ {
const auto it = activeSoundGenerators.find(buffer);
if (it != activeSoundGenerators.end())
{
activeSoundGenerators.erase(it);
}
}
namespace RDirectSound
{
void writeAudio(const size_t ms)
{
for (auto & it : activeSoundGenerators)
{
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
generator->writeAudio(ms);
}
}
void playSilence(const size_t ms)
{
for (auto & it : activeSoundGenerators)
{
const std::shared_ptr<DirectSoundGenerator> & generator = it.second;
generator->playSilence(ms);
}
}
void bufferStatusCallback(bool active, unsigned occupancy, bool underrun_likely)
{
if (active)
{
// I am not sure this is any useful
if (underrun_likely || occupancy < 40)
{
log_cb(RETRO_LOG_INFO, "RA2: %s occupancy = %d, underrun_likely = %d\n", __FUNCTION__, occupancy, underrun_likely);
playSilence(10);
}
}
}
} }

View file

@ -0,0 +1,9 @@
#pragma once
#include <cstdlib>
namespace RDirectSound
{
void writeAudio(const size_t ms);
void bufferStatusCallback(bool active, unsigned occupancy, bool underrun_likely);
}