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 + + 0.001 + 0.100 + 0.010 + 0.001 + 0.010 + -1 1 @@ -4008,6 +4015,23 @@ 1 + + + + Dynamic rate control + True + True + False + Adjust input rate automatically + True + + + False + False + 2 + + + Mute sound output @@ -4021,7 +4045,7 @@ False False - 2 + 3 @@ -4037,7 +4061,7 @@ False False - 3 + 4 @@ -4053,14 +4077,14 @@ False False - 4 + 5 True False - 4 + 5 2 10 5 @@ -4135,6 +4159,50 @@ GTK_FILL + + + True + False + 0 + Dynamic rate limit: + + + 4 + 5 + GTK_FILL + GTK_FILL + + + + + True + False + + + True + True + False + False + True + True + 3 + dynamic_rate_adjustment + + + False + False + 0 + + + + + 1 + 4 + 5 + GTK_FILL + GTK_FILL + + True @@ -4229,7 +4297,7 @@ False True - 5 + 6 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;