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 f37f07c6f..e46d77b87 100644 Binary files a/docs/graphics/options_audio.png and b/docs/graphics/options_audio.png differ 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'