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
* qapple: Qt 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 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.
Audio (speaker) works with some glitches.
Must be manually configured:
``cmake -DLIBRETRO_PATH=/path/to/libretro-common``

View file

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

View file

@ -4,6 +4,7 @@
#include "StdAfx.h"
#include "Frame.h"
#include "Common.h"
#include "linux/version.h"
#include "linux/paddle.h"
@ -12,6 +13,7 @@
#include "frontends/libretro/environment.h"
#include "frontends/libretro/joypad.h"
#include "frontends/libretro/analog.h"
#include "frontends/libretro/rdirectsound.h"
namespace
{
@ -90,7 +92,7 @@ void retro_get_system_av_info(retro_system_av_info *info)
info->geometry.aspect_ratio = 0;
info->timing.fps = 60;
info->timing.sample_rate = 0;
info->timing.sample_rate = SPKR_SAMPLE_RATE;
}
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);
retro_keyboard_callback callback = {&Game::keyboardCallback};
cb(RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK, &callback);
retro_keyboard_callback keyboardCallback = {&Game::keyboardCallback};
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)
@ -156,6 +161,8 @@ void retro_run(void)
game->processInputEvents();
game->executeOneFrame();
game->drawVideoBuffer();
const size_t ms = (1000 + 60 - 1) / 60; // round up
RDirectSound::writeAudio(ms);
}
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)
{
const std::shared_ptr<DirectSoundGenerator> generator = std::make_shared<DirectSoundGenerator>(buffer);
activeSoundGenerators[buffer] = generator;
}
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);
}