diff --git a/CHANGES b/CHANGES index ce4b8cb80..466341681 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,7 @@ Features: - Implement BIOS call Stop, for sleep mode - Automatically load patches, if found - Improved video synchronization + - Configurable audio output sample rate Bugfixes: - ARM7: Fix SWI and IRQ timings - GBA Audio: Force audio FIFOs to 32-bit diff --git a/src/gba/supervisor/config.c b/src/gba/supervisor/config.c index 19e5c2bd6..ecf175dbb 100644 --- a/src/gba/supervisor/config.c +++ b/src/gba/supervisor/config.c @@ -247,6 +247,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) { if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) { opts->audioBuffers = audioBuffers; } + _lookupUIntValue(config, "sampleRate", &opts->sampleRate); int fakeBool; if (_lookupIntValue(config, "useBios", &fakeBool)) { @@ -305,6 +306,7 @@ void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* op ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval); ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget); ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers); + ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate); ConfigurationSetIntValue(&config->defaultsTable, 0, "audioSync", opts->audioSync); ConfigurationSetIntValue(&config->defaultsTable, 0, "videoSync", opts->videoSync); ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen); diff --git a/src/gba/supervisor/config.h b/src/gba/supervisor/config.h index 07c824cd2..35d822838 100644 --- a/src/gba/supervisor/config.h +++ b/src/gba/supervisor/config.h @@ -29,6 +29,7 @@ struct GBAOptions { int rewindBufferInterval; float fpsTarget; size_t audioBuffers; + unsigned sampleRate; int fullscreen; int width; diff --git a/src/platform/qt/AudioProcessor.h b/src/platform/qt/AudioProcessor.h index abbca9395..74d76a345 100644 --- a/src/platform/qt/AudioProcessor.h +++ b/src/platform/qt/AudioProcessor.h @@ -31,6 +31,7 @@ public: virtual void setInput(GBAThread* input); int getBufferSamples() const { return m_samples; } + virtual unsigned sampleRate() const = 0; public slots: virtual void start() = 0; @@ -39,7 +40,7 @@ public slots: virtual void setBufferSamples(int samples) = 0; virtual void inputParametersChanged() = 0; - virtual unsigned sampleRate() const = 0; + virtual void requestSampleRate(unsigned) = 0; protected: GBAThread* input() { return m_context; } diff --git a/src/platform/qt/AudioProcessorQt.cpp b/src/platform/qt/AudioProcessorQt.cpp index 281ca6bba..c28d934b9 100644 --- a/src/platform/qt/AudioProcessorQt.cpp +++ b/src/platform/qt/AudioProcessorQt.cpp @@ -20,6 +20,7 @@ AudioProcessorQt::AudioProcessorQt(QObject* parent) : AudioProcessor(parent) , m_audioOutput(nullptr) , m_device(nullptr) + , m_sampleRate(44100) { } @@ -45,7 +46,7 @@ void AudioProcessorQt::start() { if (!m_audioOutput) { QAudioFormat format; - format.setSampleRate(44100); + format.setSampleRate(m_sampleRate); format.setChannelCount(2); format.setSampleSize(16); format.setCodec("audio/pcm"); @@ -84,6 +85,15 @@ void AudioProcessorQt::inputParametersChanged() { } } +void AudioProcessorQt::requestSampleRate(unsigned rate) { + m_sampleRate = rate; + if (m_device) { + QAudioFormat format(m_audioOutput->format()); + format.setSampleRate(rate); + m_device->setFormat(format); + } +} + unsigned AudioProcessorQt::sampleRate() const { if (!m_audioOutput) { return 0; diff --git a/src/platform/qt/AudioProcessorQt.h b/src/platform/qt/AudioProcessorQt.h index ee9dde24d..52d7b606c 100644 --- a/src/platform/qt/AudioProcessorQt.h +++ b/src/platform/qt/AudioProcessorQt.h @@ -20,6 +20,7 @@ public: AudioProcessorQt(QObject* parent = nullptr); virtual void setInput(GBAThread* input); + virtual unsigned sampleRate() const override; public slots: virtual void start(); @@ -28,11 +29,12 @@ public slots: virtual void setBufferSamples(int samples); virtual void inputParametersChanged(); - virtual unsigned sampleRate() const override; + virtual void requestSampleRate(unsigned) override; private: QAudioOutput* m_audioOutput; AudioDevice* m_device; + unsigned m_sampleRate; }; } diff --git a/src/platform/qt/AudioProcessorSDL.cpp b/src/platform/qt/AudioProcessorSDL.cpp index 0caa9765c..2db807f3f 100644 --- a/src/platform/qt/AudioProcessorSDL.cpp +++ b/src/platform/qt/AudioProcessorSDL.cpp @@ -15,7 +15,7 @@ using namespace QGBA; AudioProcessorSDL::AudioProcessorSDL(QObject* parent) : AudioProcessor(parent) - , m_audio() + , m_audio{ 2048, 44100 } { } @@ -55,6 +55,18 @@ void AudioProcessorSDL::setBufferSamples(int samples) { void AudioProcessorSDL::inputParametersChanged() { } -unsigned AudioProcessorSDL::sampleRate() const { - return m_audio.obtainedSpec.freq; +void AudioProcessorSDL::requestSampleRate(unsigned rate) { + m_audio.sampleRate = rate; + if (m_audio.thread) { + GBASDLDeinitAudio(&m_audio); + GBASDLInitAudio(&m_audio, input()); + } +} + +unsigned AudioProcessorSDL::sampleRate() const { + if (m_audio.thread) { + return m_audio.obtainedSpec.freq; + } else { + return 0; + } } diff --git a/src/platform/qt/AudioProcessorSDL.h b/src/platform/qt/AudioProcessorSDL.h index 002dcd839..c98d00473 100644 --- a/src/platform/qt/AudioProcessorSDL.h +++ b/src/platform/qt/AudioProcessorSDL.h @@ -22,6 +22,8 @@ public: AudioProcessorSDL(QObject* parent = nullptr); ~AudioProcessorSDL(); + virtual unsigned sampleRate() const override; + public slots: virtual void start(); virtual void pause(); @@ -29,7 +31,7 @@ public slots: virtual void setBufferSamples(int samples); virtual void inputParametersChanged(); - virtual unsigned sampleRate() const override; + virtual void requestSampleRate(unsigned) override; private: GBASDLAudio m_audio; diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index 3200f45e7..786dd2f7b 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -53,6 +53,7 @@ GBAApp::GBAApp(int& argc, char* argv[]) return; } + AudioProcessor::setDriver(static_cast(m_configController.getQtOption("audioDriver").toInt())); Window* w = new Window(&m_configController); connect(w, &Window::destroyed, [this]() { m_windows[0] = nullptr; @@ -67,9 +68,6 @@ GBAApp::GBAApp(int& argc, char* argv[]) freeArguments(&args); w->show(); - AudioProcessor::setDriver(static_cast(m_configController.getQtOption("audioDriver").toInt())); - w->controller()->reloadAudioDriver(); - w->controller()->setMultiplayerController(&m_multiplayer); } diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 6432a2ba6..63df80baa 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -87,7 +87,9 @@ GameController::GameController(QObject* parent) m_threadContext.startCallback = [](GBAThread* context) { GameController* controller = static_cast(context->userData); - controller->m_audioProcessor->setInput(context); + if (controller->m_audioProcessor) { + controller->m_audioProcessor->setInput(context); + } context->gba->luminanceSource = &controller->m_lux; GBARTCGenericSourceInit(&controller->m_rtc, context->gba); context->gba->rtcSource = &controller->m_rtc.d; @@ -588,10 +590,21 @@ void GameController::clearKeys() { } void GameController::setAudioBufferSamples(int samples) { - threadInterrupt(); - redoSamples(samples); - threadContinue(); - QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples)); + if (m_audioProcessor) { + threadInterrupt(); + redoSamples(samples); + threadContinue(); + QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples)); + } +} + +void GameController::setAudioSampleRate(unsigned rate) { + if (m_audioProcessor) { + threadInterrupt(); + redoSamples(m_audioProcessor->getBufferSamples()); + threadContinue(); + QMetaObject::invokeMethod(m_audioProcessor, "requestSampleRate", Q_ARG(unsigned, rate)); + } } void GameController::setAudioChannelEnabled(int channel, bool enable) { @@ -644,7 +657,9 @@ void GameController::setFPSTarget(float fps) { if (m_turbo && m_turboSpeed > 0) { m_threadContext.fpsTarget *= m_turboSpeed; } - redoSamples(m_audioProcessor->getBufferSamples()); + if (m_audioProcessor) { + redoSamples(m_audioProcessor->getBufferSamples()); + } threadContinue(); } @@ -801,7 +816,9 @@ void GameController::enableTurbo() { m_threadContext.sync.audioWait = true; m_threadContext.sync.videoFrameWait = false; } - redoSamples(m_audioProcessor->getBufferSamples()); + if (m_audioProcessor) { + redoSamples(m_audioProcessor->getBufferSamples()); + } threadContinue(); } @@ -830,11 +847,21 @@ void GameController::screenshot() { #endif void GameController::reloadAudioDriver() { - QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection); - int samples = m_audioProcessor->getBufferSamples(); - delete m_audioProcessor; + int samples = 0; + unsigned sampleRate = 0; + if (m_audioProcessor) { + QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection); + samples = m_audioProcessor->getBufferSamples(); + sampleRate = m_audioProcessor->sampleRate(); + delete m_audioProcessor; + } m_audioProcessor = AudioProcessor::create(); - m_audioProcessor->setBufferSamples(samples); + if (samples) { + m_audioProcessor->setBufferSamples(samples); + } + if (sampleRate) { + m_audioProcessor->requestSampleRate(sampleRate); + } m_audioProcessor->moveToThread(m_audioThread); connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start())); connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause())); diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index 9267668b5..a4de17fff 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -119,6 +119,7 @@ public slots: void keyReleased(int key); void clearKeys(); void setAudioBufferSamples(int samples); + void setAudioSampleRate(unsigned rate); void setAudioChannelEnabled(int channel, bool enable = true); void setVideoLayerEnabled(int layer, bool enable = true); void setFPSTarget(float fps); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 8639a4e32..6fd233b60 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -127,6 +127,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) connect(this, SIGNAL(shutdown()), m_controller, SLOT(closeGame())); connect(this, SIGNAL(shutdown()), m_logView, SLOT(hide())); connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int))); + connect(this, SIGNAL(sampleRateChanged(unsigned)), m_controller, SLOT(setAudioSampleRate(unsigned))); connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float))); connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS())); connect(m_display, &Display::hideCursor, [this]() { @@ -195,6 +196,7 @@ void Window::loadConfig() { m_controller->loadBIOS(opts->bios); } + // TODO: Move these to ConfigController if (opts->fpsTarget) { emit fpsTargetChanged(opts->fpsTarget); } @@ -203,6 +205,10 @@ void Window::loadConfig() { emit audioBufferSamplesChanged(opts->audioBuffers); } + if (opts->sampleRate) { + emit sampleRateChanged(opts->sampleRate); + } + if (opts->width && opts->height) { resizeFrame(opts->width, opts->height); } diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 16295f264..ebc923776 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -54,6 +54,7 @@ signals: void startDrawing(GBAThread*); void shutdown(); void audioBufferSamplesChanged(int samples); + void sampleRateChanged(unsigned samples); void fpsTargetChanged(float target); public slots: diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 058055ac2..78dda4706 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -112,6 +112,10 @@ int main(int argc, char** argv) { bool didFail = false; renderer.audio.samples = context.audioBuffers; + renderer.audio.sampleRate = 44100; + if (opts.sampleRate) { + renderer.audio.sampleRate = opts.sampleRate; + } if (!GBASDLInitAudio(&renderer.audio, &context)) { didFail = true; } diff --git a/src/platform/sdl/sdl-audio.c b/src/platform/sdl/sdl-audio.c index 6e0fdebb2..321679a96 100644 --- a/src/platform/sdl/sdl-audio.c +++ b/src/platform/sdl/sdl-audio.c @@ -22,7 +22,7 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex return false; } - context->desiredSpec.freq = 44100; + context->desiredSpec.freq = context->sampleRate; context->desiredSpec.format = AUDIO_S16SYS; context->desiredSpec.channels = 2; context->desiredSpec.samples = context->samples; diff --git a/src/platform/sdl/sdl-audio.h b/src/platform/sdl/sdl-audio.h index 6730333eb..f2f0b8df2 100644 --- a/src/platform/sdl/sdl-audio.h +++ b/src/platform/sdl/sdl-audio.h @@ -15,6 +15,7 @@ struct GBASDLAudio { // Input size_t samples; + unsigned sampleRate; // State SDL_AudioSpec desiredSpec;