diff --git a/linux.md b/linux.md index 02c20498..d4123b0e 100644 --- a/linux.md +++ b/linux.md @@ -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`` diff --git a/source/frontends/libretro/CMakeLists.txt b/source/frontends/libretro/CMakeLists.txt index 3c22a273..717a71f9 100644 --- a/source/frontends/libretro/CMakeLists.txt +++ b/source/frontends/libretro/CMakeLists.txt @@ -15,6 +15,7 @@ if (EXISTS ${LIBRETRO_PATH}/include/libretro.h) joypadbase.cpp joypad.cpp analog.cpp + rdirectsound.cpp ) target_include_directories(ra2 PRIVATE diff --git a/source/frontends/libretro/libretro.cpp b/source/frontends/libretro/libretro.cpp index f7a3c191..4a6bc18b 100644 --- a/source/frontends/libretro/libretro.cpp +++ b/source/frontends/libretro/libretro.cpp @@ -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) diff --git a/source/frontends/libretro/rdirectsound.cpp b/source/frontends/libretro/rdirectsound.cpp index 2d5b6493..78e293d5 100644 --- a/source/frontends/libretro/rdirectsound.cpp +++ b/source/frontends/libretro/rdirectsound.cpp @@ -1,10 +1,181 @@ -#include +#include "frontends/libretro/rdirectsound.h" +#include "frontends/libretro/environment.h" + +#include "linux/windows/dsound.h" + +#include +#include +#include +#include +#include + +namespace +{ + + class DirectSoundGenerator + { + public: + DirectSoundGenerator(IDirectSoundBuffer * buffer); + + void writeAudio(const size_t ms); + void playSilence(const size_t ms); + + private: + IDirectSoundBuffer * myBuffer; + + std::vector myMixerBuffer; + + size_t myBytesPerSecond; + + bool isRunning() const; + + void mixBuffer(const void * ptr, const size_t size); + }; + + + std::unordered_map> 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(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 generator = std::make_shared(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 & generator = it.second; + generator->writeAudio(ms); + } + } + + void playSilence(const size_t ms) + { + for (auto & it : activeSoundGenerators) + { + const std::shared_ptr & 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); + } + } + } + } diff --git a/source/frontends/libretro/rdirectsound.h b/source/frontends/libretro/rdirectsound.h new file mode 100644 index 00000000..b91e2f9d --- /dev/null +++ b/source/frontends/libretro/rdirectsound.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace RDirectSound +{ + void writeAudio(const size_t ms); + void bufferStatusCallback(bool active, unsigned occupancy, bool underrun_likely); +}