diff --git a/apu/apu.cpp b/apu/apu.cpp
index 8b6235a8..fc4390a7 100644
--- a/apu/apu.cpp
+++ b/apu/apu.cpp
@@ -199,7 +199,7 @@
#include "snes/snes.hpp"
-#define APU_DEFAULT_INPUT_RATE 32000
+#define APU_DEFAULT_INPUT_RATE 31987 // 60hz
#define APU_MINIMUM_SAMPLE_COUNT 512
#define APU_MINIMUM_SAMPLE_BLOCK 128
#define APU_NUMERATOR_NTSC 15664
@@ -240,6 +240,8 @@ namespace spc
if necessary on game load. */
static uint32 ratio_numerator = APU_NUMERATOR_NTSC;
static uint32 ratio_denominator = APU_DENOMINATOR_NTSC;
+
+ static double dynamic_rate_multiplier = 1.0;
}
namespace msu
@@ -274,7 +276,7 @@ static void DeStereo (uint8 *buffer, int sample_count)
int16 *buf = (int16 *) buffer;
int32 s1, s2;
- for (int i = 0; i < sample_count >> 1; i++)
+ for (int i = 0; i < (sample_count >> 1); i++)
{
s1 = (int32) buf[2 * i];
s2 = (int32) buf[2 * i + 1];
@@ -348,7 +350,7 @@ bool8 S9xMixSamples (uint8 *buffer, int sample_count)
if (msu::resampler->avail() >= sample_count)
{
msu::resampler->read((short *)msu::resample_buffer, sample_count);
- for (uint32 i = 0; i < sample_count; ++i)
+ for (int i = 0; i < sample_count; ++i)
*((int16*)(dest+(i * 2))) += *((int16*)(msu::resample_buffer +(i * 2)));
}
else // should never occur
@@ -471,12 +473,28 @@ void S9xSetSamplesAvailableCallback (apu_callback callback, void *data)
spc::extra_data = data;
}
+void S9xUpdateDynamicRate (int avail, int buffer_size)
+{
+ int half_size = buffer_size / 2;
+ int delta_mid = avail - half_size;
+ double direction = (double) delta_mid / half_size;
+ spc::dynamic_rate_multiplier = 1.0 - (Settings.DynamicRateLimit / 100000.0) * direction;
+
+ UpdatePlaybackRate();
+}
+
static void UpdatePlaybackRate (void)
{
if (Settings.SoundInputRate == 0)
Settings.SoundInputRate = APU_DEFAULT_INPUT_RATE;
double time_ratio = (double) Settings.SoundInputRate * spc::timing_hack_numerator / (Settings.SoundPlaybackRate * spc::timing_hack_denominator);
+
+ if (Settings.DynamicRateControl)
+ {
+ time_ratio *= spc::dynamic_rate_multiplier;
+ }
+
spc::resampler->time_ratio(time_ratio);
if (Settings.MSU1)
diff --git a/apu/apu.h b/apu/apu.h
index 2ffd7049..f1462a0f 100644
--- a/apu/apu.h
+++ b/apu/apu.h
@@ -229,5 +229,6 @@ void S9xFinalizeSamples (void);
void S9xClearSamples (void);
bool8 S9xMixSamples (uint8 *, int);
void S9xSetSamplesAvailableCallback (apu_callback, void *);
+void S9xUpdateDynamicRate (int, int);
#endif
diff --git a/apu/hermite_resampler.h b/apu/hermite_resampler.h
index ea526ef1..c2a175c5 100644
--- a/apu/hermite_resampler.h
+++ b/apu/hermite_resampler.h
@@ -52,7 +52,6 @@ class HermiteResampler : public Resampler
time_ratio (double ratio)
{
r_step = ratio;
- clear ();
}
void
@@ -83,7 +82,7 @@ class HermiteResampler : public Resampler
while (r_frac <= 1.0 && o_position < num_samples)
{
hermite_val[0] = hermite (r_frac, r_left [0], r_left [1], r_left [2], r_left [3]);
- hermite_val[1] = hermite (r_frac, r_right[0], r_right[1], r_right[2], r_right[3]);
+ hermite_val[1] = hermite (r_frac, r_right[0], r_right[1], r_right[2], r_right[3]);
data[o_position] = SHORT_CLAMP (hermite_val[0]);
data[o_position + 1] = SHORT_CLAMP (hermite_val[1]);
diff --git a/gtk/src/gtk_builder_window.cpp b/gtk/src/gtk_builder_window.cpp
index 50d08113..4c739318 100644
--- a/gtk/src/gtk_builder_window.cpp
+++ b/gtk/src/gtk_builder_window.cpp
@@ -226,7 +226,7 @@ GtkBuilderWindow::set_combo (const char *name, unsigned char value)
}
void
-GtkBuilderWindow::set_spin (const char *name, unsigned int value)
+GtkBuilderWindow::set_spin (const char *name, double value)
{
gtk_spin_button_set_value (GTK_SPIN_BUTTON (get_widget (name)),
(double) value);
@@ -260,11 +260,10 @@ GtkBuilderWindow::get_window (void)
return GTK_WINDOW (window);
}
-unsigned int
+double
GtkBuilderWindow::get_spin (const char *name)
{
- return (unsigned int)
- gtk_spin_button_get_value (GTK_SPIN_BUTTON (get_widget (name)));
+ return gtk_spin_button_get_value (GTK_SPIN_BUTTON (get_widget (name)));
}
int
diff --git a/gtk/src/gtk_builder_window.h b/gtk/src/gtk_builder_window.h
index 2fe0d02b..9ef9a0d7 100644
--- a/gtk/src/gtk_builder_window.h
+++ b/gtk/src/gtk_builder_window.h
@@ -30,13 +30,13 @@ class GtkBuilderWindow
unsigned char get_combo (const char *name);
void combo_box_append (const char *name, const char *value);
void combo_box_append (GtkComboBox *combo, const char *value);
- unsigned int get_spin (const char *name);
+ double get_spin (const char *name);
float get_slider (const char *name);
void set_check (const char *name, unsigned char value);
void set_entry_value (const char *name, unsigned int value);
void set_entry_text (const char *name, const char *text);
void set_combo (const char *name, unsigned char value);
- void set_spin (const char *name, unsigned int value);
+ void set_spin (const char *name, double value);
void set_slider (const char *name, float value);
int has_focus (const char *widget);
diff --git a/gtk/src/gtk_config.cpp b/gtk/src/gtk_config.cpp
index b4db218c..3c37482d 100644
--- a/gtk/src/gtk_config.cpp
+++ b/gtk/src/gtk_config.cpp
@@ -253,6 +253,8 @@ Snes9xConfig::load_defaults (void)
Settings.FrameTime = Settings.FrameTimeNTSC;
Settings.BlockInvalidVRAMAccessMaster = TRUE;
Settings.SoundSync = 1;
+ Settings.DynamicRateControl = 0;
+ Settings.DynamicRateLimit = 1000;
Settings.HDMATimingHack = 100;
Settings.ApplyCheats = 1;
@@ -402,6 +404,8 @@ Snes9xConfig::save_config_file (void)
xml_out_int (xml, "sound_driver", sound_driver);
xml_out_int (xml, "sound_input_rate", sound_input_rate);
xml_out_int (xml, "sound_sync", Settings.SoundSync);
+ xml_out_int (xml, "dynamic_rate_control", Settings.DynamicRateControl);
+ xml_out_int (xml, "dynamic_rate_limit", Settings.DynamicRateLimit);
/* Snes9X core-stored variables */
xml_out_int (xml, "transparency", Settings.Transparency);
@@ -664,6 +668,14 @@ Snes9xConfig::set_option (const char *name, const char *value)
{
Settings.ReverseStereo = atoi (value);
}
+ else if (!strcasecmp (name, "dynamic_rate_control"))
+ {
+ Settings.DynamicRateControl = atoi (value);
+ }
+ else if (!strcasecmp (name, "dynamic_rate_limit"))
+ {
+ Settings.DynamicRateLimit = atoi (value);
+ }
else if (!strcasecmp (name, "gaussian_interpolation"))
{
}
diff --git a/gtk/src/gtk_preferences.cpp b/gtk/src/gtk_preferences.cpp
index ec66c495..2ff70968 100644
--- a/gtk/src/gtk_preferences.cpp
+++ b/gtk/src/gtk_preferences.cpp
@@ -661,6 +661,8 @@ Snes9xPreferences::move_settings_to_dialog (void)
set_spin ("sound_buffer_size", config->sound_buffer_size);
set_slider ("sound_input_rate", config->sound_input_rate);
set_check ("sync_sound", Settings.SoundSync);
+ set_check ("dynamic_rate_control", Settings.DynamicRateControl);
+ set_spin ("dynamic_rate_limit", Settings.DynamicRateLimit / 100000.0);
set_spin ("rewind_buffer_size", config->rewind_buffer_size);
set_spin ("rewind_granularity", config->rewind_granularity);
@@ -816,6 +818,8 @@ Snes9xPreferences::get_settings_from_dialog (void)
Settings.SoundSync = get_check ("sync_sound");
config->mute_sound = get_check ("mute_sound_check");
config->mute_sound_turbo = get_check ("mute_sound_turbo_check");
+ Settings.DynamicRateControl = get_check ("dynamic_rate_control");
+ Settings.DynamicRateLimit = (uint32) (get_spin ("dynamic_rate_limit") * 100000);
store_ntsc_settings ();
config->ntsc_scanline_intensity = get_combo ("ntsc_scanline_intensity");
diff --git a/gtk/src/gtk_sound_driver_alsa.cpp b/gtk/src/gtk_sound_driver_alsa.cpp
index a49e9db6..bfa08b9a 100644
--- a/gtk/src/gtk_sound_driver_alsa.cpp
+++ b/gtk/src/gtk_sound_driver_alsa.cpp
@@ -65,7 +65,10 @@ bool8
S9xAlsaSoundDriver::open_device (void)
{
int err;
+ unsigned int periods = 8;
+ unsigned int buffer_size = gui_config->sound_buffer_size * 1000;
snd_pcm_sw_params_t *sw_params;
+ snd_pcm_hw_params_t *hw_params;
snd_pcm_uframes_t alsa_buffer_size, alsa_period_size;
printf ("ALSA sound driver initializing...\n");
@@ -89,28 +92,28 @@ S9xAlsaSoundDriver::open_device (void)
Settings.SoundPlaybackRate,
gui_config->sound_buffer_size);
- if ((err = snd_pcm_set_params (pcm,
- Settings.SixteenBitSound ? SND_PCM_FORMAT_S16 : SND_PCM_FORMAT_U8,
- SND_PCM_ACCESS_RW_INTERLEAVED,
- Settings.Stereo ? 2 : 1,
- Settings.SoundPlaybackRate,
- 1 /* Allow software resampling */,
- gui_config->sound_buffer_size * 1000))
- < 0)
- {
+ snd_pcm_hw_params_alloca (&hw_params);
+ snd_pcm_hw_params_any (pcm, hw_params);
+ snd_pcm_hw_params_set_format (pcm, hw_params, Settings.SixteenBitSound ? SND_PCM_FORMAT_S16 : SND_PCM_FORMAT_U8);
+ snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
+ snd_pcm_hw_params_set_channels (pcm, hw_params, Settings.Stereo ? 2 : 1);
+ snd_pcm_hw_params_set_rate_near (pcm, hw_params, &Settings.SoundPlaybackRate, NULL);
+ snd_pcm_hw_params_set_rate_resample (pcm, hw_params, 0);
+ snd_pcm_hw_params_set_buffer_time_near (pcm, hw_params, &buffer_size, NULL);
+ snd_pcm_hw_params_set_periods_near (pcm, hw_params, &periods, NULL);
+
+ if ((err = snd_pcm_hw_params (pcm, hw_params)) < 0)
goto close_fail;
- }
snd_pcm_sw_params_alloca (&sw_params);
snd_pcm_sw_params_current (pcm, sw_params);
snd_pcm_get_params (pcm, &alsa_buffer_size, &alsa_period_size);
/* Don't start until we're [nearly] full */
- snd_pcm_sw_params_set_start_threshold (pcm,
- sw_params,
- (alsa_buffer_size / alsa_period_size) * alsa_period_size);
- /* Transfer in blocks of period-size */
- snd_pcm_sw_params_set_avail_min (pcm, sw_params, alsa_period_size);
+ snd_pcm_sw_params_set_start_threshold (pcm, sw_params, (alsa_buffer_size / 2));
err = snd_pcm_sw_params (pcm, sw_params);
+
+ output_buffer_size = snd_pcm_frames_to_bytes (pcm, alsa_buffer_size);
+
if (err < 0)
goto close_fail;
@@ -143,17 +146,37 @@ S9xAlsaSoundDriver::samples_available (void)
snd_pcm_sframes_t frames_written, frames;
int bytes;
- S9xFinalizeSamples ();
+ frames = snd_pcm_avail (pcm);
+
+ if (Settings.DynamicRateControl)
+ {
+ S9xUpdateDynamicRate (snd_pcm_frames_to_bytes (pcm, frames),
+ output_buffer_size);
+ }
- frames = snd_pcm_avail_update (pcm);
if (frames < 0)
{
frames = snd_pcm_recover (pcm, frames, 1);
+ return;
+ }
+
+ S9xFinalizeSamples ();
+
+ if (Settings.DynamicRateControl)
+ {
+ // Using rate control, we should always keep the emulator's sound buffers empty to
+ // maintain an accurate measurement.
+ if (frames < (S9xGetSampleCount () >> (Settings.Stereo ? 1 : 0)))
+ {
+ S9xClearSamples ();
+ return;
+ }
}
frames = MIN (frames, S9xGetSampleCount () >> (Settings.Stereo ? 1 : 0));
bytes = snd_pcm_frames_to_bytes (pcm, frames);
+
if (bytes <= 0)
{
return;
diff --git a/gtk/src/gtk_sound_driver_alsa.h b/gtk/src/gtk_sound_driver_alsa.h
index d7dab5bc..0c4069ca 100644
--- a/gtk/src/gtk_sound_driver_alsa.h
+++ b/gtk/src/gtk_sound_driver_alsa.h
@@ -21,6 +21,7 @@ class S9xAlsaSoundDriver : public S9xSoundDriver
snd_pcm_t *pcm;
int sound_buffer_size;
uint8 *sound_buffer;
+ int output_buffer_size;
};
#endif /* __GTK_SOUND_DRIVER_ALSA_H */
diff --git a/gtk/src/gtk_sound_driver_oss.cpp b/gtk/src/gtk_sound_driver_oss.cpp
index f7527941..0fde3b5c 100644
--- a/gtk/src/gtk_sound_driver_oss.cpp
+++ b/gtk/src/gtk_sound_driver_oss.cpp
@@ -64,7 +64,7 @@ bool8
S9xOSSSoundDriver::open_device (void)
{
int temp;
- int output_buffer_size;
+ audio_buf_info info;
output_buffer_size = (gui_config->sound_buffer_size * Settings.SoundPlaybackRate) / 1000;
@@ -72,8 +72,6 @@ S9xOSSSoundDriver::open_device (void)
output_buffer_size *= 2;
if (Settings.SixteenBitSound)
output_buffer_size *= 2;
- if (output_buffer_size > 65536)
- output_buffer_size = 65536;
if (output_buffer_size < 256)
output_buffer_size = 256;
@@ -84,7 +82,13 @@ S9xOSSSoundDriver::open_device (void)
filedes = open ("/dev/dsp", O_WRONLY | O_NONBLOCK);
if (filedes < 0)
- goto fail;
+ {
+ printf ("Failed\n --> (Device: /dev/dsp1)...");
+ filedes = open ("/dev/dsp1", O_WRONLY | O_NONBLOCK);
+
+ if (filedes < 0)
+ goto fail;
+ }
printf ("OK\n");
@@ -132,9 +136,14 @@ S9xOSSSoundDriver::open_device (void)
/* OSS requires a power-of-two buffer size, first 16 bits are the number
* of fragments to generate, second 16 are the respective power-of-two. */
- temp = (2 << 16) | (S9xSoundBase2log (output_buffer_size));
+ temp = (8 << 16) | (S9xSoundBase2log (output_buffer_size / 8));
- output_buffer_size = S9xSoundPowerof2 (temp & 0xffff);
+ if (ioctl (filedes, SNDCTL_DSP_SETFRAGMENT, &temp) < 0)
+ goto close_fail;
+
+ ioctl (filedes, SNDCTL_DSP_GETOSPACE, &info);
+
+ output_buffer_size = info.fragsize * info.fragstotal;
printf (" --> (Buffer size: %d bytes, %dms latency)...",
output_buffer_size,
@@ -142,9 +151,6 @@ S9xOSSSoundDriver::open_device (void)
>> (Settings.SixteenBitSound ? 1 : 0))
/ (Settings.SoundPlaybackRate));
- if (ioctl (filedes, SNDCTL_DSP_SETFRAGMENT, &temp) < 0)
- goto close_fail;
-
printf ("OK\n");
S9xSetSamplesAvailableCallback (oss_samples_available, this);
@@ -175,11 +181,27 @@ S9xOSSSoundDriver::samples_available (void)
int bytes_to_write;
int bytes_written;
+ ioctl (filedes, SNDCTL_DSP_GETOSPACE, &info);
+
+ if (Settings.DynamicRateControl)
+ {
+ S9xUpdateDynamicRate (info.bytes, output_buffer_size);
+ }
+
S9xFinalizeSamples ();
samples_to_write = S9xGetSampleCount ();
- ioctl (filedes, SNDCTL_DSP_GETOSPACE, &info);
+ if (Settings.DynamicRateControl)
+ {
+ // Using rate control, we should always keep the emulator's sound buffers empty to
+ // maintain an accurate measurement.
+ if (samples_to_write > (info.bytes >> (Settings.SixteenBitSound ? 1 : 0)))
+ {
+ S9xClearSamples ();
+ return;
+ }
+ }
samples_to_write = MIN (info.bytes >> (Settings.SixteenBitSound ? 1 : 0),
samples_to_write);
diff --git a/gtk/src/gtk_sound_driver_oss.h b/gtk/src/gtk_sound_driver_oss.h
index 8ae772df..fb35b16c 100644
--- a/gtk/src/gtk_sound_driver_oss.h
+++ b/gtk/src/gtk_sound_driver_oss.h
@@ -20,6 +20,7 @@ class S9xOSSSoundDriver : public S9xSoundDriver
int filedes;
uint8 *sound_buffer;
int sound_buffer_size;
+ int output_buffer_size;
};
diff --git a/gtk/src/gtk_sound_driver_portaudio.cpp b/gtk/src/gtk_sound_driver_portaudio.cpp
index 295055ab..06d264d7 100644
--- a/gtk/src/gtk_sound_driver_portaudio.cpp
+++ b/gtk/src/gtk_sound_driver_portaudio.cpp
@@ -1,17 +1,10 @@
#include "gtk_s9x.h"
#include "gtk_sound_driver_portaudio.h"
-static int
-port_audio_callback (const void *input,
- void *output,
- unsigned long frameCount,
- const PaStreamCallbackTimeInfo* timeInfo,
- PaStreamCallbackFlags statusFlags,
- void *userData)
+static inline int
+frames_to_bytes (int frames)
{
- ((S9xPortAudioSoundDriver *) userData)->mix ((unsigned char *) output, frameCount * (Settings.Stereo ? 2 : 1) * (Settings.SixteenBitSound ? 2 : 1));
-
- return 0;
+ return (frames * (Settings.SixteenBitSound ? 2 : 1) * (Settings.Stereo ? 2 : 1));
}
static void
@@ -25,19 +18,14 @@ port_audio_samples_available_callback (void *data)
void
S9xPortAudioSoundDriver::mix (unsigned char *output, int bytes)
{
- g_mutex_lock (mutex);
-
- S9xMixSamples (output, bytes >> (Settings.SixteenBitSound ? 1 : 0));
-
- g_mutex_unlock (mutex);
-
return;
}
S9xPortAudioSoundDriver::S9xPortAudioSoundDriver(void)
{
audio_stream = NULL;
- mutex = NULL;
+ sound_buffer = NULL;
+ sound_buffer_size = 0;
return;
}
@@ -62,16 +50,16 @@ S9xPortAudioSoundDriver::terminate (void)
{
stop ();
- if (mutex)
- {
- g_mutex_free (mutex);
- mutex = NULL;
- }
-
S9xSetSamplesAvailableCallback (NULL, NULL);
Pa_Terminate ();
+ if (sound_buffer)
+ {
+ free (sound_buffer);
+ sound_buffer = NULL;
+ }
+
return;
}
@@ -173,8 +161,11 @@ S9xPortAudioSoundDriver::open_device (void)
Settings.SoundPlaybackRate,
0,
paNoFlag,
- port_audio_callback,
- this);
+ NULL,
+ NULL);
+
+ int frames = Pa_GetStreamWriteAvailable (audio_stream);
+ output_buffer_size = frames_to_bytes (frames);
if (err == paNoError)
{
@@ -195,7 +186,6 @@ S9xPortAudioSoundDriver::open_device (void)
return FALSE;
}
- mutex = g_mutex_new ();
S9xSetSamplesAvailableCallback (port_audio_samples_available_callback, this);
fflush (stdout);
@@ -207,11 +197,41 @@ S9xPortAudioSoundDriver::open_device (void)
void
S9xPortAudioSoundDriver::samples_available (void)
{
- g_mutex_lock (mutex);
+ int frames;
+ int bytes;
+
+ frames = Pa_GetStreamWriteAvailable (audio_stream);
+
+ if (Settings.DynamicRateControl)
+ {
+ S9xUpdateDynamicRate (frames_to_bytes (frames), output_buffer_size);
+ }
S9xFinalizeSamples ();
- g_mutex_unlock (mutex);
+ if (Settings.DynamicRateControl)
+ {
+ // Using rate control, we should always keep the emulator's sound buffers empty to
+ // maintain an accurate measurement.
+ if (frames < (S9xGetSampleCount () >> (Settings.Stereo ? 1 : 0)))
+ {
+ S9xClearSamples ();
+ return;
+ }
+ }
+
+ frames = MIN (frames, S9xGetSampleCount () >> (Settings.Stereo ? 1 : 0));
+ bytes = frames_to_bytes (frames);
+
+ if (sound_buffer_size < bytes || sound_buffer == NULL)
+ {
+ sound_buffer = (uint8 *) realloc (sound_buffer, bytes);
+ sound_buffer_size = bytes;
+ }
+
+ S9xMixSamples (sound_buffer, frames << (Settings.Stereo ? 1 : 0));
+
+ Pa_WriteStream (audio_stream, sound_buffer, frames);
return;
}
diff --git a/gtk/src/gtk_sound_driver_portaudio.h b/gtk/src/gtk_sound_driver_portaudio.h
index 1fc79645..1de5485f 100644
--- a/gtk/src/gtk_sound_driver_portaudio.h
+++ b/gtk/src/gtk_sound_driver_portaudio.h
@@ -22,7 +22,9 @@ class S9xPortAudioSoundDriver : public S9xSoundDriver
private:
PaStream *audio_stream;
- GMutex *mutex;
+ int sound_buffer_size;
+ uint8 *sound_buffer;
+ int output_buffer_size;
};
diff --git a/gtk/src/gtk_sound_driver_pulse.cpp b/gtk/src/gtk_sound_driver_pulse.cpp
index 4331a945..07f6727e 100644
--- a/gtk/src/gtk_sound_driver_pulse.cpp
+++ b/gtk/src/gtk_sound_driver_pulse.cpp
@@ -13,8 +13,9 @@ pulse_samples_available (void *data)
S9xPulseSoundDriver::S9xPulseSoundDriver (void)
{
- pulse = NULL;
- buffer = NULL;
+ mainloop = NULL;
+ context = NULL;
+ stream = NULL;
buffer_size = 0;
return;
@@ -29,19 +30,24 @@ S9xPulseSoundDriver::init (void)
void
S9xPulseSoundDriver::terminate (void)
{
- stop ();
-
S9xSetSamplesAvailableCallback (NULL, NULL);
- if (pulse)
+ if (stream)
{
- pa_simple_free (pulse);
+ pa_stream_disconnect (stream);
+ pa_stream_unref (stream);
}
- if (buffer)
+ if (context)
{
- delete[] buffer;
- buffer = NULL;
+ pa_context_disconnect (context);
+ pa_context_unref (context);
+ }
+
+ if (mainloop)
+ {
+ pa_threaded_mainloop_stop (mainloop);
+ pa_threaded_mainloop_free (mainloop);
}
return;
@@ -59,12 +65,73 @@ S9xPulseSoundDriver::stop (void)
return;
}
+void
+S9xPulseSoundDriver::lock (void)
+{
+ pa_threaded_mainloop_lock (mainloop);
+
+ return;
+}
+
+void
+S9xPulseSoundDriver::unlock (void)
+{
+ pa_threaded_mainloop_unlock (mainloop);
+
+ return;
+}
+
+void
+S9xPulseSoundDriver::wait (void)
+{
+ pa_threaded_mainloop_wait (mainloop);
+
+ return;
+}
+
+static void
+context_state_cb (pa_context *c, void *userdata)
+{
+ S9xPulseSoundDriver *driver = (S9xPulseSoundDriver *) userdata;
+ int state;
+
+ state = pa_context_get_state (c);
+
+ if (state == PA_CONTEXT_READY ||
+ state == PA_CONTEXT_FAILED ||
+ state == PA_CONTEXT_TERMINATED)
+ {
+ pa_threaded_mainloop_signal (driver->mainloop, 0);
+ }
+
+ return;
+}
+
+static void
+stream_state_callback (pa_stream *p, void *userdata)
+{
+ S9xPulseSoundDriver *driver = (S9xPulseSoundDriver *) userdata;
+ int state;
+
+ state = pa_stream_get_state (p);
+
+ if (state == PA_STREAM_READY ||
+ state == PA_STREAM_FAILED ||
+ state == PA_STREAM_TERMINATED)
+ {
+ pa_threaded_mainloop_signal (driver->mainloop, 0);
+ }
+
+ return;
+}
+
bool8
S9xPulseSoundDriver::open_device (void)
{
- int err;
+ int err = PA_ERR_UNKNOWN;
pa_sample_spec ss;
pa_buffer_attr buffer_attr;
+ const pa_buffer_attr *actual_buffer_attr;
ss.channels = Settings.Stereo ? 2 : 1;
ss.format = Settings.SixteenBitSound ? PA_SAMPLE_S16NE : PA_SAMPLE_U8;
@@ -76,35 +143,88 @@ S9xPulseSoundDriver::open_device (void)
buffer_attr.minreq = -1;
buffer_attr.prebuf = -1;
- printf ("PulseAudio sound driver initializing...\n");
+ printf ("PulseAudio sound driver initializing...");
- printf (" --> (%dhz, %s %s, %dms)...",
+ printf (" --> (%dhz, %s %s, %dms)...\n",
Settings.SoundPlaybackRate,
Settings.SixteenBitSound ? "16-bit" : "8-bit",
Settings.Stereo ? "Stereo" : "Mono",
gui_config->sound_buffer_size);
+ fflush (stdout);
- pulse = pa_simple_new (NULL,
- "Snes9x",
- PA_STREAM_PLAYBACK,
- NULL,
- "Game",
- &ss,
- NULL,
- &buffer_attr,
- &err);
-
- if (!pulse)
+ mainloop = pa_threaded_mainloop_new ();
+ if (!mainloop)
{
- printf ("Failed: %s\n", pa_strerror (err));
- return FALSE;
+ fprintf (stderr, "Failed to create mainloop.\n");
+ goto error0;
}
+ context = pa_context_new (pa_threaded_mainloop_get_api (mainloop), "Snes9x");
+ if (!context)
+ goto error1;
+
+ pa_context_set_state_callback (context, context_state_cb, this);
+ if ((err = pa_context_connect (context, NULL, PA_CONTEXT_NOFLAGS, NULL)) != 0)
+ goto error2;
+
+ lock ();
+
+ if ((err = pa_threaded_mainloop_start (mainloop)) != 0)
+ goto error2;
+ wait ();
+
+ if ((err = pa_context_get_state (context)) != PA_CONTEXT_READY)
+ {
+ printf ("Coundn't create context: State: %d\n", err);
+ goto error2;
+ }
+
+ stream = pa_stream_new (context, "Game", &ss, NULL);
+
+ if (!stream)
+ goto error2;
+
+ pa_stream_set_state_callback (stream, stream_state_callback, this);
+
+ if (pa_stream_connect_playback (stream,
+ NULL,
+ &buffer_attr,
+ PA_STREAM_ADJUST_LATENCY,
+ NULL,
+ NULL) < 0)
+ goto error3;
+ wait ();
+
+ if (pa_stream_get_state (stream) != PA_STREAM_READY)
+ {
+ goto error3;
+ }
+
+ actual_buffer_attr = pa_stream_get_buffer_attr (stream);
+
+ buffer_size = actual_buffer_attr->tlength;
+
printf ("OK\n");
S9xSetSamplesAvailableCallback (pulse_samples_available, this);
+ unlock ();
+
return TRUE;
+
+error3:
+ pa_stream_disconnect (stream);
+ pa_stream_unref (stream);
+error2:
+ pa_context_disconnect (context);
+ pa_context_unref (context);
+ unlock ();
+error1:
+ pa_threaded_mainloop_free (mainloop);
+error0:
+ printf ("Failed: %s\n", pa_strerror (err));
+
+ return FALSE;
}
void
@@ -116,30 +236,45 @@ S9xPulseSoundDriver::mix (void)
void
S9xPulseSoundDriver::samples_available (void)
{
- int bytes, err;
+ size_t bytes;
+ int samples;
+ const pa_buffer_attr *buffer_attr;
+ void *output_buffer = NULL;
+
+ lock ();
+ bytes = pa_stream_writable_size (stream);
+ buffer_attr = pa_stream_get_buffer_attr (stream);
+ unlock ();
+
+ buffer_size = buffer_attr->tlength;
+
+ if (Settings.DynamicRateControl)
+ {
+ S9xUpdateDynamicRate (bytes, buffer_size);
+ }
S9xFinalizeSamples ();
- bytes = (S9xGetSampleCount () << (Settings.SixteenBitSound ? 1 : 0));
+ samples = S9xGetSampleCount ();
- if (bytes <= 128)
- return;
-
- if (buffer_size < bytes || buffer == NULL)
+ if (Settings.DynamicRateControl)
{
- delete[] buffer;
- buffer = new uint8[bytes];
- buffer_size = bytes;
+ if ((int) bytes < (samples << (Settings.SixteenBitSound ? 1 : 0)))
+ {
+ S9xClearSamples ();
+ return;
+ }
}
- S9xMixSamples (buffer, bytes >> (Settings.SixteenBitSound ? 1 : 0));
+ bytes = MIN (bytes, (samples << (Settings.SixteenBitSound ? 1 : 0)));
- /* PulseAudio-simple has no write indicator, so we don't know when the
- buffer is full. So we just drop the audio when we're in Turbo Mode. */
- if (Settings.TurboMode)
- return;
+ lock ();
- pa_simple_write (pulse, buffer, bytes, &err);
+ pa_stream_begin_write (stream, &output_buffer, &bytes);
+ S9xMixSamples ((uint8 *) output_buffer, bytes >> (Settings.SixteenBitSound ? 1 : 0));
+ pa_stream_write (stream, output_buffer, bytes, NULL, 0, PA_SEEK_RELATIVE);
+
+ unlock ();
return;
}
diff --git a/gtk/src/gtk_sound_driver_pulse.h b/gtk/src/gtk_sound_driver_pulse.h
index 878716cf..417a4872 100644
--- a/gtk/src/gtk_sound_driver_pulse.h
+++ b/gtk/src/gtk_sound_driver_pulse.h
@@ -3,8 +3,7 @@
#include "gtk_sound.h"
#include "gtk_sound_driver.h"
-#include "pulse/simple.h"
-#include "pulse/error.h"
+#include "pulse/pulseaudio.h"
class S9xPulseSoundDriver : public S9xSoundDriver
{
@@ -17,10 +16,15 @@ class S9xPulseSoundDriver : public S9xSoundDriver
void stop (void);
void mix (void);
void samples_available (void);
+ void lock (void);
+ void unlock (void);
+ void wait (void);
+
+ pa_threaded_mainloop *mainloop;
+ pa_context *context;
+ pa_stream *stream;
private:
- pa_simple *pulse;
- uint8 *buffer;
int buffer_size;
};
diff --git a/gtk/src/snes9x.ui b/gtk/src/snes9x.ui
index 465adba8..12af4edd 100644
--- a/gtk/src/snes9x.ui
+++ b/gtk/src/snes9x.ui
@@ -150,6 +150,13 @@
0.01
0.10000000000000001
+
diff --git a/snes9x.cpp b/snes9x.cpp
index b6193fb5..46652d8d 100644
--- a/snes9x.cpp
+++ b/snes9x.cpp
@@ -422,6 +422,8 @@ void S9xLoadConfigFiles (char **argv, int argc)
Settings.SoundPlaybackRate = conf.GetUInt("Sound::Rate", 32000);
Settings.SoundInputRate = conf.GetUInt("Sound::InputRate", 32000);
Settings.Mute = conf.GetBool("Sound::Mute", false);
+ Settings.DynamicRateControl = conf.GetBool("Sound::DynamicRateControl", false);
+ Settings.DynamicRateLimit = conf.GetUInt("Sound::DynamicRateLimit", 1000);
// Display
@@ -849,7 +851,7 @@ char * S9xParseArgs (char **argv, int argc)
#endif
// HACKING OR DEBUGGING OPTIONS
-
+
#ifdef DEBUGGER
if (!strcasecmp(argv[i], "-debug"))
CPU.Flags |= DEBUG_MODE_FLAG;
diff --git a/snes9x.h b/snes9x.h
index a97708e3..397b69dd 100644
--- a/snes9x.h
+++ b/snes9x.h
@@ -407,6 +407,8 @@ struct SSettings
bool8 Stereo;
bool8 ReverseStereo;
bool8 Mute;
+ bool8 DynamicRateControl;
+ uint32 DynamicRateLimit; /* Multiplied by 100000 */
bool8 SupportHiRes;
bool8 Transparency;