/*****************************************************************************\ Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. This file is licensed under the Snes9x License. For further information, consult the LICENSE file in the root directory. \*****************************************************************************/ #include #include "apu.h" #include "resampler.h" #include "bapu/snes/snes.hpp" #include "bapu/dsp/sdsp.hpp" #include "bapu/smp/smp.hpp" static constexpr int APU_DEFAULT_INPUT_RATE = 31950; // ~59.94Hz static constexpr int APU_SAMPLE_BLOCK = 48; static constexpr int APU_NUMERATOR_NTSC = 15664; static constexpr int APU_DENOMINATOR_NTSC = 328125; static constexpr int APU_NUMERATOR_PAL = 34176; static constexpr int APU_DENOMINATOR_PAL = 709379; // Max number of samples we'll ever generate before call to port API and // moving the samples to the resampler. // This is 535 sample frames, which corresponds to 1 video frame + some leeway // for use with SoundSync, multiplied by 2, for left and right samples. static constexpr int MINIMUM_BUFFER_SIZE = 550 * 2; namespace SNES { CPU cpu; } namespace spc { static bool sound_in_sync = true; static bool sound_enabled = false; static std::unique_ptr resampler; static int32_t reference_time; static uint32_t remainder; static constexpr 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_t ratio_numerator = APU_NUMERATOR_NTSC; static uint32_t ratio_denominator = APU_DENOMINATOR_NTSC; } void S9xClearSamples() { spc::resampler->clear(); } bool S9xMixSamples(uint8_t *dest, int sample_count) { int16_t *out = reinterpret_cast(dest); if (Settings.Mute) { std::fill_n(&out[0], sample_count, 0); S9xClearSamples(); } else { if (spc::resampler->avail() >= sample_count) spc::resampler->read(reinterpret_cast(out), sample_count); else { std::fill_n(&out[0], sample_count, 0); return false; } } spc::sound_in_sync = spc::resampler->space_empty() >= 535 * 2 || !Settings.SoundSync || Settings.TurboMode || Settings.Mute; return true; } int S9xGetSampleCount() { return spc::resampler->avail(); } void S9xLandSamples() { spc::sound_in_sync = spc::resampler->space_empty() >= 535 * 2 || !Settings.SoundSync || Settings.TurboMode || Settings.Mute; } bool S9xSyncSound() { if (!Settings.SoundSync || spc::sound_in_sync) return true; S9xLandSamples(); return spc::sound_in_sync; } static void UpdatePlaybackRate() { if (!Settings.SoundInputRate) Settings.SoundInputRate = APU_DEFAULT_INPUT_RATE; double time_ratio = static_cast(Settings.SoundInputRate) * spc::timing_hack_numerator / (Settings.SoundPlaybackRate * spc::timing_hack_denominator); spc::resampler->time_ratio(time_ratio); } bool S9xInitSound(int buffer_ms) { // The resampler and spc unit use samples (16-bit short) as arguments. int buffer_size_samples = MINIMUM_BUFFER_SIZE; int requested_buffer_size_samples = Settings.SoundPlaybackRate * buffer_ms * 2 / 1000; if (requested_buffer_size_samples > buffer_size_samples) buffer_size_samples = requested_buffer_size_samples; spc::resampler.reset(new Resampler(buffer_size_samples)); if (!spc::resampler) return false; SNES::dsp.spc_dsp.set_output(spc::resampler.get()); UpdatePlaybackRate(); spc::sound_enabled = S9xOpenSoundDevice(); return spc::sound_enabled; } void S9xSetSoundControl(uint8_t voice_switch) { SNES::dsp.spc_dsp.set_stereo_switch((voice_switch << 8) | voice_switch); } void S9xSetSoundMute(bool mute) { Settings.Mute = mute; if (!spc::sound_enabled) Settings.Mute = true; } bool S9xInitAPU() { spc::resampler.reset(); return true; } void S9xDeinitAPU() { spc::resampler.reset(); } static inline int S9xAPUGetClock(int32_t cpucycles) { return (spc::ratio_numerator * (cpucycles - spc::reference_time) + spc::remainder) / spc::ratio_denominator; } static inline int S9xAPUGetClockRemainder(int32_t cpucycles) { return (spc::ratio_numerator * (cpucycles - spc::reference_time) + spc::remainder) % spc::ratio_denominator; } void S9xAPUExecute() { SNES::smp.clock -= S9xAPUGetClock(CPU.Cycles); SNES::smp.enter(); spc::remainder = S9xAPUGetClockRemainder(CPU.Cycles); S9xAPUSetReferenceTime(CPU.Cycles); } uint8_t S9xAPUReadPort(int port) { S9xAPUExecute(); return static_cast(SNES::smp.port_read(port & 3)); } void S9xAPUWritePort(int port, uint8_t byte) { S9xAPUExecute(); SNES::cpu.port_write(port & 3, byte); } void S9xAPUSetReferenceTime(int32_t cpucycles) { spc::reference_time = cpucycles; } void S9xAPUEndScanline() { S9xAPUExecute(); SNES::dsp.synchronize(); if (spc::resampler->space_filled() >= APU_SAMPLE_BLOCK || !spc::sound_in_sync) S9xLandSamples(); } void S9xAPUTimingSetSpeedup(int 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::timing_hack_denominator / spc::timing_hack_numerator; UpdatePlaybackRate(); } void S9xResetAPU() { spc::reference_time = 0; spc::remainder = 0; SNES::cpu.reset(); SNES::smp.power(); SNES::dsp.power(); S9xClearSamples(); }