Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libretro: mix MB and Speaker #249

Merged
merged 3 commits into from
Feb 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion source/Utilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ void LoadConfiguration(bool loadImages)
REGLOAD(REGVALUE_SPKR_VOLUME, &dwTmp);
SpkrSetVolume(dwTmp, GetPropertySheet().GetVolumeMax());

dwTmp = 70;
dwTmp = 50;
REGLOAD(REGVALUE_MB_VOLUME, &dwTmp);
GetCardMgr().GetMockingboardCardMgr().SetVolume(dwTmp, GetPropertySheet().GetVolumeMax());

Expand Down
26 changes: 3 additions & 23 deletions source/frontends/libretro/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
#include "frontends/libretro/game.h"
#include "frontends/libretro/retroregistry.h"
#include "frontends/libretro/retroframe.h"
#include "frontends/libretro/rdirectsound.h"
#include "frontends/libretro/rkeyboard.h"
#include "frontends/common2/utils.h"
#include "frontends/common2/ptreeregistry.h"
#include "frontends/common2/programoptions.h"

#include "Registry.h"
#include "Common.h"
#include "Interface.h"

#include "linux/keyboardbuffer.h"
Expand Down Expand Up @@ -45,7 +45,6 @@ namespace ra2
Game::Game(const bool supportsInputBitmasks)
: mySupportsInputBitmasks(supportsInputBitmasks)
, myButtonStates(0)
, myAudioSource(AudioSource::UNKNOWN)
, myKeyboardType(KeyboardType::ASCII)
{
myLoggerContext = std::make_shared<LoggerContext>(true);
Expand Down Expand Up @@ -81,7 +80,6 @@ namespace ra2

void Game::refreshVariables()
{
myAudioSource = GetAudioSource();
myKeyboardType = GetKeyboardEmulationType();
}

Expand Down Expand Up @@ -294,24 +292,6 @@ namespace ra2
{
saveRegistryToINI(myRegistry);
}
if (checkButton(RETRO_DEVICE_ID_JOYPAD_R2))
{
switch (myAudioSource)
{
case AudioSource::SPEAKER:
{
myAudioSource = AudioSource::MOCKINGBOARD;
display_message(std::string("Audio source: ") + REGVALUE_AUDIO_MOCKINGBOARD);
break;
}
case AudioSource::MOCKINGBOARD:
{
myAudioSource = AudioSource::SPEAKER;
display_message(std::string("Audio source: ") + REGVALUE_AUDIO_SPEAKER);
break;
}
}
}
if (checkButton(RETRO_DEVICE_ID_JOYPAD_START))
{
// reset emulator by pressing "start" twice
Expand Down Expand Up @@ -379,9 +359,9 @@ namespace ra2
myFrame->Restart();
}

void Game::writeAudio(const size_t fps)
void Game::writeAudio(const size_t fps, const size_t sampleRate, const size_t channels)
{
ra2::writeAudio(myAudioSource, fps);
ra2::writeAudio(fps, sampleRate, channels, myAudioBuffer);
}

} // namespace ra2
12 changes: 8 additions & 4 deletions source/frontends/libretro/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
#include "frontends/libretro/environment.h"
#include "frontends/libretro/diskcontrol.h"
#include "frontends/libretro/rkeyboard.h"
#include "frontends/libretro/rdirectsound.h"

#include "Common.h"

#include <memory>
#include <chrono>
#include <string>
#include <vector>

Expand Down Expand Up @@ -37,7 +37,7 @@ namespace ra2
void updateVariables();
void executeOneFrame();
void processInputEvents();
void writeAudio(const size_t fps);
void writeAudio(const size_t fps, const size_t sampleRate, const size_t channels);

void drawVideoBuffer();

Expand All @@ -48,13 +48,15 @@ namespace ra2
void keyboardCallback(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers);

static constexpr size_t FPS = 60;
static constexpr size_t SAMPLE_RATE = SPKR_SAMPLE_RATE;
static constexpr size_t CHANNELS = 2;

static unsigned ourInputDevices[MAX_PADS];
static constexpr retro_usec_t ourFrameTime = 1000000 / FPS;

private:
const bool mySupportsInputBitmasks;
size_t myButtonStates;
AudioSource myAudioSource;
KeyboardType myKeyboardType;

// keep them in this order!
Expand All @@ -77,6 +79,8 @@ namespace ra2

DiskControl myDiskControl;

std::vector<int16_t> myAudioBuffer;

size_t updateButtonStates();
void keyboardEmulation();
void mouseEmulation();
Expand Down
4 changes: 2 additions & 2 deletions source/frontends/libretro/libretro.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ void retro_get_system_av_info(retro_system_av_info *info)
info->geometry.aspect_ratio = 0;

info->timing.fps = ra2::Game::FPS;
info->timing.sample_rate = SPKR_SAMPLE_RATE;
info->timing.sample_rate = ra2::Game::SAMPLE_RATE;
}

void retro_set_environment(retro_environment_t cb)
Expand Down Expand Up @@ -294,7 +294,7 @@ void retro_run(void)
ourGame->processInputEvents();
ourGame->executeOneFrame();
GetFrame().VideoPresentScreen();
ourGame->writeAudio(ra2::Game::FPS);
ourGame->writeAudio(ra2::Game::FPS, ra2::Game::SAMPLE_RATE, ra2::Game::CHANNELS);
}

bool retro_load_game(const retro_game_info *info)
Expand Down
164 changes: 79 additions & 85 deletions source/frontends/libretro/rdirectsound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,11 @@
#include "linux/linuxsoundbuffer.h"

#include <unordered_set>
#include <memory>
#include <cmath>
#include <vector>

namespace
{

ra2::AudioSource getAudioSourceFromName(const std::string &name)
{
// These are the strings used in DSGetSoundBuffer

if (name == "Spkr")
{
return ra2::AudioSource::SPEAKER;
}

if (name == "MB")
{
return ra2::AudioSource::MOCKINGBOARD;
}

if (name == "SSI263")
{
return ra2::AudioSource::SSI263;
}

// something new, just ignore it
return ra2::AudioSource::UNKNOWN;
}

class DirectSoundGenerator : public LinuxSoundBuffer
{
public:
Expand All @@ -44,11 +19,10 @@ namespace
void writeAudio(const size_t fps, const bool write);

bool isRunning();

ra2::AudioSource getSource() const;
bool isSameFormat(const size_t sampleRate, const size_t channels) const;
void advanceOneFrame(const size_t fps);

private:
const ra2::AudioSource myAudioSource;
std::vector<int16_t> myMixerBuffer;

void mixBuffer(const void *ptr, const size_t size);
Expand All @@ -59,7 +33,6 @@ namespace
DirectSoundGenerator::DirectSoundGenerator(
DWORD dwBufferSize, DWORD nSampleRate, int nChannels, LPCSTR pszVoiceName)
: LinuxSoundBuffer(dwBufferSize, nSampleRate, nChannels, pszVoiceName)
, myAudioSource(getAudioSourceFromName(myVoiceName))
{
}

Expand All @@ -82,63 +55,44 @@ namespace
}
}

ra2::AudioSource DirectSoundGenerator::getSource() const
bool DirectSoundGenerator::isSameFormat(const size_t sampleRate, const size_t channels) const
{
return myAudioSource;
return (mySampleRate == sampleRate) && (myChannels == channels);
}

void DirectSoundGenerator::mixBuffer(const void *ptr, const size_t size)
void DirectSoundGenerator::advanceOneFrame(const size_t fps)
{
const int16_t frames = size / (sizeof(int16_t) * myChannels);
const int16_t *data = static_cast<const int16_t *>(ptr);
const size_t bytesPerFrame = myChannels * sizeof(int16_t);
const size_t bytesToRead = mySampleRate * bytesPerFrame / fps;

if (myChannels == 2)
{
myMixerBuffer.assign(data, data + frames * myChannels);
}
else
{
myMixerBuffer.resize(2 * frames);
for (int16_t i = 0; i < frames; ++i)
{
myMixerBuffer[i * 2] = data[i];
myMixerBuffer[i * 2 + 1] = data[i];
}
}

const double logVolume = 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);
// it does not matter if we read broken frames, this generator will never play sound

for (int16_t &sample : myMixerBuffer)
{
sample = (sample * rvolume) / 128;
}

ra2::audio_batch_cb(myMixerBuffer.data(), frames);
LPVOID lpvAudioPtr1, lpvAudioPtr2;
DWORD dwAudioBytes1, dwAudioBytes2;
Read(bytesToRead, &lpvAudioPtr1, &dwAudioBytes1, &lpvAudioPtr2, &dwAudioBytes2);
}

void DirectSoundGenerator::writeAudio(const size_t fps, const bool write)
void mixBuffer(LinuxSoundBuffer *generator, LPVOID lpvAudioPtr, DWORD dwAudioBytes, int16_t *ptr)
{
const size_t frames = mySampleRate / fps;
const size_t bytesToRead = frames * myChannels * sizeof(int16_t);
// mix the buffer
const int16_t *data = static_cast<const int16_t *>(lpvAudioPtr);

LPVOID lpvAudioPtr1, lpvAudioPtr2;
DWORD dwAudioBytes1, dwAudioBytes2;
// always read to keep AppleWin audio algorithms working correctly.
Read(bytesToRead, &lpvAudioPtr1, &dwAudioBytes1, &lpvAudioPtr2, &dwAudioBytes2);
_ASSERT(dwAudioBytes % sizeof(int16_t) == 0);
const size_t samples = dwAudioBytes / sizeof(int16_t);

const double logVolume = generator->GetLogarithmicVolume();

// same formula as QAudio::convertVolume()
const double linVolume = logVolume > 0.99 ? 1.0 : -std::log(1.0 - logVolume) / std::log(100.0);
const size_t rvolume = size_t(linVolume * 128);

// it is very uncommon 2 sources play at the same time
// so we can just add the samples

if (write)
for (size_t i = 0; i < samples; ++i)
{
if (lpvAudioPtr1 && dwAudioBytes1)
{
mixBuffer(lpvAudioPtr1, dwAudioBytes1);
}
if (lpvAudioPtr2 && dwAudioBytes2)
{
mixBuffer(lpvAudioPtr2, dwAudioBytes2);
}
*ptr += (data[i] * rvolume) / 128;
++ptr;
}
}

Expand All @@ -150,31 +104,71 @@ namespace ra2
std::shared_ptr<SoundBuffer> iCreateDirectSoundBuffer(
uint32_t dwBufferSize, uint32_t nSampleRate, int nChannels, const char *pszVoiceName)
{
// make sure the size is an integer multiple of the frame size
const uint32_t bytesPerFrame = nChannels * sizeof(int16_t);
const uint32_t alignedBufferSize = ((dwBufferSize + bytesPerFrame - 1) / bytesPerFrame) * bytesPerFrame;

std::shared_ptr<DirectSoundGenerator> generator =
std::make_shared<DirectSoundGenerator>(dwBufferSize, nSampleRate, nChannels, pszVoiceName);
std::make_shared<DirectSoundGenerator>(alignedBufferSize, nSampleRate, nChannels, pszVoiceName);

DirectSoundGenerator *ptr = generator.get();
activeSoundGenerators.insert(ptr);
return generator;
}

void writeAudio(const AudioSource selectedSource, const size_t fps)
void writeAudio(const size_t fps, const size_t sampleRate, const size_t channels, std::vector<int16_t> &buffer)
{
bool found = false;
// we have already checked that
// - the buffer is a multiple of the frame size
// and we always write full frames

const size_t targetFrames = sampleRate / fps;
const size_t bytesPerFrame = channels * sizeof(int16_t);
size_t framesToRead = targetFrames; // for the target sampleRate & channels

for (const auto &it : activeSoundGenerators)
{
const auto &generator = it;
if (generator->isRunning())
{
const bool selected = !found && (selectedSource == generator->getSource());
// we still read audio from all buffers
// to keep AppleWin audio generation woking correctly
// but only write on the selected one
generator->writeAudio(fps, selected);
// TODO: implement an algorithm to merge 2 channels (speaker + mockingboard)
found = found || selected;
if (generator->isSameFormat(sampleRate, channels))
{
const size_t bytesAvailable = generator->GetBytesInBuffer();
const size_t framesAvailable = bytesAvailable / bytesPerFrame;
framesToRead = std::min(framesToRead, framesAvailable);
}
else
{
// throw away the audio
generator->advanceOneFrame(fps);
}
}
}

buffer.clear();
buffer.resize(framesToRead * channels, 0);

// only generators with the target sampleRate and number of channels are mixed
const size_t bytesToRead = framesToRead * bytesPerFrame;

for (const auto &it : activeSoundGenerators)
{
const auto &generator = it;
if (generator->isRunning() && generator->isSameFormat(sampleRate, channels))
{
LPVOID lpvAudioPtr1, lpvAudioPtr2;
DWORD dwAudioBytes1, dwAudioBytes2;
const size_t bytesRead =
generator->Read(bytesToRead, &lpvAudioPtr1, &dwAudioBytes1, &lpvAudioPtr2, &dwAudioBytes2);
_ASSERT(bytesRead == bytesToRead);

int16_t *ptr = buffer.data();

mixBuffer(generator, lpvAudioPtr1, dwAudioBytes1, ptr);
mixBuffer(generator, lpvAudioPtr2, dwAudioBytes2, ptr);
}
}
// TODO: if found = false, we should probably write some silence
ra2::audio_batch_cb(buffer.data(), framesToRead);
}

void bufferStatusCallback(bool active, unsigned occupancy, bool underrun_likely)
Expand Down
Loading