libretro: play speaker audio (with some glitches).
Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
This commit is contained in:
parent
9b90335c63
commit
1a5ec9ec0f
5 changed files with 196 additions and 5 deletions
3
linux.md
3
linux.md
|
@ -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``
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
9
source/frontends/libretro/rdirectsound.h
Normal file
9
source/frontends/libretro/rdirectsound.h
Normal 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);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue