Add volume control for the pulse audio backend. Unfortunately that can not be done with the pulse-simple api, so I had to switch to the asynchronous pulse api.

git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@6104 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
Glenn Rice 2010-08-17 02:14:04 +00:00
parent fb1c14e2cc
commit 7866fade02
6 changed files with 289 additions and 68 deletions

View File

@ -247,7 +247,7 @@ else:
conf.Define('HAVE_OPENAL', env['HAVE_OPENAL']) conf.Define('HAVE_OPENAL', env['HAVE_OPENAL'])
env['HAVE_PORTAUDIO'] = conf.CheckPortaudio(1890) env['HAVE_PORTAUDIO'] = conf.CheckPortaudio(1890)
conf.Define('HAVE_PORTAUDIO', env['HAVE_PORTAUDIO']) conf.Define('HAVE_PORTAUDIO', env['HAVE_PORTAUDIO'])
env['HAVE_PULSEAUDIO'] = conf.CheckPKG('libpulse-simple') env['HAVE_PULSEAUDIO'] = conf.CheckPKG('libpulse')
conf.Define('HAVE_PULSEAUDIO', env['HAVE_PULSEAUDIO']) conf.Define('HAVE_PULSEAUDIO', env['HAVE_PULSEAUDIO'])
env['HAVE_X11'] = conf.CheckPKG('x11') env['HAVE_X11'] = conf.CheckPKG('x11')

View File

@ -21,9 +21,11 @@
#include "PulseAudioStream.h" #include "PulseAudioStream.h"
#define BUFFER_SIZE 4096 #define BUFFER_SIZE 4096
#define BUFFER_SIZE_BYTES (BUFFER_SIZE*2*2) #define BUFFER_SIZE_BYTES (BUFFER_SIZE * 4)
PulseAudio::PulseAudio(CMixer *mixer) : SoundStream(mixer), thread_data(0), handle(NULL) PulseAudio::PulseAudio(CMixer *mixer)
: SoundStream(mixer), thread_running(false), mainloop(NULL)
, context(NULL), stream(NULL), iVolume(100)
{ {
mix_buffer = new u8[BUFFER_SIZE_BYTES]; mix_buffer = new u8[BUFFER_SIZE_BYTES];
} }
@ -33,22 +35,22 @@ PulseAudio::~PulseAudio()
delete [] mix_buffer; delete [] mix_buffer;
} }
static void *ThreadTrampoline(void *args) void *PulseAudio::ThreadTrampoline(void *args)
{ {
reinterpret_cast<PulseAudio *>(args)->SoundLoop(); ((PulseAudio *)args)->SoundLoop();
return NULL; return NULL;
} }
bool PulseAudio::Start() bool PulseAudio::Start()
{ {
thread_running = true;
thread = new Common::Thread(&ThreadTrampoline, this); thread = new Common::Thread(&ThreadTrampoline, this);
thread_data = 0;
return true; return true;
} }
void PulseAudio::Stop() void PulseAudio::Stop()
{ {
thread_data = 1; thread_running = false;
delete thread; delete thread;
thread = NULL; thread = NULL;
} }
@ -61,22 +63,16 @@ void PulseAudio::Update()
// Called on audio thread. // Called on audio thread.
void PulseAudio::SoundLoop() void PulseAudio::SoundLoop()
{ {
if (!PulseInit()) { thread_running = PulseInit();
thread_data = 2;
return; while (thread_running)
}
while (!thread_data)
{ {
int err;
int frames_to_deliver = 512; int frames_to_deliver = 512;
m_mixer->Mix(reinterpret_cast<short *>(mix_buffer), frames_to_deliver); m_mixer->Mix((short *)mix_buffer, frames_to_deliver);
if (pa_simple_write(handle, mix_buffer, frames_to_deliver * 2 * 2, &err) < 0) if (!Write(mix_buffer, frames_to_deliver * 4))
{ ERROR_LOG(AUDIO, "PulseAudio failure writing data");
ERROR_LOG(AUDIO, "pa_simple_write fail: %s", pa_strerror(err));
}
} }
PulseShutdown(); PulseShutdown();
thread_data = 2;
} }
bool PulseAudio::PulseInit() bool PulseAudio::PulseInit()
@ -88,25 +84,238 @@ bool PulseAudio::PulseInit()
2 2
}; };
int err; mainloop = pa_threaded_mainloop_new();
if (!(handle = pa_simple_new(NULL, "dolphin-emu", PA_STREAM_PLAYBACK, NULL, context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "dolphin-emu");
"emulator", &ss, NULL, NULL, &err))) pa_context_set_state_callback(context, ContextStateCB, this);
if (pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
{ {
ERROR_LOG(AUDIO, "PulseAudio open error: %s\n", pa_strerror(err)); ERROR_LOG(AUDIO, "PulseAudio failed to connect context: %s",
pa_strerror(pa_context_errno(context)));
return false; return false;
} }
NOTICE_LOG(AUDIO, "Pulse successfully initialized.\n"); pa_threaded_mainloop_lock(mainloop);
pa_threaded_mainloop_start(mainloop);
for (;;)
{
pa_context_state_t state;
state = pa_context_get_state(context);
if (state == PA_CONTEXT_READY)
break;
if (!PA_CONTEXT_IS_GOOD(state))
{
ERROR_LOG(AUDIO, "PulseAudio context state failure: %s",
pa_strerror(pa_context_errno(context)));
pa_threaded_mainloop_unlock(mainloop);
return false;
}
// Wait until the context is ready
pa_threaded_mainloop_wait(mainloop);
}
if (!(stream = pa_stream_new(context, "emulator", &ss, NULL)))
{
ERROR_LOG(AUDIO, "PulseAudio failed to create playback stream: %s",
pa_strerror(pa_context_errno(context)));
pa_threaded_mainloop_unlock(mainloop);
return false;
}
// Set callbacks for the playback stream
pa_stream_set_state_callback(stream, StreamStateCB, this);
pa_stream_set_write_callback(stream, StreamWriteCB, this);
if (pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_NOFLAGS, NULL, NULL) < 0)
{
ERROR_LOG(AUDIO, "PulseAudio failed to connect playback stream: %s",
pa_strerror(pa_context_errno(context)));
pa_threaded_mainloop_unlock(mainloop);
return false;
}
for (;;)
{
pa_stream_state_t state;
state = pa_stream_get_state(stream);
if (state == PA_STREAM_READY)
break;
if (!PA_STREAM_IS_GOOD(state))
{
ERROR_LOG(AUDIO, "PulseAudio stream state failure: %s",
pa_strerror(pa_context_errno(context)));
pa_threaded_mainloop_unlock(mainloop);
return false;
}
// Wait until the stream is ready
pa_threaded_mainloop_wait(mainloop);
}
pa_threaded_mainloop_unlock(mainloop);
SetVolume(iVolume);
NOTICE_LOG(AUDIO, "Pulse successfully initialized.");
return true; return true;
} }
void PulseAudio::PulseShutdown() void PulseAudio::PulseShutdown()
{ {
if (handle != NULL) if (mainloop)
pa_threaded_mainloop_stop(mainloop);
if (stream)
pa_stream_unref(stream);
if (context)
{ {
pa_simple_free(handle); pa_context_disconnect(context);
handle = NULL; pa_context_unref(context);
}
if (mainloop)
pa_threaded_mainloop_free(mainloop);
}
void PulseAudio::SignalMainLoop()
{
pa_threaded_mainloop_signal(mainloop, 0);
}
void PulseAudio::ContextStateCB(pa_context *c, void *userdata)
{
switch (pa_context_get_state(c))
{
case PA_CONTEXT_READY:
case PA_CONTEXT_TERMINATED:
case PA_CONTEXT_FAILED:
((PulseAudio *)userdata)->SignalMainLoop();
break;
default:
break;
} }
} }
void PulseAudio::StreamStateCB(pa_stream *s, void * userdata)
{
switch (pa_stream_get_state(s))
{
case PA_STREAM_READY:
case PA_STREAM_TERMINATED:
case PA_STREAM_FAILED:
((PulseAudio *)userdata)->SignalMainLoop();
break;
default:
break;
}
}
void PulseAudio::StreamWriteCB(pa_stream *s, size_t length, void *userdata)
{
((PulseAudio *)userdata)->SignalMainLoop();
}
static bool StateIsGood(pa_context *context, pa_stream *stream)
{
if (!context || !PA_CONTEXT_IS_GOOD(pa_context_get_state(context)) ||
!stream || !PA_STREAM_IS_GOOD(pa_stream_get_state(stream)))
{
if ((context && pa_context_get_state(context) == PA_CONTEXT_FAILED) ||
(stream && pa_stream_get_state(stream) == PA_STREAM_FAILED))
{
ERROR_LOG(AUDIO, "PulseAudio state failure: %s",
pa_strerror(pa_context_errno(context)));
}
else
{
ERROR_LOG(AUDIO, "PulseAudio state failure: %s",
pa_strerror(PA_ERR_BADSTATE));
}
return false;
}
return true;
}
bool PulseAudio::Write(const void *data, size_t length)
{
if (!data || length == 0 || !stream)
return false;
pa_threaded_mainloop_lock(mainloop);
if (!StateIsGood(context, stream))
{
pa_threaded_mainloop_unlock(mainloop);
return false;
}
while (length > 0)
{
size_t l;
int r;
while (!(l = pa_stream_writable_size(stream)))
{
pa_threaded_mainloop_wait(mainloop);
if (!StateIsGood(context, stream))
{
pa_threaded_mainloop_unlock(mainloop);
return false;
}
}
if (l == (size_t)-1)
{
ERROR_LOG(AUDIO, "PulseAudio invalid stream: %s",
pa_strerror(pa_context_errno(context)));
pa_threaded_mainloop_unlock(mainloop);
return false;
}
if (l > length)
l = length;
r = pa_stream_write(stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE);
if (r < 0)
{
ERROR_LOG(AUDIO, "PulseAudio error writing to stream: %s",
pa_strerror(pa_context_errno(context)));
pa_threaded_mainloop_unlock(mainloop);
return false;
}
data = (const uint8_t*) data + l;
length -= l;
}
pa_threaded_mainloop_unlock(mainloop);
return true;
}
void PulseAudio::SetVolume(int volume)
{
iVolume = volume;
if (!stream)
return;
pa_cvolume cvolume;
const pa_channel_map *channels = pa_stream_get_channel_map(stream);
pa_cvolume_set(&cvolume, channels->channels,
iVolume * (PA_VOLUME_NORM - PA_VOLUME_MUTED) / 100);
pa_context_set_sink_input_volume(context, pa_stream_get_index(stream),
&cvolume, NULL, this);
}

View File

@ -19,9 +19,7 @@
#define _PULSE_AUDIO_STREAM_H #define _PULSE_AUDIO_STREAM_H
#if defined(HAVE_PULSEAUDIO) && HAVE_PULSEAUDIO #if defined(HAVE_PULSEAUDIO) && HAVE_PULSEAUDIO
#include <pulse/simple.h> #include <pulse/pulseaudio.h>
#include <pulse/error.h>
#include <pulse/gccmacro.h>
#endif #endif
#include "Common.h" #include "Common.h"
@ -37,30 +35,34 @@ public:
virtual ~PulseAudio(); virtual ~PulseAudio();
virtual bool Start(); virtual bool Start();
virtual void SoundLoop();
virtual void Stop(); virtual void Stop();
virtual void SetVolume(int volume);
static bool isValid() { static bool isValid() {return true;}
return true;
} virtual bool usesMixer() const {return true;}
virtual bool usesMixer() const {
return true;
}
virtual void Update(); virtual void Update();
private: private:
virtual void SoundLoop();
static void *ThreadTrampoline(void *args);
bool PulseInit(); bool PulseInit();
void PulseShutdown(); void PulseShutdown();
bool Write(const void *data, size_t bytes);
void SignalMainLoop();
static void ContextStateCB(pa_context *c, void *userdata);
static void StreamStateCB(pa_stream *s, void * userdata);
static void StreamWriteCB(pa_stream *s, size_t length, void *userdata);
u8 *mix_buffer; u8 *mix_buffer;
Common::Thread *thread; Common::Thread *thread;
// 0 = continue volatile bool thread_running;
// 1 = shutdown
// 2 = done shutting down.
volatile int thread_data;
pa_simple *handle; pa_threaded_mainloop *mainloop;
pa_context *context;
pa_stream *stream;
int iVolume;
#else #else
public: public:
PulseAudio(CMixer *mixer) : SoundStream(mixer) {} PulseAudio(CMixer *mixer) : SoundStream(mixer) {}
@ -68,4 +70,3 @@ public:
}; };
#endif #endif

View File

@ -15,7 +15,6 @@
// Official SVN repository and contact information can be found at // Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/ // http://code.google.com/p/dolphin-emu/
#include "Config.h" #include "Config.h"
#include "ConfigDlg.h" #include "ConfigDlg.h"
@ -28,24 +27,33 @@ BEGIN_EVENT_TABLE(DSPConfigDialogHLE, wxDialog)
EVT_COMMAND_SCROLL(ID_VOLUME, DSPConfigDialogHLE::VolumeChanged) EVT_COMMAND_SCROLL(ID_VOLUME, DSPConfigDialogHLE::VolumeChanged)
END_EVENT_TABLE() END_EVENT_TABLE()
DSPConfigDialogHLE::DSPConfigDialogHLE(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &position, const wxSize& size, long style) DSPConfigDialogHLE::DSPConfigDialogHLE(wxWindow *parent, wxWindowID id,
const wxString &title, const wxPoint &position, const wxSize& size, long style)
: wxDialog(parent, id, title, position, size, style) : wxDialog(parent, id, title, position, size, style)
{ {
m_OK = new wxButton(this, wxID_OK, wxT("OK"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); wxButton *m_OK = new wxButton(this, wxID_OK, wxT("OK"),
wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator);
wxStaticBoxSizer *sbSettings = new wxStaticBoxSizer(wxVERTICAL, this, wxT("Sound Settings")); wxStaticBoxSizer *sbSettings = new wxStaticBoxSizer(wxVERTICAL, this, wxT("Sound Settings"));
wxStaticBoxSizer *sbSettingsV = new wxStaticBoxSizer(wxVERTICAL, this, wxT("Volume")); wxStaticBoxSizer *sbSettingsV = new wxStaticBoxSizer(wxVERTICAL, this, wxT("Volume"));
// Create items // Create items
m_buttonEnableHLEAudio = new wxCheckBox(this, ID_ENABLE_HLE_AUDIO, wxT("Enable HLE Audio"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); m_buttonEnableHLEAudio = new wxCheckBox(this, ID_ENABLE_HLE_AUDIO, wxT("Enable HLE Audio"),
m_buttonEnableDTKMusic = new wxCheckBox(this, ID_ENABLE_DTK_MUSIC, wxT("Enable DTK Music"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator);
m_buttonEnableThrottle = new wxCheckBox(this, ID_ENABLE_THROTTLE, wxT("Enable Audio Throttle"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); m_buttonEnableDTKMusic = new wxCheckBox(this, ID_ENABLE_DTK_MUSIC, wxT("Enable DTK Music"),
wxStaticText *BackendText = new wxStaticText(this, wxID_ANY, wxT("Audio Backend"), wxDefaultPosition, wxDefaultSize, 0); wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator);
m_BackendSelection = new wxChoice(this, ID_BACKEND, wxDefaultPosition, wxSize(110, 20), wxArrayBackends, 0, wxDefaultValidator, wxEmptyString); m_buttonEnableThrottle = new wxCheckBox(this, ID_ENABLE_THROTTLE, wxT("Enable Audio Throttle"),
wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator);
wxStaticText *BackendText = new wxStaticText(this, wxID_ANY, wxT("Audio Backend"),
wxDefaultPosition, wxDefaultSize, 0);
m_BackendSelection = new wxChoice(this, ID_BACKEND, wxDefaultPosition, wxSize(110, 20),
wxArrayBackends, 0, wxDefaultValidator, wxEmptyString);
m_volumeSlider = new wxSlider(this, ID_VOLUME, ac_Config.m_Volume, 1, 100, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE); m_volumeSlider = new wxSlider(this, ID_VOLUME, ac_Config.m_Volume, 1, 100,
wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE);
m_volumeSlider->Enable(SupportsVolumeChanges(ac_Config.sBackend)); m_volumeSlider->Enable(SupportsVolumeChanges(ac_Config.sBackend));
m_volumeText = new wxStaticText(this, wxID_ANY, wxString::Format(wxT("%d %%"), ac_Config.m_Volume), wxDefaultPosition, wxDefaultSize, 0); m_volumeText = new wxStaticText(this, wxID_ANY, wxString::Format(wxT("%d %%"),
ac_Config.m_Volume), wxDefaultPosition, wxDefaultSize, 0);
// Update values // Update values
m_buttonEnableHLEAudio->SetValue(g_Config.m_EnableHLEAudio ? true : false); m_buttonEnableHLEAudio->SetValue(g_Config.m_EnableHLEAudio ? true : false);
@ -59,8 +67,9 @@ DSPConfigDialogHLE::DSPConfigDialogHLE(wxWindow *parent, wxWindowID id, const wx
wxT("Disabling this could cause abnormal game speed, such as too fast.\n") wxT("Disabling this could cause abnormal game speed, such as too fast.\n")
wxT("But sometimes enabling this could cause constant noise.\n") wxT("But sometimes enabling this could cause constant noise.\n")
wxT("\nKeyboard Shortcut <TAB>: Hold down to instantly disable Throttle.")); wxT("\nKeyboard Shortcut <TAB>: Hold down to instantly disable Throttle."));
m_BackendSelection->SetToolTip(wxT("Changing this will have no effect while the emulator is running!")); m_BackendSelection->
m_volumeSlider->SetToolTip(wxT("This setting only affects DSound and OpenAL.")); SetToolTip(wxT("Changing this will have no effect while the emulator is running!"));
m_volumeSlider->SetToolTip(wxT("This setting only affects DSound, OpenAL, and PulseAudio."));
// Create sizer and add items to dialog // Create sizer and add items to dialog
wxBoxSizer *sMain = new wxBoxSizer(wxVERTICAL); wxBoxSizer *sMain = new wxBoxSizer(wxVERTICAL);
@ -75,14 +84,14 @@ DSPConfigDialogHLE::DSPConfigDialogHLE(wxWindow *parent, wxWindowID id, const wx
sBackend->Add(m_BackendSelection, 0, wxALL, 1); sBackend->Add(m_BackendSelection, 0, wxALL, 1);
sbSettings->Add(sBackend, 0, wxALL, 2); sbSettings->Add(sBackend, 0, wxALL, 2);
sbSettingsV->Add(m_volumeSlider, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER, 6); sbSettingsV->Add(m_volumeSlider, 1, wxLEFT|wxRIGHT|wxALIGN_CENTER, 6);
sbSettingsV->Add(m_volumeText, 0, wxALL|wxALIGN_LEFT, 4); sbSettingsV->Add(m_volumeText, 0, wxALL|wxALIGN_LEFT, 4);
sSettings->Add(sbSettings, 0, wxALL|wxEXPAND, 4); sSettings->Add(sbSettings, 0, wxALL|wxEXPAND, 4);
sSettings->Add(sbSettingsV, 0, wxALL|wxEXPAND, 4); sSettings->Add(sbSettingsV, 0, wxALL|wxEXPAND, 4);
sMain->Add(sSettings, 0, wxALL|wxEXPAND, 4); sMain->Add(sSettings, 0, wxALL|wxEXPAND, 4);
sButtons->AddStretchSpacer(); sButtons->AddStretchSpacer();
sButtons->Add(m_OK, 0, wxALL, 1); sButtons->Add(m_OK, 0, wxALL, 1);
sMain->Add(sButtons, 0, wxALL|wxEXPAND, 4); sMain->Add(sButtons, 0, wxALL|wxEXPAND, 4);
SetSizerAndFit(sMain); SetSizerAndFit(sMain);
@ -145,10 +154,12 @@ bool DSPConfigDialogHLE::SupportsVolumeChanges(std::string backend)
// but getting the backend from string etc. is probably // but getting the backend from string etc. is probably
// too much just to enable/disable a stupid slider... // too much just to enable/disable a stupid slider...
return (backend == BACKEND_DIRECTSOUND || return (backend == BACKEND_DIRECTSOUND ||
backend == BACKEND_OPENAL); backend == BACKEND_OPENAL ||
backend == BACKEND_PULSEAUDIO);
} }
void DSPConfigDialogHLE::BackendChanged(wxCommandEvent& event) void DSPConfigDialogHLE::BackendChanged(wxCommandEvent& event)
{ {
m_volumeSlider->Enable(SupportsVolumeChanges(std::string(m_BackendSelection->GetStringSelection().mb_str()))); m_volumeSlider->Enable(SupportsVolumeChanges(
std::string(m_BackendSelection->GetStringSelection().mb_str())));
} }

View File

@ -36,13 +36,12 @@ public:
virtual ~DSPConfigDialogHLE(); virtual ~DSPConfigDialogHLE();
void AddBackend(const char *backend); void AddBackend(const char *backend);
void ClearBackends(); void ClearBackends();
private: private:
DECLARE_EVENT_TABLE(); DECLARE_EVENT_TABLE();
wxSlider* m_volumeSlider; wxSlider* m_volumeSlider;
wxStaticText* m_volumeText; wxStaticText* m_volumeText;
wxButton* m_OK;
wxCheckBox* m_buttonEnableHLEAudio; wxCheckBox* m_buttonEnableHLEAudio;
wxCheckBox* m_buttonEnableDTKMusic; wxCheckBox* m_buttonEnableDTKMusic;
wxCheckBox* m_buttonEnableThrottle; wxCheckBox* m_buttonEnableThrottle;
@ -57,7 +56,7 @@ private:
ID_BACKEND, ID_BACKEND,
ID_VOLUME ID_VOLUME
}; };
void OnOK(wxCommandEvent& event); void OnOK(wxCommandEvent& event);
void SettingsChanged(wxCommandEvent& event); void SettingsChanged(wxCommandEvent& event);
void VolumeChanged(wxScrollEvent& event); void VolumeChanged(wxScrollEvent& event);

View File

@ -64,7 +64,7 @@ DSPConfigDialogLLE::DSPConfigDialogLLE(wxWindow *parent, wxWindowID id, const wx
m_buttonEnableJIT->SetToolTip(wxT("Enables dynamic recompilation of DSP code.\n") m_buttonEnableJIT->SetToolTip(wxT("Enables dynamic recompilation of DSP code.\n")
wxT("Changing this will have no effect while the emulator is running!")); wxT("Changing this will have no effect while the emulator is running!"));
m_BackendSelection->SetToolTip(wxT("Changing this will have no effect while the emulator is running!")); m_BackendSelection->SetToolTip(wxT("Changing this will have no effect while the emulator is running!"));
m_volumeSlider->SetToolTip(wxT("This setting only affects DSound and OpenAL.")); m_volumeSlider->SetToolTip(wxT("This setting only affects DSound, OpenAL, and PulseAudio."));
// Create sizer and add items to dialog // Create sizer and add items to dialog
wxBoxSizer *sMain = new wxBoxSizer(wxVERTICAL); wxBoxSizer *sMain = new wxBoxSizer(wxVERTICAL);
@ -80,7 +80,7 @@ DSPConfigDialogLLE::DSPConfigDialogLLE(wxWindow *parent, wxWindowID id, const wx
sBackend->Add(m_BackendSelection, 0, wxALL, 1); sBackend->Add(m_BackendSelection, 0, wxALL, 1);
sbSettings->Add(sBackend, 0, wxALL, 2); sbSettings->Add(sBackend, 0, wxALL, 2);
sbSettingsV->Add(m_volumeSlider, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER, 6); sbSettingsV->Add(m_volumeSlider, 1, wxLEFT|wxRIGHT|wxALIGN_CENTER, 6);
sbSettingsV->Add(m_volumeText, 0, wxALL|wxALIGN_LEFT, 4); sbSettingsV->Add(m_volumeText, 0, wxALL|wxALIGN_LEFT, 4);
sSettings->Add(sbSettings, 0, wxALL|wxEXPAND, 4); sSettings->Add(sbSettings, 0, wxALL|wxEXPAND, 4);
@ -151,7 +151,8 @@ bool DSPConfigDialogLLE::SupportsVolumeChanges(std::string backend)
// but getting the backend from string etc. is probably // but getting the backend from string etc. is probably
// too much just to enable/disable a stupid slider... // too much just to enable/disable a stupid slider...
return (backend == BACKEND_DIRECTSOUND || return (backend == BACKEND_DIRECTSOUND ||
backend == BACKEND_OPENAL); backend == BACKEND_OPENAL ||
backend == BACKEND_PULSEAUDIO);
} }
void DSPConfigDialogLLE::BackendChanged(wxCommandEvent& event) void DSPConfigDialogLLE::BackendChanged(wxCommandEvent& event)