diff --git a/apu/apu.cpp b/apu/apu.cpp index 144d6942..d5aabc89 100644 --- a/apu/apu.cpp +++ b/apu/apu.cpp @@ -199,60 +199,133 @@ #include "snes/snes.hpp" -#define APU_DEFAULT_INPUT_RATE 31987 // 60hz -#define APU_MINIMUM_SAMPLE_COUNT 512 -#define APU_MINIMUM_SAMPLE_BLOCK 128 -#define APU_NUMERATOR_NTSC 15664 -#define APU_DENOMINATOR_NTSC 328125 -#define APU_NUMERATOR_PAL 34176 -#define APU_DENOMINATOR_PAL 709379 +#define APU_DEFAULT_INPUT_RATE 31987 // 60hz +#define APU_MINIMUM_SAMPLE_COUNT 512 +#define APU_MINIMUM_SAMPLE_BLOCK 128 +#define APU_NUMERATOR_NTSC 15664 +#define APU_DENOMINATOR_NTSC 328125 +#define APU_NUMERATOR_PAL 34176 +#define APU_DENOMINATOR_PAL 709379 namespace SNES { #include "bapu/dsp/blargg_endian.h" - - CPU cpu; +CPU cpu; } namespace spc { - static apu_callback sa_callback = NULL; - static void *extra_data = NULL; +static apu_callback sa_callback = NULL; +static void *extra_data = NULL; - static bool8 sound_in_sync = TRUE; - static bool8 sound_enabled = FALSE; +static bool8 sound_in_sync = TRUE; +static bool8 sound_enabled = FALSE; - static int buffer_size; - static int lag_master = 0; - static int lag = 0; +static int buffer_size; +static int lag_master = 0; +static int lag = 0; - static uint8 *landing_buffer = NULL; - static uint8 *shrink_buffer = NULL; +static uint8 *landing_buffer = NULL; +static uint8 *shrink_buffer = NULL; - static Resampler *resampler = NULL; +static Resampler *resampler = NULL; - static int32 reference_time; - static uint32 remainder; +static int32 reference_time; +static uint32 remainder; - static const int timing_hack_numerator = 256; - static int timing_hack_denominator = 256; - /* Set these to NTSC for now. Will change to PAL in S9xAPUTimingSetSpeedup - if necessary on game load. */ - static uint32 ratio_numerator = APU_NUMERATOR_NTSC; - static uint32 ratio_denominator = APU_DENOMINATOR_NTSC; +static const int timing_hack_numerator = 256; +static int timing_hack_denominator = 256; +/* Set these to NTSC for now. Will change to PAL in S9xAPUTimingSetSpeedup + 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; +static double dynamic_rate_multiplier = 1.0; +static bool8 using_threads = FALSE; } namespace msu { - static int buffer_size; - static uint8 *landing_buffer = NULL; - static Resampler *resampler = NULL; - static int resample_buffer_size = -1; - static uint8 *resample_buffer = NULL; +static int buffer_size; +static uint8 *landing_buffer = NULL; +static Resampler *resampler = NULL; +static int resample_buffer_size = -1; +static uint8 *resample_buffer = NULL; } +#ifdef USE_THREADS +#include +static pthread_t worker_thread; + +static pthread_mutex_t thread_running_mutex; +static pthread_cond_t thread_running_cond; +static bool8 thread_running = FALSE; +static bool8 thread_exit = FALSE; + +static void *S9xAPUWorkerThread(void *arg) +{ + while (!thread_exit) + { + pthread_mutex_lock(&thread_running_mutex); + + while (!thread_running) + { + pthread_cond_wait(&thread_running_cond, &thread_running_mutex); + if (thread_exit) + break; + } + + pthread_mutex_unlock(&thread_running_mutex); + + S9xAPUExecute(); + SNES::dsp.synchronize(); + + pthread_mutex_lock(&thread_running_mutex); + thread_running = FALSE; + pthread_cond_broadcast(&thread_running_cond); + pthread_mutex_unlock(&thread_running_mutex); + } + + return NULL; +} + +static void S9xAPUThreadWait(void) +{ + pthread_mutex_lock(&thread_running_mutex); + while (thread_running) + { + pthread_cond_wait(&thread_running_cond, &thread_running_mutex); + } + pthread_mutex_unlock(&thread_running_mutex); +} + +static void S9xAPUThreadRun(void) +{ + pthread_mutex_lock(&thread_running_mutex); + thread_running++; + pthread_cond_broadcast(&thread_running_cond); + pthread_mutex_unlock(&thread_running_mutex); +} + +static void S9xAPUThreadInit(void) +{ + pthread_cond_init(&thread_running_cond, NULL); + pthread_mutex_init(&thread_running_mutex, NULL); + thread_running = TRUE; + pthread_create(&worker_thread, NULL, S9xAPUWorkerThread, NULL); +} + +static void S9xAPUThreadDeinit(void) +{ + S9xAPUThreadWait(); + thread_exit = TRUE; + S9xAPUThreadRun(); + pthread_join(worker_thread, NULL); + pthread_cond_destroy(&thread_running_cond); + pthread_mutex_destroy(&thread_running_mutex); +} +#endif + static void EightBitize (uint8 *, int); static void DeStereo (uint8 *, int); static void ReverseStereo (uint8 *, int); @@ -264,522 +337,574 @@ static inline int S9xAPUGetClockRemainder (int32); static void EightBitize (uint8 *buffer, int sample_count) { - uint8 *buf8 = (uint8 *) buffer; - int16 *buf16 = (int16 *) buffer; + uint8 *buf8 = (uint8 *) buffer; + int16 *buf16 = (int16 *) buffer; - for (int i = 0; i < sample_count; i++) - buf8[i] = (uint8) ((buf16[i] / 256) + 128); + for (int i = 0; i < sample_count; i++) + buf8[i] = (uint8) ((buf16[i] / 256) + 128); } static void DeStereo (uint8 *buffer, int sample_count) { - int16 *buf = (int16 *) buffer; - int32 s1, s2; + int16 *buf = (int16 *) buffer; + int32 s1, s2; - for (int i = 0; i < (sample_count >> 1); i++) - { - s1 = (int32) buf[2 * i]; - s2 = (int32) buf[2 * i + 1]; - buf[i] = (int16) ((s1 + s2) >> 1); - } + for (int i = 0; i < (sample_count >> 1); i++) + { + s1 = (int32) buf[2 * i]; + s2 = (int32) buf[2 * i + 1]; + buf[i] = (int16) ((s1 + s2) >> 1); + } } static void ReverseStereo (uint8 *src_buffer, int sample_count) { - int16 *buffer = (int16 *) src_buffer; + int16 *buffer = (int16 *) src_buffer; - for (int i = 0; i < sample_count; i += 2) - { - buffer[i + 1] ^= buffer[i]; - buffer[i] ^= buffer[i + 1]; - buffer[i + 1] ^= buffer[i]; - } + for (int i = 0; i < sample_count; i += 2) + { + buffer[i + 1] ^= buffer[i]; + buffer[i] ^= buffer[i + 1]; + buffer[i + 1] ^= buffer[i]; + } } bool8 S9xMixSamples (uint8 *buffer, int sample_count) { - static int shrink_buffer_size = -1; - uint8 *dest; + static int shrink_buffer_size = -1; + uint8 *dest; - if (!Settings.SixteenBitSound || !Settings.Stereo) - { - /* We still need both stereo samples for generating the mono sample */ - if (!Settings.Stereo) - sample_count <<= 1; + if (!Settings.SixteenBitSound || !Settings.Stereo) + { + /* We still need both stereo samples for generating the mono sample */ + if (!Settings.Stereo) + sample_count <<= 1; - /* We still have to generate 16-bit samples for bit-dropping, too */ - if (shrink_buffer_size < (sample_count << 1)) - { - delete[] spc::shrink_buffer; - spc::shrink_buffer = new uint8[sample_count << 1]; - shrink_buffer_size = sample_count << 1; - } + /* We still have to generate 16-bit samples for bit-dropping, too */ + if (shrink_buffer_size < (sample_count << 1)) + { + delete[] spc::shrink_buffer; + spc::shrink_buffer = new uint8[sample_count << 1]; + shrink_buffer_size = sample_count << 1; + } - dest = spc::shrink_buffer; - } - else - dest = buffer; + dest = spc::shrink_buffer; + } + else + dest = buffer; - if (Settings.MSU1 && msu::resample_buffer_size < (sample_count << 1)) - { - delete[] msu::resample_buffer; - msu::resample_buffer = new uint8[sample_count << 1]; - msu::resample_buffer_size = sample_count << 1; - } + if (Settings.MSU1 && msu::resample_buffer_size < (sample_count << 1)) + { + delete[] msu::resample_buffer; + msu::resample_buffer = new uint8[sample_count << 1]; + msu::resample_buffer_size = sample_count << 1; + } - if (Settings.Mute) - { - memset(dest, 0, sample_count << 1); - spc::resampler->clear(); + if (Settings.Mute) + { + memset(dest, 0, sample_count << 1); + spc::resampler->clear(); - if(Settings.MSU1) - msu::resampler->clear(); + if(Settings.MSU1) + msu::resampler->clear(); - return (FALSE); - } - else - { - if (spc::resampler->avail() >= (sample_count + spc::lag)) - { - spc::resampler->read((short *) dest, sample_count); - if (spc::lag == spc::lag_master) - spc::lag = 0; + return (FALSE); + } + else + { + if (spc::resampler->avail() >= (sample_count + spc::lag)) + { + spc::resampler->read((short *) dest, sample_count); + if (spc::lag == spc::lag_master) + spc::lag = 0; - if (Settings.MSU1) - { - if (msu::resampler->avail() >= sample_count) - { - msu::resampler->read((short *)msu::resample_buffer, sample_count); - for (int i = 0; i < sample_count; ++i) - *((int16*)(dest+(i * 2))) += *((int16*)(msu::resample_buffer +(i * 2))); - } - else // should never occur - assert(0); - } - } - else - { - memset(buffer, (Settings.SixteenBitSound ? 0 : 128), (sample_count << (Settings.SixteenBitSound ? 1 : 0)) >> (Settings.Stereo ? 0 : 1)); - if (spc::lag == 0) - spc::lag = spc::lag_master; + if (Settings.MSU1) + { + if (msu::resampler->avail() >= sample_count) + { + msu::resampler->read((short *)msu::resample_buffer, sample_count); + for (int i = 0; i < sample_count; ++i) + *((int16*)(dest+(i * 2))) += *((int16*)(msu::resample_buffer +(i * 2))); + } + else // should never occur + assert(0); + } + } + else + { + memset(buffer, (Settings.SixteenBitSound ? 0 : 128), (sample_count << (Settings.SixteenBitSound ? 1 : 0)) >> (Settings.Stereo ? 0 : 1)); + if (spc::lag == 0) + spc::lag = spc::lag_master; - return (FALSE); - } - } + return (FALSE); + } + } - if (Settings.ReverseStereo && Settings.Stereo) - ReverseStereo(dest, sample_count); + if (Settings.ReverseStereo && Settings.Stereo) + ReverseStereo(dest, sample_count); - if (!Settings.Stereo || !Settings.SixteenBitSound) - { - if (!Settings.Stereo) - { - DeStereo(dest, sample_count); - sample_count >>= 1; - } + if (!Settings.Stereo || !Settings.SixteenBitSound) + { + if (!Settings.Stereo) + { + DeStereo(dest, sample_count); + sample_count >>= 1; + } - if (!Settings.SixteenBitSound) - EightBitize(dest, sample_count); + if (!Settings.SixteenBitSound) + EightBitize(dest, sample_count); - memcpy(buffer, dest, (sample_count << (Settings.SixteenBitSound ? 1 : 0))); - } + memcpy(buffer, dest, (sample_count << (Settings.SixteenBitSound ? 1 : 0))); + } - return (TRUE); + return (TRUE); } int S9xGetSampleCount (void) { - return (spc::resampler->avail() >> (Settings.Stereo ? 0 : 1)); + return (spc::resampler->avail() >> (Settings.Stereo ? 0 : 1)); } /* TODO: Attach */ void S9xFinalizeSamples (void) { - bool drop_current_msu1_samples = true; + bool drop_current_msu1_samples = true; - if (!Settings.Mute) - { - drop_current_msu1_samples = false; + if (!Settings.Mute) + { + drop_current_msu1_samples = false; - if (!spc::resampler->push((short *)spc::landing_buffer, SNES::dsp.spc_dsp.sample_count())) - { - /* We weren't able to process the entire buffer. Potential overrun. */ - spc::sound_in_sync = FALSE; + if (!spc::resampler->push((short *)spc::landing_buffer, SNES::dsp.spc_dsp.sample_count())) + { + /* We weren't able to process the entire buffer. Potential overrun. */ + spc::sound_in_sync = FALSE; - if (Settings.SoundSync && !Settings.TurboMode) - return; + if (Settings.SoundSync && !Settings.TurboMode) + return; - // since we drop the current dsp samples we also want to drop generated msu1 samples - drop_current_msu1_samples = true; - } - } + // since we drop the current dsp samples we also want to drop generated msu1 samples + drop_current_msu1_samples = true; + } + } - // only generate msu1 if we really consumed the dsp samples (sample_count() resets at end of function), - // otherwise we will generate multiple times for the same samples - so this needs to be after all early - // function returns - if (Settings.MSU1) - { - // generate the same number of msu1 samples as dsp samples were generated - S9xMSU1SetOutput((int16 *)msu::landing_buffer, msu::buffer_size); - S9xMSU1Generate(SNES::dsp.spc_dsp.sample_count()); - if (!drop_current_msu1_samples && !msu::resampler->push((short *)msu::landing_buffer, S9xMSU1Samples())) - { - // should not occur, msu buffer is larger and we drop msu samples if spc buffer overruns - assert(0); - } - } + // only generate msu1 if we really consumed the dsp samples (sample_count() resets at end of function), + // otherwise we will generate multiple times for the same samples - so this needs to be after all early + // function returns + if (Settings.MSU1) + { + // generate the same number of msu1 samples as dsp samples were generated + S9xMSU1SetOutput((int16 *)msu::landing_buffer, msu::buffer_size); + S9xMSU1Generate(SNES::dsp.spc_dsp.sample_count()); + if (!drop_current_msu1_samples && !msu::resampler->push((short *)msu::landing_buffer, S9xMSU1Samples())) + { + // should not occur, msu buffer is larger and we drop msu samples if spc buffer overruns + assert(0); + } + } - if (!Settings.SoundSync || Settings.TurboMode || Settings.Mute) - spc::sound_in_sync = TRUE; - else - if (spc::resampler->space_empty() >= spc::resampler->space_filled()) - spc::sound_in_sync = TRUE; - else - spc::sound_in_sync = FALSE; + if (!Settings.SoundSync || Settings.TurboMode || Settings.Mute) + spc::sound_in_sync = TRUE; + else + if (spc::resampler->space_empty() >= spc::resampler->space_filled()) + spc::sound_in_sync = TRUE; + else + spc::sound_in_sync = FALSE; - SNES::dsp.spc_dsp.set_output((SNES::SPC_DSP::sample_t *) spc::landing_buffer, spc::buffer_size); + SNES::dsp.spc_dsp.set_output((SNES::SPC_DSP::sample_t *) spc::landing_buffer, spc::buffer_size); } void S9xLandSamples (void) { - if (spc::sa_callback != NULL) - spc::sa_callback(spc::extra_data); - else - S9xFinalizeSamples(); + if (spc::sa_callback != NULL) + spc::sa_callback(spc::extra_data); + else + S9xFinalizeSamples(); } void S9xClearSamples (void) { - spc::resampler->clear(); - if (Settings.MSU1) - msu::resampler->clear(); - spc::lag = spc::lag_master; + spc::resampler->clear(); + if (Settings.MSU1) + msu::resampler->clear(); + spc::lag = spc::lag_master; } bool8 S9xSyncSound (void) { - if (!Settings.SoundSync || spc::sound_in_sync) - return (TRUE); + if (!Settings.SoundSync || spc::sound_in_sync) + return (TRUE); - S9xLandSamples(); + S9xLandSamples(); - return (spc::sound_in_sync); + return (spc::sound_in_sync); } void S9xSetSamplesAvailableCallback (apu_callback callback, void *data) { - spc::sa_callback = callback; - spc::extra_data = data; + spc::sa_callback = callback; + spc::extra_data = data; } void S9xUpdateDynamicRate (int avail, int buffer_size) { - spc::dynamic_rate_multiplier = 1.0 + (Settings.DynamicRateLimit * (buffer_size - 2 * avail)) / - (double)(1000 * buffer_size); + spc::dynamic_rate_multiplier = 1.0 + (Settings.DynamicRateLimit * (buffer_size - 2 * avail)) / + (double)(1000 * buffer_size); - UpdatePlaybackRate(); + UpdatePlaybackRate(); } -static void UpdatePlaybackRate (void) +static inline void UpdatePlaybackRate (void) { - if (Settings.SoundInputRate == 0) - Settings.SoundInputRate = APU_DEFAULT_INPUT_RATE; + 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); + 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; - } + if (Settings.DynamicRateControl) + { + time_ratio *= spc::dynamic_rate_multiplier; + } - spc::resampler->time_ratio(time_ratio); + spc::resampler->time_ratio(time_ratio); - if (Settings.MSU1) - { - time_ratio = (44100.0 / Settings.SoundPlaybackRate) * (Settings.SoundInputRate / 32040.0); - msu::resampler->time_ratio(time_ratio); - } + if (Settings.MSU1) + { + time_ratio = (44100.0 / Settings.SoundPlaybackRate) * (Settings.SoundInputRate / 32040.0); + msu::resampler->time_ratio(time_ratio); + } } bool8 S9xInitSound (int buffer_ms, int lag_ms) { - // buffer_ms : buffer size given in millisecond - // lag_ms : allowable time-lag given in millisecond + // buffer_ms : buffer size given in millisecond + // lag_ms : allowable time-lag given in millisecond - int sample_count = buffer_ms * 32040 / 1000; - int lag_sample_count = lag_ms * 32040 / 1000; + int sample_count = buffer_ms * 32040 / 1000; + int lag_sample_count = lag_ms * 32040 / 1000; - spc::lag_master = lag_sample_count; - if (Settings.Stereo) - spc::lag_master <<= 1; - spc::lag = spc::lag_master; + spc::lag_master = lag_sample_count; + if (Settings.Stereo) + spc::lag_master <<= 1; + spc::lag = spc::lag_master; - if (sample_count < APU_MINIMUM_SAMPLE_COUNT) - sample_count = APU_MINIMUM_SAMPLE_COUNT; + if (sample_count < APU_MINIMUM_SAMPLE_COUNT) + sample_count = APU_MINIMUM_SAMPLE_COUNT; - spc::buffer_size = sample_count << 2; - msu::buffer_size = (int)((sample_count << 2) * 1.5); // Always 16-bit, Stereo; 1.5 to never overflow before dsp buffer + spc::buffer_size = sample_count << 2; + msu::buffer_size = (int)((sample_count << 2) * 1.5); // Always 16-bit, Stereo; 1.5 to never overflow before dsp buffer - printf("Sound buffer size: %d (%d samples)\n", spc::buffer_size, sample_count); + printf("Sound buffer size: %d (%d samples)\n", spc::buffer_size, sample_count); - if (spc::landing_buffer) - delete[] spc::landing_buffer; - spc::landing_buffer = new uint8[spc::buffer_size * 2]; - if (!spc::landing_buffer) - return (FALSE); - if (msu::landing_buffer) - delete[] msu::landing_buffer; - msu::landing_buffer = new uint8[msu::buffer_size * 2]; - if (!msu::landing_buffer) - return (FALSE); + if (spc::landing_buffer) + delete[] spc::landing_buffer; + spc::landing_buffer = new uint8[spc::buffer_size * 2]; + if (!spc::landing_buffer) + return (FALSE); + if (msu::landing_buffer) + delete[] msu::landing_buffer; + msu::landing_buffer = new uint8[msu::buffer_size * 2]; + if (!msu::landing_buffer) + return (FALSE); - /* The resampler and spc unit use samples (16-bit short) as - arguments. Use 2x in the resampler for buffer leveling with SoundSync */ - if (!spc::resampler) - { - spc::resampler = new HermiteResampler(spc::buffer_size >> (Settings.SoundSync ? 0 : 1)); - if (!spc::resampler) - { - delete[] spc::landing_buffer; - return (FALSE); - } - } - else - spc::resampler->resize(spc::buffer_size >> (Settings.SoundSync ? 0 : 1)); + /* The resampler and spc unit use samples (16-bit short) as + arguments. Use 2x in the resampler for buffer leveling with SoundSync */ + if (!spc::resampler) + { + spc::resampler = new HermiteResampler(spc::buffer_size >> (Settings.SoundSync ? 0 : 1)); + if (!spc::resampler) + { + delete[] spc::landing_buffer; + return (FALSE); + } + } + else + spc::resampler->resize(spc::buffer_size >> (Settings.SoundSync ? 0 : 1)); - if (!msu::resampler) - { - msu::resampler = new HermiteResampler(msu::buffer_size); - if (!msu::resampler) - { - delete[] msu::landing_buffer; - return (FALSE); - } - } - else - msu::resampler->resize(msu::buffer_size); + if (!msu::resampler) + { + msu::resampler = new HermiteResampler(msu::buffer_size); + if (!msu::resampler) + { + delete[] msu::landing_buffer; + return (FALSE); + } + } + else + msu::resampler->resize(msu::buffer_size); - SNES::dsp.spc_dsp.set_output ((SNES::SPC_DSP::sample_t *) spc::landing_buffer, spc::buffer_size); + SNES::dsp.spc_dsp.set_output ((SNES::SPC_DSP::sample_t *) spc::landing_buffer, spc::buffer_size); - UpdatePlaybackRate(); + UpdatePlaybackRate(); - spc::sound_enabled = S9xOpenSoundDevice(); + spc::sound_enabled = S9xOpenSoundDevice(); - return (spc::sound_enabled); +#ifdef USE_THREADS + if (spc::using_threads) + { + S9xAPUThreadDeinit(); + } + + spc::using_threads = Settings.ThreadedAPU & spc::sound_enabled; + + if (spc::using_threads) + { + printf ("Using threaded APU.\n"); + S9xAPUThreadInit(); + } +#endif + + return (spc::sound_enabled); } void S9xSetSoundControl (uint8 voice_switch) { - SNES::dsp.spc_dsp.set_stereo_switch (voice_switch << 8 | voice_switch); + SNES::dsp.spc_dsp.set_stereo_switch (voice_switch << 8 | voice_switch); } void S9xSetSoundMute (bool8 mute) { - Settings.Mute = mute; - if (!spc::sound_enabled) - Settings.Mute = TRUE; + Settings.Mute = mute; + if (!spc::sound_enabled) + Settings.Mute = TRUE; } void S9xDumpSPCSnapshot (void) { - SNES::dsp.spc_dsp.dump_spc_snapshot(); + SNES::dsp.spc_dsp.dump_spc_snapshot(); } static void SPCSnapshotCallback (void) { - S9xSPCDump(S9xGetFilenameInc((".spc"), SPC_DIR)); - printf("Dumped key-on triggered spc snapshot.\n"); + S9xSPCDump(S9xGetFilenameInc((".spc"), SPC_DIR)); + printf("Dumped key-on triggered spc snapshot.\n"); } bool8 S9xInitAPU (void) { - spc::landing_buffer = NULL; - spc::shrink_buffer = NULL; - spc::resampler = NULL; - msu::resampler = NULL; + spc::landing_buffer = NULL; + spc::shrink_buffer = NULL; + spc::resampler = NULL; + msu::resampler = NULL; - return (TRUE); + return (TRUE); } void S9xDeinitAPU (void) { - if (spc::resampler) - { - delete spc::resampler; - spc::resampler = NULL; - } + if (spc::using_threads) + { + S9xAPUThreadDeinit(); + } - if (spc::landing_buffer) - { - delete[] spc::landing_buffer; - spc::landing_buffer = NULL; - } + if (spc::resampler) + { + delete spc::resampler; + spc::resampler = NULL; + } - if (spc::shrink_buffer) - { - delete[] spc::shrink_buffer; - spc::shrink_buffer = NULL; - } + if (spc::landing_buffer) + { + delete[] spc::landing_buffer; + spc::landing_buffer = NULL; + } - if (msu::resampler) - { - delete msu::resampler; - msu::resampler = NULL; - } + if (spc::shrink_buffer) + { + delete[] spc::shrink_buffer; + spc::shrink_buffer = NULL; + } - if (msu::landing_buffer) - { - delete[] msu::landing_buffer; - msu::landing_buffer = NULL; - } + if (msu::resampler) + { + delete msu::resampler; + msu::resampler = NULL; + } - if (msu::resample_buffer) - { - delete[] msu::resample_buffer; - msu::resample_buffer = NULL; - } + if (msu::landing_buffer) + { + delete[] msu::landing_buffer; + msu::landing_buffer = NULL; + } - S9xMSU1DeInit(); + if (msu::resample_buffer) + { + delete[] msu::resample_buffer; + msu::resample_buffer = NULL; + } + + S9xMSU1DeInit(); } static inline int S9xAPUGetClock (int32 cpucycles) { - return (spc::ratio_numerator * (cpucycles - spc::reference_time) + spc::remainder) / - spc::ratio_denominator; + return (spc::ratio_numerator * (cpucycles - spc::reference_time) + spc::remainder) / + spc::ratio_denominator; } static inline int S9xAPUGetClockRemainder (int32 cpucycles) { - return (spc::ratio_numerator * (cpucycles - spc::reference_time) + spc::remainder) % - spc::ratio_denominator; + return (spc::ratio_numerator * (cpucycles - spc::reference_time) + spc::remainder) % + spc::ratio_denominator; +} + +static inline void S9xAPUUpdateClock (void) +{ + SNES::smp.clock -= S9xAPUGetClock (CPU.Cycles); + spc::remainder = S9xAPUGetClockRemainder(CPU.Cycles); + S9xAPUSetReferenceTime(CPU.Cycles); } uint8 S9xAPUReadPort (int port) { - S9xAPUExecute (); - return ((uint8) SNES::smp.port_read (port & 3)); +#ifdef USE_THREADS + if (spc::using_threads) + { + S9xAPUThreadWait(); + } +#endif + S9xAPUUpdateClock(); + S9xAPUExecute (); + return ((uint8) SNES::smp.port_read (port & 3)); } void S9xAPUWritePort (int port, uint8 byte) { - S9xAPUExecute (); - SNES::cpu.port_write (port & 3, byte); +#ifdef USE_THREADS + if (spc::using_threads) + { + S9xAPUThreadWait(); + } +#endif + S9xAPUUpdateClock(); + S9xAPUExecute (); + SNES::cpu.port_write (port & 3, byte); } void S9xAPUSetReferenceTime (int32 cpucycles) { - spc::reference_time = cpucycles; + spc::reference_time = cpucycles; } void S9xAPUExecute (void) { - SNES::smp.clock -= S9xAPUGetClock (CPU.Cycles); - SNES::smp.enter (); - - spc::remainder = S9xAPUGetClockRemainder(CPU.Cycles); - - S9xAPUSetReferenceTime(CPU.Cycles); + SNES::smp.enter (); } void S9xAPUEndScanline (void) { - S9xAPUExecute(); - SNES::dsp.synchronize(); +#ifdef USE_THREADS + if (spc::using_threads) + { + S9xAPUThreadWait(); - if (SNES::dsp.spc_dsp.sample_count() >= APU_MINIMUM_SAMPLE_BLOCK || !spc::sound_in_sync) - S9xLandSamples(); + if (SNES::dsp.spc_dsp.sample_count() >= APU_MINIMUM_SAMPLE_BLOCK || !spc::sound_in_sync) + S9xLandSamples(); + + S9xAPUUpdateClock(); + S9xAPUThreadRun(); + + return; + } +#endif + + S9xAPUUpdateClock(); + S9xAPUExecute(); + SNES::dsp.synchronize(); + + if (SNES::dsp.spc_dsp.sample_count() >= APU_MINIMUM_SAMPLE_BLOCK || !spc::sound_in_sync) + S9xLandSamples(); } void S9xAPUTimingSetSpeedup (int ticks) { - if (ticks != 0) - printf("APU speedup hack: %d\n", ticks); + if (ticks != 0) + printf("APU speedup hack: %d\n", ticks); - spc::timing_hack_denominator = 256 - ticks; + spc::timing_hack_denominator = 256 - ticks; - spc::ratio_numerator = Settings.PAL ? APU_NUMERATOR_PAL : APU_NUMERATOR_NTSC; - spc::ratio_denominator = Settings.PAL ? APU_DENOMINATOR_PAL : APU_DENOMINATOR_NTSC; - spc::ratio_denominator = spc::ratio_denominator * spc::timing_hack_denominator / spc::timing_hack_numerator; + spc::ratio_numerator = Settings.PAL ? APU_NUMERATOR_PAL : APU_NUMERATOR_NTSC; + spc::ratio_denominator = Settings.PAL ? APU_DENOMINATOR_PAL : APU_DENOMINATOR_NTSC; + spc::ratio_denominator = spc::ratio_denominator * spc::timing_hack_denominator / spc::timing_hack_numerator; - UpdatePlaybackRate(); + UpdatePlaybackRate(); } void S9xResetAPU (void) { - spc::reference_time = 0; - spc::remainder = 0; + spc::reference_time = 0; + spc::remainder = 0; - SNES::cpu.reset (); - SNES::cpu.frequency = Settings.PAL ? PAL_MASTER_CLOCK : NTSC_MASTER_CLOCK; - SNES::smp.power (); - SNES::dsp.power (); - SNES::dsp.spc_dsp.set_output ((SNES::SPC_DSP::sample_t *) spc::landing_buffer, spc::buffer_size >> 1); - SNES::dsp.spc_dsp.set_spc_snapshot_callback(SPCSnapshotCallback); + SNES::cpu.reset (); + SNES::cpu.frequency = Settings.PAL ? PAL_MASTER_CLOCK : NTSC_MASTER_CLOCK; + SNES::smp.power (); + SNES::dsp.power (); + SNES::dsp.spc_dsp.set_output ((SNES::SPC_DSP::sample_t *) spc::landing_buffer, spc::buffer_size >> 1); + SNES::dsp.spc_dsp.set_spc_snapshot_callback(SPCSnapshotCallback); - spc::resampler->clear(); + spc::resampler->clear(); - if (Settings.MSU1) - msu::resampler->clear(); + if (Settings.MSU1) + msu::resampler->clear(); } void S9xSoftResetAPU (void) { - spc::reference_time = 0; - spc::remainder = 0; - SNES::cpu.reset (); - SNES::smp.reset (); - SNES::dsp.reset (); - SNES::dsp.spc_dsp.set_output ((SNES::SPC_DSP::sample_t *) spc::landing_buffer, spc::buffer_size >> 1); + spc::reference_time = 0; + spc::remainder = 0; + SNES::cpu.reset (); + SNES::smp.reset (); + SNES::dsp.reset (); + SNES::dsp.spc_dsp.set_output ((SNES::SPC_DSP::sample_t *) spc::landing_buffer, spc::buffer_size >> 1); - spc::resampler->clear(); + spc::resampler->clear(); - if (Settings.MSU1) - msu::resampler->clear(); + if (Settings.MSU1) + msu::resampler->clear(); } void S9xAPUSaveState (uint8 *block) { - uint8 *ptr = block; + uint8 *ptr = block; - SNES::smp.save_state (&ptr); - SNES::dsp.save_state (&ptr); + SNES::smp.save_state (&ptr); + SNES::dsp.save_state (&ptr); - SNES::set_le32(ptr, spc::reference_time); - ptr += sizeof(int32); - SNES::set_le32(ptr, spc::remainder); - ptr += sizeof(int32); - SNES::set_le32(ptr, SNES::dsp.clock); - ptr += sizeof(int32); - memcpy (ptr, SNES::cpu.registers, 4); - ptr += sizeof(int32); + SNES::set_le32(ptr, spc::reference_time); + ptr += sizeof(int32); + SNES::set_le32(ptr, spc::remainder); + ptr += sizeof(int32); + SNES::set_le32(ptr, SNES::dsp.clock); + ptr += sizeof(int32); + memcpy (ptr, SNES::cpu.registers, 4); + ptr += sizeof(int32); - memset (ptr, 0, SPC_SAVE_STATE_BLOCK_SIZE-(ptr-block)); + memset (ptr, 0, SPC_SAVE_STATE_BLOCK_SIZE-(ptr-block)); } void S9xAPULoadState (uint8 *block) { - uint8 *ptr = block; + uint8 *ptr = block; - SNES::smp.load_state (&ptr); - SNES::dsp.load_state (&ptr); + SNES::smp.load_state (&ptr); + SNES::dsp.load_state (&ptr); - spc::reference_time = SNES::get_le32(ptr); - ptr += sizeof(int32); - spc::remainder = SNES::get_le32(ptr); - ptr += sizeof(int32); - SNES::dsp.clock = SNES::get_le32(ptr); - ptr += sizeof(int32); - memcpy (SNES::cpu.registers, ptr, 4); + spc::reference_time = SNES::get_le32(ptr); + ptr += sizeof(int32); + spc::remainder = SNES::get_le32(ptr); + ptr += sizeof(int32); + SNES::dsp.clock = SNES::get_le32(ptr); + ptr += sizeof(int32); + memcpy (SNES::cpu.registers, ptr, 4); } static void to_var_from_buf (uint8 **buf, void *var, size_t size) { - memcpy(var, *buf, size); - *buf += size; + memcpy(var, *buf, size); + *buf += size; } #undef IF_0_THEN_256 @@ -828,10 +953,10 @@ void S9xAPULoadBlarggState(uint8 *oldblock) uint8_t divider[3], counter[3]; for ( int i = 0; i < 3; i++ ) { - next_time[i] = copier.copy_int( 0, sizeof(uint16_t) ); - divider[i] = copier.copy_int( 0, sizeof(uint8_t) ); - counter[i] = copier.copy_int( 0, sizeof(uint8_t) ); - copier.extra(); + next_time[i] = copier.copy_int( 0, sizeof(uint16_t) ); + divider[i] = copier.copy_int( 0, sizeof(uint8_t) ); + counter[i] = copier.copy_int( 0, sizeof(uint8_t) ); + copier.extra(); } // construct timers out of available parts from blargg smp SNES::smp.timer0.enable = regs[1] >> 0 & 1; // regs[1] = CONTROL @@ -889,28 +1014,28 @@ void S9xAPULoadBlarggState(uint8 *oldblock) bool8 S9xSPCDump (const char *filename) { - FILE *fs; - uint8 buf[SPC_FILE_SIZE]; - size_t ignore; + FILE *fs; + uint8 buf[SPC_FILE_SIZE]; + size_t ignore; - fs = fopen(filename, "wb"); - if (!fs) - return (FALSE); + fs = fopen(filename, "wb"); + if (!fs) + return (FALSE); - S9xSetSoundMute(TRUE); + S9xSetSoundMute(TRUE); - SNES::smp.save_spc (buf); + SNES::smp.save_spc (buf); - ignore = fwrite (buf, SPC_FILE_SIZE, 1, fs); + ignore = fwrite (buf, SPC_FILE_SIZE, 1, fs); - if (ignore == 0) - { - fprintf (stderr, "Couldn't write file %s.\n", filename); - } + if (ignore == 0) + { + fprintf (stderr, "Couldn't write file %s.\n", filename); + } - fclose(fs); + fclose(fs); - S9xSetSoundMute(FALSE); + S9xSetSoundMute(FALSE); - return (TRUE); + return (TRUE); } diff --git a/gtk/configure.ac b/gtk/configure.ac index 4cd190bf..4454fca8 100644 --- a/gtk/configure.ac +++ b/gtk/configure.ac @@ -66,6 +66,12 @@ AC_ARG_WITH(xrandr, [], [with_xrandr=yes]) +AC_ARG_WITH(pthreads, + [AS_HELP_STRING([--with(out)-pthreads], + [Enable POSIX Threads (default: with)])], + [], + [with_pthreads=yes]) + AC_ARG_WITH(portaudio, [AS_HELP_STRING([--with(out)-portaudio], [Enable PortAudio sound driver support (default: with)])], @@ -268,6 +274,17 @@ if test yes = "$with_oss" ; then ]) fi +THREADS=0 +if test yes = "$with_pthreads" ; then + AC_CHECK_HEADER(pthread.h, [ + CFLAGS="$CFLAGS -DUSE_THREADS" + LIBS="$LIBS -lpthread" + THREADS=1 + ],[ + echo "Cannot find pthreads. Disabling threading." + ]) +fi + ALSA=0 ALSA_CFLAGS="" ALSA_LIBS="" diff --git a/gtk/src/gtk_config.cpp b/gtk/src/gtk_config.cpp index 54393e59..1da0e386 100644 --- a/gtk/src/gtk_config.cpp +++ b/gtk/src/gtk_config.cpp @@ -255,6 +255,7 @@ Snes9xConfig::load_defaults (void) Settings.SoundSync = 1; Settings.DynamicRateControl = 1; Settings.DynamicRateLimit = 5; + Settings.ThreadedAPU = FALSE; Settings.HDMATimingHack = 100; Settings.ApplyCheats = 1; @@ -406,6 +407,7 @@ Snes9xConfig::save_config_file (void) 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); + xml_out_int (xml, "threaded_apu", Settings.ThreadedAPU); /* Snes9X core-stored variables */ xml_out_int (xml, "transparency", Settings.Transparency); @@ -677,6 +679,10 @@ Snes9xConfig::set_option (const char *name, const char *value) Settings.DynamicRateLimit = atoi (value); Settings.DynamicRateLimit = CLAMP (Settings.DynamicRateLimit, 1, 1000); } + else if (!strcasecmp (name, "threaded_apu")) + { + Settings.ThreadedAPU = atoi (value); + } else if (!strcasecmp (name, "gaussian_interpolation")) { } diff --git a/gtk/src/gtk_preferences.cpp b/gtk/src/gtk_preferences.cpp index 85794c85..130673e8 100644 --- a/gtk/src/gtk_preferences.cpp +++ b/gtk/src/gtk_preferences.cpp @@ -678,6 +678,7 @@ Snes9xPreferences::move_settings_to_dialog (void) set_check ("sync_sound", Settings.SoundSync); set_check ("dynamic_rate_control", Settings.DynamicRateControl); set_spin ("dynamic_rate_limit", Settings.DynamicRateLimit / 1000.0); + set_check ("threaded_apu", Settings.ThreadedAPU); set_spin ("rewind_buffer_size", config->rewind_buffer_size); set_spin ("rewind_granularity", config->rewind_granularity); @@ -770,7 +771,8 @@ Snes9xPreferences::get_settings_from_dialog (void) (7 - (get_combo ("playback_combo")))) || (config->sound_input_rate != get_slider ("sound_input_rate")) || (Settings.SoundSync != get_check ("sync_sound")) || - (Settings.DynamicRateControl != get_check ("dynamic_rate_control")) + (Settings.DynamicRateControl != get_check ("dynamic_rate_control")) || + (Settings.ThreadedAPU != get_check ("threaded_apu")) ) { sound_needs_restart = 1; @@ -836,6 +838,7 @@ Snes9xPreferences::get_settings_from_dialog (void) 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") * 1000); + Settings.ThreadedAPU = get_check ("threaded_apu"); store_ntsc_settings (); config->ntsc_scanline_intensity = get_combo ("ntsc_scanline_intensity"); diff --git a/gtk/src/snes9x.ui b/gtk/src/snes9x.ui index a4c038bb..ab5f3322 100644 --- a/gtk/src/snes9x.ui +++ b/gtk/src/snes9x.ui @@ -4016,7 +4016,21 @@ 1 - + + + Threaded sound processors + True + True + False + Tries to run the S-SMP and S-DSP on a separate thread + True + + + False + False + 2 + + Dynamic rate control @@ -4029,7 +4043,7 @@ False False - 2 + 3 @@ -4046,7 +4060,7 @@ False False - 3 + 4 @@ -4062,7 +4076,7 @@ False False - 4 + 5 @@ -4078,7 +4092,7 @@ False False - 5 + 6 @@ -4298,7 +4312,7 @@ False True - 6 + 7 diff --git a/snes9x.cpp b/snes9x.cpp index 7fab3827..f3904797 100644 --- a/snes9x.cpp +++ b/snes9x.cpp @@ -424,6 +424,7 @@ void S9xLoadConfigFiles (char **argv, int argc) Settings.Mute = conf.GetBool("Sound::Mute", false); Settings.DynamicRateControl = conf.GetBool("Sound::DynamicRateControl", false); Settings.DynamicRateLimit = conf.GetInt ("Sound::DynamicRateLimit", 5); + Settings.ThreadedAPU = conf.GetBool("Sound::ThreadedAPU", false); // Display diff --git a/snes9x.h b/snes9x.h index 5f439768..d9884479 100644 --- a/snes9x.h +++ b/snes9x.h @@ -409,6 +409,7 @@ struct SSettings bool8 Mute; bool8 DynamicRateControl; int32 DynamicRateLimit; /* Multiplied by 1000 */ + bool8 ThreadedAPU; bool8 SupportHiRes; bool8 Transparency;