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 @@
|
Item | Brief description | For more information, see CommandLine |
Enable audio | Self-explanatory | -audio.enabled |
Volume | Self-explanatory | -audio.volume |
- Mode | Select an audio preset or choose 'custom' for manual configuration | -audio.preset |
+ Device | Use the specified audio device. | -audio.device |
+ Mode | Select an audio preset or choose 'Custom' for manual configuration. | -audio.preset |
Fragment size | The 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 |
Headroom | Number of frames to buffer before playback starts. Higher values increase latency, but reduce the potential for dropouts. | -audio.headroom |
- Buffer size | Maximum size of the audio buffer. Higher values increase maximum latency, but reduce the potential for dropouts | -audio.buffer_size |
+ Buffer size | Maximum size of the audio buffer. Higher values increase maximum latency, but reduce the potential for dropouts. | -audio.buffer_size |
Stereo for all ROMs | Enable stereo mode for all ROMs. | -audio.stereo |
Pitfall II music pitch | Defines 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'