From 75a74e0be580981011f5980e49d7eead030f2504 Mon Sep 17 00:00:00 2001 From: thrust26 Date: Mon, 27 Jul 2020 09:43:31 +0200 Subject: [PATCH] added option to select audio device (resolves #682) --- Changes.txt | 8 ++++--- docs/graphics/options_audio.png | Bin 3879 -> 4108 bytes docs/index.html | 10 +++++++-- src/common/AudioSettings.cxx | 14 +++++++++++++ src/common/AudioSettings.hxx | 6 ++++++ src/common/FrameBufferSDL2.cxx | 2 +- src/common/SoundNull.hxx | 7 +++++++ src/common/SoundSDL2.cxx | 36 ++++++++++++++++++++++++++++++-- src/common/SoundSDL2.hxx | 9 ++++++++ src/emucore/Settings.cxx | 2 ++ src/emucore/Sound.hxx | 18 ++++++++++++++++ src/gui/VideoAudioDialog.cxx | 20 +++++++++++++++++- src/gui/VideoAudioDialog.hxx | 2 ++ 13 files changed, 125 insertions(+), 9 deletions(-) diff --git a/Changes.txt b/Changes.txt index c9b902582..3db21a9ea 100644 --- a/Changes.txt +++ b/Changes.txt @@ -20,9 +20,9 @@ * Extended global hotkeys for debug options. - * Added option to playback a game using the Time Machine + * Added option to playback a game using the Time Machine. - * Allow taking snapshots from within Time Machine dialog + * Allow taking snapshots from within Time Machine dialog. * Added ability to access most files that Stella uses from within a ZIP file. This includes the following: @@ -33,7 +33,9 @@ Basically, you are now able to put many files that Stella uses inside one ZIP file, and distribute just that file. - * Replaced "Re-disassemble" with "Disassemble @ current line" in debugger + * Added option to select the audio device. + + * Replaced "Re-disassemble" with "Disassemble @ current line" in debugger. * Fix bug when taking fullscreen snapshots; the dimensions were sometimes cut off. diff --git a/docs/graphics/options_audio.png b/docs/graphics/options_audio.png index f37f07c6f33333eb2756b43a5e6fddaf2e48ad85..e46d77b87f9813ce9b3abc88ba21ded4ed05f6e1 100644 GIT binary patch delta 4015 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 IJUZe30E-gU_y7O^ delta 3784 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 O3Rqj&p02U*i2WC2PkKiH diff --git a/docs/index.html b/docs/index.html index 04829b57e..555895e6a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2201,6 +2201,11 @@ Set the volume. + +
-audio.device <number>
+ Set the audio device (0 = default). + +
-audio.preset <1 - 5>
Set an audio preset. Numbers in sequence represent presets for @@ -3084,7 +3089,8 @@ ItemBrief descriptionFor more information,
see CommandLine Enable audioSelf-explanatory-audio.enabled VolumeSelf-explanatory-audio.volume - ModeSelect an audio preset or choose 'custom' for manual configuration-audio.preset + DeviceUse the specified audio device.-audio.device + ModeSelect an audio preset or choose 'Custom' for manual configuration.-audio.preset Fragment sizeThe number of samples in a single fragment processed by the audio driver. Smaller values mean less latency, but may lead to dropouts (depending on OS and hardware).-audio.fragment_size Sample rate Output samples per second. Higher values reduce artifacts from resampling and decrease latency, @@ -3097,7 +3103,7 @@ some games (notably Quadrun). -audio.resampling_quality HeadroomNumber of frames to buffer before playback starts. Higher values increase latency, but reduce the potential for dropouts.-audio.headroom - Buffer sizeMaximum size of the audio buffer. Higher values increase maximum latency, but reduce the potential for dropouts-audio.buffer_size + Buffer sizeMaximum size of the audio buffer. Higher values increase maximum latency, but reduce the potential for dropouts.-audio.buffer_size Stereo for all ROMsEnable stereo mode for all ROMs.-audio.stereo Pitfall II music pitchDefines the pitch of Pitfall II music (which may vary between carts).-audio.dpc_pitch diff --git a/src/common/AudioSettings.cxx b/src/common/AudioSettings.cxx index 2881c4cfa..640ba9527 100644 --- a/src/common/AudioSettings.cxx +++ b/src/common/AudioSettings.cxx @@ -154,6 +154,12 @@ uInt32 AudioSettings::volume() const return lboundInt(mySettings.getInt(SETTING_VOLUME), 0); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 AudioSettings::device() const +{ + return mySettings.getInt(SETTING_DEVICE); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool AudioSettings::enabled() const { @@ -285,6 +291,14 @@ void AudioSettings::setVolume(uInt32 volume) normalize(mySettings); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::setDevice(uInt32 device) +{ + if(!myIsPersistent) return; + + mySettings.setValue(SETTING_DEVICE, device); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioSettings::setEnabled(bool isEnabled) { diff --git a/src/common/AudioSettings.hxx b/src/common/AudioSettings.hxx index 95104c74f..d319671c3 100644 --- a/src/common/AudioSettings.hxx +++ b/src/common/AudioSettings.hxx @@ -48,6 +48,7 @@ class AudioSettings static constexpr const char* SETTING_RESAMPLING_QUALITY = "audio.resampling_quality"; static constexpr const char* SETTING_STEREO = "audio.stereo"; static constexpr const char* SETTING_VOLUME = "audio.volume"; + static constexpr const char* SETTING_DEVICE = "audio.device"; static constexpr const char* SETTING_ENABLED = "audio.enabled"; static constexpr const char* SETTING_DPC_PITCH = "audio.dpc_pitch"; @@ -59,6 +60,7 @@ class AudioSettings static constexpr ResamplingQuality DEFAULT_RESAMPLING_QUALITY = ResamplingQuality::lanczos_2; static constexpr bool DEFAULT_STEREO = false; static constexpr uInt32 DEFAULT_VOLUME = 80; + static constexpr uInt32 DEFAULT_DEVICE = 0; static constexpr bool DEFAULT_ENABLED = true; static constexpr uInt32 DEFAULT_DPC_PITCH = 20000; @@ -87,6 +89,8 @@ class AudioSettings uInt32 volume() const; + uInt32 device() const; + bool enabled() const; uInt32 dpcPitch() const; @@ -109,6 +113,8 @@ class AudioSettings void setVolume(uInt32 volume); + void setDevice(uInt32 device); + void setEnabled(bool isEnabled); void setPersistent(bool isPersistent); diff --git a/src/common/FrameBufferSDL2.cxx b/src/common/FrameBufferSDL2.cxx index d930b2fc0..24b038d01 100644 --- a/src/common/FrameBufferSDL2.cxx +++ b/src/common/FrameBufferSDL2.cxx @@ -118,7 +118,7 @@ void FrameBufferSDL2::queryHardware(vector& fullscreenRes, Logger::debug(s.str()); s.str(""); lastRes = res.str(); - s << lastRes << ": "; + s << " " << lastRes << ": "; } s << mode.refresh_rate << "Hz"; if(mode.w == display.w && mode.h == display.h && mode.refresh_rate == display.refresh_rate) diff --git a/src/common/SoundNull.hxx b/src/common/SoundNull.hxx index 8974e9834..6bd9db54d 100644 --- a/src/common/SoundNull.hxx +++ b/src/common/SoundNull.hxx @@ -101,6 +101,13 @@ class SoundNull : public Sound */ void adjustVolume(int direction = 1) override { } + /** + Sets the audio device. + + @param device The number of the device to select (0 = default). + */ + void setDevice(uInt32 device) override { }; + /** This method is called to provide information about the sound device. */ diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 928ebbc44..01c2a81ba 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -56,6 +56,9 @@ SoundSDL2::SoundSDL2(OSystem& osystem, AudioSettings& audioSettings) return; } + queryHardware(myDevices); + + SDL_zero(myHardwareSpec); if(!openDevice()) return; @@ -76,6 +79,29 @@ SoundSDL2::~SoundSDL2() SDL_QuitSubSystem(SDL_INIT_AUDIO); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL2::queryHardware(VariantList& devices) +{ + ASSERT_MAIN_THREAD; + + int numDevices = SDL_GetNumAudioDevices(0); + + // log the available audio devices + ostringstream s; + s << "Supported audio devices (" << numDevices << "):"; + Logger::debug(s.str()); + + VarList::push_back(devices, "Default", 0); + for(int i = 0; i < numDevices; ++i) { + ostringstream ss; + + ss << " " << i + 1 << ": " << SDL_GetAudioDeviceName(i, 0); + Logger::debug(ss.str()); + + VarList::push_back(devices, SDL_GetAudioDeviceName(i, 0), i + 1); + } +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool SoundSDL2::openDevice() { @@ -91,7 +117,11 @@ bool SoundSDL2::openDevice() if(myIsInitializedFlag) SDL_CloseAudioDevice(myDevice); - myDevice = SDL_OpenAudioDevice(nullptr, 0, &desired, &myHardwareSpec, + + myDeviceId = BSPF::clamp(myAudioSettings.device(), 0u, uInt32(myDevices.size() - 1)); + const char* device = myDeviceId ? myDevices.at(myDeviceId).first.c_str() : nullptr; + + myDevice = SDL_OpenAudioDevice(device, 0, &desired, &myHardwareSpec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); if(myDevice == 0) @@ -126,7 +156,8 @@ void SoundSDL2::open(shared_ptr audioQueue, // Do we need to re-open the sound device? // Only do this when absolutely necessary if(myAudioSettings.sampleRate() != uInt32(myHardwareSpec.freq) || - myAudioSettings.fragmentSize() != uInt32(myHardwareSpec.samples)) + myAudioSettings.fragmentSize() != uInt32(myHardwareSpec.samples) || + myAudioSettings.device() != myDeviceId) openDevice(); myEmulationTiming = emulationTiming; @@ -261,6 +292,7 @@ string SoundSDL2::about() const ostringstream buf; buf << "Sound enabled:" << endl << " Volume: " << myVolume << "%" << endl + << " Device: " << myDevices.at(myDeviceId).first << endl << " Channels: " << uInt32(myHardwareSpec.channels) << (myAudioQueue->isStereo() ? " (Stereo)" : " (Mono)") << endl << " Preset: "; diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index 398f1fa92..5a7cd330b 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -108,6 +108,13 @@ class SoundSDL2 : public Sound string about() const override; protected: + /** + This method is called to query the audio devices. + + @param devices List of device names + */ + void queryHardware(VariantList& devices); + /** Invoked by the sound callback to process the next sound fragment. The stream is 16-bits (even though the callback is 8-bits), since @@ -139,6 +146,8 @@ class SoundSDL2 : public Sound // Audio specification structure SDL_AudioSpec myHardwareSpec; + uInt32 myDeviceId{0}; + SDL_AudioDeviceID myDevice{0}; shared_ptr myAudioQueue; diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx index 0ccc7ebd5..d3c388d64 100644 --- a/src/emucore/Settings.cxx +++ b/src/emucore/Settings.cxx @@ -80,6 +80,7 @@ Settings::Settings() // Sound options setPermanent(AudioSettings::SETTING_ENABLED, AudioSettings::DEFAULT_ENABLED); setPermanent(AudioSettings::SETTING_VOLUME, AudioSettings::DEFAULT_VOLUME); + setPermanent(AudioSettings::SETTING_DEVICE, AudioSettings::DEFAULT_DEVICE); setPermanent(AudioSettings::SETTING_PRESET, static_cast(AudioSettings::DEFAULT_PRESET)); setPermanent(AudioSettings::SETTING_FRAGMENT_SIZE, AudioSettings::DEFAULT_FRAGMENT_SIZE); setPermanent(AudioSettings::SETTING_SAMPLE_RATE, AudioSettings::DEFAULT_SAMPLE_RATE); @@ -426,6 +427,7 @@ void Settings::usage() const #ifdef SOUND_SUPPORT << " -audio.enabled <1|0> Enable audio\n" << " -audio.volume <0-100> Volume\n" + << " -audio.device ID of the audio device (0 = default)\n" << " -audio.preset <1-5> Audio preset (or 1 for custom)\n" << " -audio.sample_rate Output sample rate (44100|48000|96000)\n" << " -audio.fragment_size Fragment size (128|256|512|1024|\n" diff --git a/src/emucore/Sound.hxx b/src/emucore/Sound.hxx index 4e1fda0d5..880c47f00 100644 --- a/src/emucore/Sound.hxx +++ b/src/emucore/Sound.hxx @@ -97,10 +97,28 @@ class Sound */ virtual string about() const = 0; + /** + Get the supported devices for the audio hardware. + + @return An array of supported devices + */ + const VariantList& supportedDevices() const {return myDevices;} + + protected: + /** + This method is called to query the audio devices. + + @param devices List of device names + */ + virtual void queryHardware(VariantList& devices) = 0; + protected: // The OSystem for this sound object OSystem& myOSystem; + // Supported device + VariantList myDevices; + private: // Following constructors and assignment operators not supported Sound() = delete; diff --git a/src/gui/VideoAudioDialog.cxx b/src/gui/VideoAudioDialog.cxx index e87cbe221..edb40c7bb 100644 --- a/src/gui/VideoAudioDialog.cxx +++ b/src/gui/VideoAudioDialog.cxx @@ -68,7 +68,7 @@ VideoAudioDialog::VideoAudioDialog(OSystem& osystem, DialogContainer& parent, // Set real dimensions setSize(44 * fontWidth + HBORDER * 2 + PopUpWidget::dropDownWidth(font) * 2, - _th + VGAP * 6 + lineHeight + 10 * (lineHeight + VGAP) + buttonHeight + VBORDER * 3, + _th + VGAP * 3 + lineHeight + 11 * (lineHeight + VGAP) + buttonHeight + VBORDER * 3, max_w, max_h); // The tab widget @@ -365,6 +365,14 @@ void VideoAudioDialog::addAudioTab() wid.push_back(myVolumeSlider); ypos += lineHeight + VGAP; + // Device + myDevicePopup = new PopUpWidget(myTab, _font, xpos, ypos, + _w - xpos - lwidth - HBORDER - PopUpWidget::dropDownWidth(_font) - 2, lineHeight, + instance().sound().supportedDevices(), + "Device", lwidth, kDeviceChanged); + wid.push_back(myDevicePopup); + ypos += lineHeight + VGAP; + // Mode items.clear(); VarList::push_back(items, "Low quality, medium lag", static_cast(AudioSettings::Preset::lowQualityMediumLag)); @@ -542,6 +550,11 @@ void VideoAudioDialog::loadConfig() // Volume myVolumeSlider->setValue(audioSettings.volume()); + // Device + uInt32 deviceId = BSPF::clamp(audioSettings.device(), 0u, + uInt32(instance().sound().supportedDevices().size() - 1)); + myDevicePopup->setSelected(deviceId); + // Stereo myStereoSoundCheckbox->setState(audioSettings.stereo()); @@ -666,6 +679,9 @@ void VideoAudioDialog::saveConfig() audioSettings.setVolume(myVolumeSlider->getValue()); instance().sound().setVolume(myVolumeSlider->getValue()); + // Device + audioSettings.setDevice(myDevicePopup->getSelected()); + // Stereo audioSettings.setStereo(myStereoSoundCheckbox->getState()); @@ -754,6 +770,7 @@ void VideoAudioDialog::setDefaults() case 3: // Audio mySoundEnableCheckbox->setState(AudioSettings::DEFAULT_ENABLED); myVolumeSlider->setValue(AudioSettings::DEFAULT_VOLUME); + myDevicePopup->setSelected(AudioSettings::DEFAULT_DEVICE); myStereoSoundCheckbox->setState(AudioSettings::DEFAULT_STEREO); myDpcPitch->setValue(AudioSettings::DEFAULT_DPC_PITCH); myModePopup->setSelected(static_cast(AudioSettings::DEFAULT_PRESET)); @@ -1061,6 +1078,7 @@ void VideoAudioDialog::updateEnabledState() bool userMode = preset == AudioSettings::Preset::custom; myVolumeSlider->setEnabled(active); + myDevicePopup->setEnabled(active); myStereoSoundCheckbox->setEnabled(active); myModePopup->setEnabled(active); // enable only for Pitfall II cart diff --git a/src/gui/VideoAudioDialog.hxx b/src/gui/VideoAudioDialog.hxx index 0961f776e..ef0f850a8 100644 --- a/src/gui/VideoAudioDialog.hxx +++ b/src/gui/VideoAudioDialog.hxx @@ -115,6 +115,7 @@ class VideoAudioDialog : public Dialog // Audio CheckboxWidget* mySoundEnableCheckbox{nullptr}; SliderWidget* myVolumeSlider{nullptr}; + PopUpWidget* myDevicePopup{nullptr}; CheckboxWidget* myStereoSoundCheckbox{nullptr}; PopUpWidget* myModePopup{nullptr}; PopUpWidget* myFragsizePopup{nullptr}; @@ -149,6 +150,7 @@ class VideoAudioDialog : public Dialog kScanlinesChanged = 'VDsc', kSoundEnableChanged = 'ADse', + kDeviceChanged = 'ADdc', kModeChanged = 'ADmc', kHeadroomChanged = 'ADhc', kBufferSizeChanged = 'ADbc'